• JDBC:从原理到应用


    一、是为何物

      1、概念

      JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。简言之,JDBC就是Java用于执行SQL语句实现数据库操作的API。

      如果不好理解我们可以先看一下驱动程序的概念:

      驱动程序:指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。简言之就是操作系统通过一段代码来操作设备,而这段代码就称为该设备的驱动程序,是操作系统与硬件之间的桥梁

      同理,数据库驱动程序就是提供用于操作数据库的一段代码。我们现在想要使用纯Java来操作数据库,肯定不能直接调用这段代码,为了更便捷和灵活地使用高级语言直接对数据库进行管理,JDBC因此而产生,JDBC是一套协议,是JAVA开发人员和数据库厂商达成的协议,也就是由Sun定义一组接口,由数据库厂商来实现,并规定了JAVA开发人员访问数据库所使用的方法的调用规范。

      2、分类

      据访问数据库的技术不同, JDBC 驱动程序相应地分为四种类型。不同类型的驱动程序有着不一样的特性和使用方法。

    1. JDBC-ODBC桥驱动程序(JDBC-ODBC Bridge Driver)
      此类驱动程序由JDBC-ODBC桥和一个ODBC驱动程序组成。其工作原理是,通过一段本地C代码将JDBC调用转化成ODBC调用。这一类型必须在本地计算机上先安装好ODBC驱动程序,然后通过JDBC-ODBCBridge的转换,将Java程序中使用的JDBCAPI访问指令转化成ODBCAPI指令,进而通过ODBC驱动程序调用本地数据库驱动代码完成对数据库的访问。
       
    2. 部分Java的本地JDBCAPI驱动程序
      JDBC API驱动程序(Anative API partly Java technology-enabled Driver)此类驱动程序也必须在本地计算机上先安装好特定的驱动程序(类似ODBC),然后通过PartialJavaJDBCDriver的转换,把Java程序中使用的JDBC API转换成NativeAPI,进而存取数据库。
    3. 纯Java的数据库中间件驱动程序(目前主流驱动)
      纯Java的数据库中间件驱动程序(Pure Java Driver for Database Middleware)使用这类驱动程序时,不需要在本地计算机上安装任何附加软件,但是必须在安装数据库管理系统的服务器端加装中间件(Middleware),这个中间件负责所有存取数据库时必要的转换。其工作原理是:驱动程序将JDBC访问转换成与数据库无关的标准网络协议(通常是HTTP或HTTPS)送出,然后再由中间件服务器将其转换成数据库专用的访问指令,完成对数据库的操作。中间件服务器能支持对多种数据库的访问。
       
    4. 纯Java的JDBC驱动程序
      纯Java的JDBC驱动程序(Direct-to-DatabasePureJavaDriver)这类驱动程序是直接面向数据库的纯Java驱动程序,即所谓的"瘦"驱动程序。使用这类驱动程序时无需安装任何附加的软件(无论是本地计算机或是数据库服务器端),所有存取数据库的操作都直接由JDBC驱动程序来完成,此类驱动程序能将JDBC调用转换成DBMS专用的网络协议,能够自动识别网络协议下的特殊数据库并能直接创建数据连接。

      第一类最节省投资。但执行效率比较低,不适合对大数据量存取的应用;第二种方式具有开放性,但不够兼容使用较麻烦;

      第三类驱动程序是由纯Java语言开发而成的,并且中间件也仅需要在服务器上安装,不再需要客户端的本机代码,这类驱动程序的体积最小,效率较高,具有最大的灵活性。而且,此类驱动采用标准的网络协议,可以被防火墙支持,是开发Applet程序理想的选择(其实这些驱动是为Applet特别编写的),是Internet应用理想的解决方案。另外,开发者还可以利用单一的驱动程序连接到多种数据库。由于此种JDBC驱动程序提供了许多企业级的特征,因而非常适合用户的特殊用途,如:SSL安全、分布式事务处理等。如果用户未来扩张应用需要多个数据库,则选择这种方式是最理想的。由于它具有较好的性能和安全性,广泛应用于基于Web的应用系统的开发。其不足之处是:需要在服务器端安装中间件,这适当影响了驱动程序的效率。

      第四类驱动程序可能是最佳的JDBC驱动程序类型。这是一类新型的驱动程序,它由数据库厂商提供,能够实现对于本公司数据库系统的最优化访问。这种驱动程序的效率最高,拥有最佳的兼容性。然而,这种驱动由于使用DBMS专用的网络协议,可能不被防火墙支持,在Internet中会存在潜在安全隐患,成为这类驱动最大的缺陷。其次,不同DBMS的驱动程序不同,在一个异构计算环境中,驱动程序的数量可能会比较多。

      由于我们主流选择的JDBC驱动为第三类,下面给出第三类的JDBC驱动程序结构图:

      

      从图可见,JDBC驱动程序为两层结构,它由驱动程序客户端程序和驱动程序服务器端程序组成。客户端直接与用户交互,它为用户提供符合JDBC规范的数据库统一编程接口,客户端将数据库请求通过特定的网络协议传送给服务器。服务器充当中间件的角色,它负责接收和处理用户的请求以及支持对多种数据库的操作。JDBC驱动程序自身并不直接与数据库交互,而是借助于其他已实现的数据库驱动程序,成为“雇主”。可以想象,它“雇佣”的数据库驱动程序越多,则可支持的数据库就越多,“雇佣”的数据库驱动程序越强大,则类型3JDBC驱动程序的功能也越强大。除此之外,“雇佣”的数据库驱动程序的数量和成员可以动态改变,以满足系统扩展的需求。第三类 JDBC驱动程序的这一特性正是它强大的根源所在。

    二、动手测一测(以MySQL为例)

      了解了JDBC的大致原理,我们学会使用才是关键。

      1、环境

      既然要使用API,我们就需要导入JDBC运行环境所需要的jar包,而不同的数据库jar包也是不同的,下面以MySQL数据库为例,导入下列jar包(百度一堆或者去官网下载):

      

      2、一般步骤

    1. 注册驱动
    2. 获取连接对象(url、username、password)
    3. 编写SQL语句
    4. 获取语句执行对象
    5. (给占位符设置参数)
    6. 执行SQL语句
    7. 获得执行后的结果集
    8. 处理结果集

      3、Java代码

      下面看具体实现代码:

    •  
       1     public void JDBCDemo(String pname,int price) throws ClassNotFoundException, Exception{
       2         //1、注册驱动
       3         Class.forName("com.mysql.jdbc.Driver");
       4         //2、获取连接对象
       5         Connection conn = DriverManager.getConnection(
       6                 "jdbc:mysql://localhost:3306/web08?useUnicode=true&characterEncoding=utf-8&useSSL=false", 
       7                 "root", "password");
       8         //sql语句
       9         String sql = "select * from product where pname=? and price=?";
      10         //3、获得sql语句执行对象
      11         PreparedStatement ps = conn.prepareStatement(sql);
      12         //4、设置参数给占位符
      13         ps.setString(1, pname);
      14         ps.setInt(2, price);
      15         //5、执行并保存结果集
      16         ResultSet rs = ps.executeQuery();
      17         //6、处理结果集
      18         if(rs.next()){
      19             System.out.println(rs.getString(2)+":"+rs.getInt(3));
      20         }else
      21             System.out.println("查询失败");
      22         if(rs!=null){
      23             rs.close();
      24         }
      25         if(ps!=null){
      26             ps.close();
      27         }
      28         if(conn!=null){
      29             conn.close();
      30         }
      31     }
      32 }
    •  程序员在使用Java操作数据库时只需要按照给定的规则进行编程即可操作数据库。下面是常用api的详细讲解。

    三、API详解

      1、注册驱动程序

      Class.forName(),是最常见的注册JDBC驱动程序的方法,注册某数据库就将该数据库驱动的名称以字符串的形式作为参数:

      

      例如我们需要注册MySQL的数据库驱动,则使用代码:

    • 1 Class.froName("com.mysql.jdbc.Driver");

      另外还可以使用DriverManage的静态方法:DriverManager.registerDriver()来注册驱动,参数为对应的数据库驱动对象。

      2、获得连接对象Connection

      可以使用 DriverManager.getConnection()方法建立连接。根据传入参数的不同,有三种重载的DriverManager.getConnection()方法:

    • getConnection(String url)
    • getConnection(String url, Properties prop)
    • getConnection(String url, String user, String password)

      这里每个格式都需要一个数据库URL。 数据库URL是指向数据库的地址。制定数据库URL是建立连接相关联的大多数错误问题发生的地方。各数据库对应的URL如下所示:

      

      假设我们现在需要连接MySQL数据库,格式为:jdbc:mysql://hostname:port/datebaseName。我们需要的信息是hostname主机名和端口号,一般默认为localHost:3306;还需要datebaseName数据库名,假设为freeDB;当然还有URL格式未包含的也是必须的信息:连接数据库的用户名和密码,假设为Leslie和030401。那么就有URL:

    • 1 String url = "jdbc:mysql//localhost:3306/freeDB";

      下面分别使用三种方法来实现:

    • 使用一个URL作为参数的方式:需要将username+password以参数的形式放到URL中,但是每种数据库的放置都不太相同
      //连接mysql的纯URL
      String url = "jdbc:mysql//localhost:3306/freeDB?username=leslie&password=030401";
      Connection conn = DriverManager.getConnection(url,p);
      //连接Oracle的纯URL
      String url = "jdbc:oracle:thin:leslie/030401@192.0.0.10:1521:freeDB";
      Connection conn = DriverManager.getConnection(url);
    • 使用URL、properties作为参数的方式:即需要将username和password以键值对形式存放在properties对象中作为参数
      1 //MySql
      2 String url = "jdbc:mysql//localhost:3306/freeDB";
      3 Properties p = new Properties();
      4 p.put("username","leslie");
      5 p.put("password","030401");
      6 Connection conn  = DriverManager.getConnection(url,p);
    • 使用URL、username、password三个参数分开的方式(推荐)
      1 String url = "jdbc:mysql//localhost:3306/freeDB";
      2 String username =  "leslie";
      3 String password  = "030401";
      4 Connection conn = DriverManager.getConnection(url,username,password);

      3、获得SQL语句执行对象

      SQL语句的执行对象按理说有Statement和PreparedStatement两个,但我们一般都不会去使用Statement,先看下两者的基本描述:

    • Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句,即静态SQL语句。
    • PreparedStatement 继承于Statement。实例包含已编译的 SQL 语句,这就是使语句“准备好”。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX() 方法来提供

      简言之,Statement执行静态SQL语句,而它的子类PreparedStatement执行预编译SQL,即可传入参数。两者相比之下,PreparedStatement有以下优势

    • 预编译处理,可动态执行SQL语句。很明显,SQL语句的预编译,使用占位符?去代替未知数据,因而一个句子可以执行多种不同的SQL,而Statement需要重新书写SQL语句,笨重。
    • 速度快,执行效率高。SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。我们要利用预编译的特性,比如第一种查询和第二种查询,第二种才是预编译形式,而第一种其实是恢复了父类Statement的做法:
      1 //第一种方式,追加字符串:没有进行预编译,所以效率低
      2 String loanType = getLoanType();
      3 PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType);
      4 
      5 //第二种方式,使用占位符,进行预编译,效率高(推荐)
      6 PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?");
      7 prestmt.setString(1,loanType);
    • 可防止SQL注入式攻击。静态SQL语句说到底还是字符串,所以存在拼字符串的而带来的注入式SQL攻击风险。比如某个网站的登录验证SQL查询代码为:
      1 String sql = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"

       验证需要用户输入用户名和密码,正确则执行查询语句(登录),但如果这样输入:

      userName = "1' OR '1'='1";
      passWord = "1' OR '1'='1";

       那么执行语句就变成了:

      1 String sql = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"

       这样,where语句恒为真,就能实现无账号登录。此外便可能被恶意修改甚至删除数据表。然而使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统(eg:MySQL)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,且占位符?不允许多值,只能填入一个值,因此就算参数中含有破坏性的指令,也不会被数据库所运行。

      说了那么多,PreparedStatement如何获取呢?PreparedStatement是和连接对象Connection关联的,所以我们需要使用Connection对象的PreparedStatement(sql)方法进行获取:

    • String sql  = "select * from user where name=? and ange=?";//预处理,需要我们先写好sql语句
      PreparedStatement ps = conn.preparedStatement(sql);//conn是连接对象,参数为sql语句

      4、给占位符设置参数

      根据需要的参数类型,使用setXXX()的方法即可。(注:从第一个问好起是从1开始编号)

    • 1 ps.setString(1,"tom");
      2 ps.setInt(2,18);

      5、执行SQL语句

      sql语句有增删查改等几种类型,所以执行方法有以下三种:

    • execute():执行SQL语句,可以是任何种类的 SQL 语句。返回值是boolean类型。
    • executeQuery():至少SQL语句查询,查询结果返回为ResultSet 对象
    • executeUpdate() 执行更新语句。该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERTUPDATEDELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。返回值是int。

      例如本例中的语句是查询语句,所以执行代码为:

    • 1 ResultSet rs = ps.executeQuery();

      6、处理结果或结果集

      如果返回值是boolean或者int很好处理,但如果是查询结果集ResultSet对象,一般使用while循环来处理: 

      ResultSet 对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。next() 方法将光标移动到下一行;因为该方法在 ResultSet 对象没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。另外,可以使用ResultSet对象的getXXX(int columnIndex)获得游标所在行指定列的值。原理如下图所示:

      

      所以,本例的结果集处理如下:

    • 1 while(rs.next()){
      2        system.out.println(rs.getString(1));
      3        system.out.println(rs.getInt(2));    
      4 }

      7、关闭连接

      在JDBC程序结束之后,显式地需要关闭与数据库的所有连接以结束每个数据库会话。 但是,如果在编写程序中忘记了关闭也没有关系,Java的垃圾收集器将在清除过时的对象时也会关闭这些连接。

      依靠垃圾收集,特别是数据库编程,是一个非常差的编程实践。所以应该要使用与连接对象关联的close()方法关闭连接。要确保连接已关闭,可以将关闭连接的代码中编写在“finally”块中。 一个finally块总是会被执行,不管是否发生异常。

    • conn.close();

    四、工具类JDBCUtils的使用

       在使用JDBC连接数据库的过程中,我们发现大多数步骤都是一样的,而我们平常使用数据库的频率如果很高所以我们需要对JDBC的通用代码进行提炼,提高代码复用率,提炼出来的工具类我们一般称为JDBCUtils,工具类中包含了我们常用的很多方法,比如连接数据库和断开连接就是常用的方法,我们只要掌握了JDBC原理,就可以自己设计满足需求工具类或参考以下工具类(后面我们会说到DBUtils工具类,这是Apache组织提供的JDBC工具类,比较全面,基本能够满足我们的需求,本文最后一节会进行详解):

    •  1 package cn.jdbc;
       2 
       3 import java.io.IOException;
       4 import java.io.InputStream;
       5 import java.sql.Connection;
       6 import java.sql.DriverManager;
       7 import java.sql.PreparedStatement;
       8 import java.sql.ResultSet;
       9 import java.sql.SQLException;
      10 import java.util.Properties;
      11 
      12 /**
      13  * @author Fzz
      14  * @date 2017年11月12日
      15  * @Description TODO:提供获取连接和释放资源的方法
      16  */
      17 public class jdbcUtil{
      18     /*
      19      * 获取Properties有两种方法:
      20      * 一是使用io流获取文件内容,
      21      * 二是使用jdk提供的专门获取Properties配置文件的工具类:ResourceBundle
      22      */
      23     private static String driver;
      24     private static String url;
      25     private static String username;
      26     private static String password;
      27     static{
      28         //1、通过本类获取类加载器
      29         ClassLoader classLoader = jdbcUtil_v3.class.getClassLoader();
      30         //2、使用类加载器获得输入流
      31         InputStream is = classLoader.getResourceAsStream("jdbc.properties");
      32         //3、Properties工具类
      33         Properties prop = new Properties();
      34         //4、加载流
      35         try {
      36             prop.load(is);
      37         } catch (IOException e) {
      38             e.printStackTrace();
      39         }
      40         //5、
      41         driver = prop.getProperty("driver");
      42         url = prop.getProperty("url");
      43         username = prop.getProperty("username");
      44         password = prop.getProperty("password");
      45     }
      46     /**
      47      * @Title: getConnection
      48      * @Description: TODO(获取mySql连接 )
      49      * @return: Connection
      50      */
      51     public static Connection getConnection(){
      52         Connection conn = null;
      53         try {
      54             //注册驱动
      55             Class.forName(driver);
      56             //获取连接
      57             conn = DriverManager.getConnection(url,username,password);
      58         } catch (Exception e) {
      59             e.printStackTrace();
      60         }
      61         return conn;
      62     }
      63     
      64     /**
      65      * @Title: release
      66      * @Description: TODO:释放连接mySql的资源
      67      * @return: void
      68      */
      69     public static void release(Connection conn,PreparedStatement ps,ResultSet rs){
      70         try{
      71             if(rs != null){
      72                 rs.close();
      73             }
      74             if(ps != null){
      75                 ps.close();
      76             }
      77             if(conn != null){
      78                 conn.close();
      79             }
      80         }catch(SQLException e){
      81             e.printStackTrace();
      82         }
      83     }
      84 }

    五、JDBC共享连接池的使用

      对于频繁使用数据库或使用人数较多的项目,连接对象Connection的开开关关也是不好的吖,我们就可以设置JDBC连接池,连接池就相当于一个存放若干连接对象的池子,由于用户人数多,每次访问数据库需要一个连接对象,那么就从池子里取出一个,用完放回即可,而不是销毁。这样,就可以实现连接池共享的目的,减少系统资源消耗。

      Java提供了一个公共接口:Javax.sql.DataSource。此接口提供了 DataSource 对象所表示的物理数据源的连接。作为 DriverManager 工具的替代项DataSource 对象是获取连接的首选方法。

      简单来说,就是DateSource接口是Drivermanager的替代项,提供了getConnection()方法并生产标准的Connection对象,那么要实现连接池,就需要实现该接口和该方法。(所以我们常说的数据源也就是连接池)连接池处理自定义的方式,目前主要的连接池工具有C3P0(主流)、DBCP。

      1、自定义共享连接池

      一般步骤:

    1. 实现数据源DataSource,即实现Javax.sql.DataSource接口;由于只是简单的演示,我们只实现其中的getConnection()方法即可。
    2. 创建一个LinkList容器。既然是“池子”,就需要保存东西,即存储连接池对象,而连接池涉及移除/添加连接池对象,优先考虑使用LinkList来存储。
    3. 使用静态代码块初始化若干个连接池对象。由于只是测试,我们初始化3个就行。
    4. 实现getConnection()方法。注意,为了保证连接对象只提供给一个线程(一个用户)使用,我们需要先将连接对象从池子中取出来
    5. 用完的连接对象不需要执行close()而是放回池子去
    •  1 package cn.jdbcUtils;
       2 
       3 import java.io.PrintWriter;
       4 import java.sql.Connection;
       5 import java.sql.SQLException;
       6 import java.sql.SQLFeatureNotSupportedException;
       7 import java.util.LinkedList;
       8 import java.util.logging.Logger;
       9 
      10 import javax.sql.DataSource;
      11 
      12 import cn.day09.jdbc.jdbcUtil;
      13 
      14 /**
      15  * @author Fzz
      16  * @date 2017年11月12日
      17  * @Description TODO:自定义JDBC连接池
      18  */
      19 
      20 public class MyDataSource implements DataSource{//[1]实现接口
      21     //[2]创建一个容器存储连接池里的Connection对象。
      22     private static LinkedList<Connection> pool = new LinkedList<Connection>();
      23     
      24     //[3]初始化3个Connection对象放进池子。
      25     static{
      26         Connection conn = null;
      27         for (int i = 0; i < 3; i++) {
      28             conn = jdbcUtil.getConnection();//这里我们使用上面创建的jdbcUtils来获取连接
      29             pool.add(conn);
      30         }
      31     }
      32 
      33     @Override
      34     /**
      35      * [4]从池子里取连接对象
      36      */
      37     public Connection getConnection() throws SQLException {
      38         //使用前先判断连接池是否有连接对象,没有则添加
      39         Connection conn = null;
      40         if(pool.size() == 0){
      41             for (int i = 0; i < 3; i++) {
      42                 conn = jdbcUtil.getConnection();
      43                 pool.add(conn);
      44             }
      45         }
      46         conn = pool.removeFirst();//取出来
      47         return conn;
      48     }
      49     
      50     /**
      51      * [5]用完归还连接到连接池
      52      */
      53     public boolean returnConnToPool(Connection conn){
      54         return pool.add(conn);
      55     }
      56     
      57     //下面是未实现的方法。
      58     @Override
      59     public PrintWriter getLogWriter() throws SQLException {
      60         // TODO Auto-generated method stub
      61         return null;
      62     }
      63     @Override
      64     public void setLogWriter(PrintWriter out) throws SQLException {
      65         // TODO Auto-generated method stub
      66         
      67     }
      68     @Override
      69     public void setLoginTimeout(int seconds) throws SQLException {
      70         // TODO Auto-generated method stub
      71         
      72     }
      73     @Override
      74     public int getLoginTimeout() throws SQLException {
      75         // TODO Auto-generated method stub
      76         return 0;
      77     }
      78     @Override
      79     public Logger getParentLogger() throws SQLFeatureNotSupportedException {
      80         // TODO Auto-generated method stub
      81         return null;
      82     }
      83     @Override
      84     public <T> T unwrap(Class<T> iface) throws SQLException {
      85         // TODO Auto-generated method stub
      86         return null;
      87     }
      88     @Override
      89     public boolean isWrapperFor(Class<?> iface) throws SQLException {
      90         // TODO Auto-generated method stub
      91         return false;
      92     }
      93     @Override
      94     public Connection getConnection(String username, String password) throws SQLException {
      95         // TODO Auto-generated method stub
      96         return null;
      97     }
      98 }

      【扩展:方法的增强】

      自定义共享连接池到了这里基本已经完成,但美中不足的是:之前我们使用连接对象是这样的,用getConnection()方法获取,用完再用close()关闭。现在我们通过共享连接池来使用连接对象,是用getConnection()取出连接对象,使用returnConnToPool()归还连接对象。而美中不足的就是,我们希望通过共享连接池使用连接对象是仍然使用getConnection()和close()这两个方法,以保持前后一致的美感(呵呵),但我们是实现DataSource接口,接口中没有close()方法。可能你会说这是多余,就直接使用returnConnToPool()不挺好的吗,还能加深理解。

      其实,这里我只是作为另一个知识点的引申——方法的增强,以此找了个小小的借口。(碧池)

      目前有四种方法可以进行某个方法的增强,但各有优缺点:

    • 使用继承增强方法
      使用前提是需要实现继承关系,子类通过继承父类方法,进而改进方法。
    • 使用装饰者设计模式增强方法
      装饰者设计模式是专门用来增强方法的设计模式,使用前提是需要装饰者实现被装饰对象(即被增强对象)相同接口,以实现交互。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。装饰者模式比继承灵活,但程序也相对复杂。
    • 使用动态代理增强方法。(难点)
      动态代理与装饰者模式类似,且需要使用反射技术。
    • 使用字节码增强方法。(难点)
      Java字节码增强指的是在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。Java字节码增强的应用场景主要是减少冗余代码,对开发人员屏蔽底层的实现细节。字节码增强技术主要有两种实现机制:一种是通过创建原始类的一个子类;另一种是直接去修改原先的class字节码。
      目前有cglib、Javassist等框架提供使用。

      上面只是简单的介绍,有意者可以自行搜索。此处不是本文重点故不再赘述,其实是笔者自己也没学。(只想对笔者说一句:“多捞啊”)

      2、C3P0连接池的使用

      C3P0是开源的共享连接池,比如Sring、Hibernate等开源项目都有使用C3P0。C3P0是第三方工具,除了需要导出相应jar包,还需要编写配置文件 c3p0-config.xml

    1. 导包
      我们首先去下载一个C3P0压缩包(自行百度)。然后解压找到lib目录,导入第一个包即可:
    2. 编写配置文件:c3p0-config.xml
      其实不一定非要配置文件,但相关设置比较麻烦建议编写。在压缩包的doc目录下有 index.html是c3p0的帮助文档(英文版):


      帮助文档中就有配置文件相关格式的说明,这里总结如下:
       1 <?xml version="1.0" encoding="UTF-8"?>
       2 <c3p0-config>
       3     
       4     <!-- 默认配置1-->
       5   <default-config>
       6       <连接数据库的一些基本参数>
       7     <property name="driverClass">com.mysql.jdbc.Driver</property>
       8     <property name="jdbcUrl">jdbc:mysql:///FreeDB</property>
       9     <property name="user">leslie</property>
      10     <property name="password">030401</property>
      11     <property name="initialPoolSize">5</property>
      12     <property name="maxPoolSize">20</property>
      13   </default-config>
      14   
      15   <!-- 配置2命名 -->
      16   <named-config name="mysql_web09"> 
      17     <property name="driverClass">数据库驱动</property>
      18     <property name="jdbcUrl">数据库url</property>
      19     <property name="user">用户名</property>
      20     <property name="password">密码</property>
      21     <property name="initialPoolSize">最小连接数</property>
      22     <property name="maxPoolSize">最大连接数</property>
      23   </named-config>
      24   
      25 </c3p0-config>
    3. C3P0Utils
      c3p0提供了一个工具类可以方便我们的使用,免去了多余类和方法的学习和直接使用:
       1 package cn.c3p0Utils;
       2 
       3 import java.sql.Connection;
       4 import java.sql.SQLException;
       5 import com.mchange.v2.c3p0.ComboPooledDataSource;
       6 
       7 /**
       8  * C3P0工具类
       9  * @author Fzz
      10  * @date 2017年11月12日12  */
      13 public class C3P0Utils {
      14     //创建c3p0的连接池对象(注:ComboPooledDataSource空参数时使用默认配置,否则传入需要使用的配置名字即可)
      15     private static ComboPooledDataSource datasource = new ComboPooledDataSource();
      16     /**
      17      * 返回DataSource
      18      * @Title: getDataSource
      19      * @Description: TODO:获得连接池对象
      20      * @return: ComboPooledDataSource
      21      */
      22     public static ComboPooledDataSource getDataSource(){
      23         return datasource;//将连接池对象返回即可
      24     }
      25     /**
      26      * 返回Connection
      27      * @Title: getConnection
      28      * @Description: TODO:获得连接对象(从池子中取)
      29      * @return: Connection
      30      */
      31     public static Connection getConnection(){
      32         try {
      33             return datasource.getConnection();
      34         } catch (SQLException e) {
      35             throw new RuntimeException(e);
      36         }
      37     }
      38 }

      注:这里C3P0Utils提供没有进行连接对象的归还方法,这是因为一我们在使用完连接对象conn后直接使用conn.close()即可归还。(c3p0使用代理已经增强了close()方法不是关闭是归还连接池,和上文拓展说的一个意思),二是后面我们要使用DBUtils工具类进行数据库操作,而DBUtils底层会自动维护连接对象,固此处不再提供归还方法。

    六、使用DBUtils工具类进行数据库测CRUD(增查改删)

      上文我们也说到JDBC工具类对提高代码复用率的重要性,DBUtils是Apache大佬提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时又不会影响程序的性能。

      1、准备工作

    1. 导包:既然是第三方工具,就需要导包,包括数据库驱动包、DBUtils包和连接池包(这里我们使用C3P0连接池):
    2. 准备连接池:比如编写c3p0工具类和配置文件。
    3. JavaBean类的编写。JavaBean是一种JAVA语言规范写成可重用组件的类。写成JavaBean的类,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性,set和get方法获取。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制(反射机制)发现和操作这些JavaBean 的属性。比如我的数据库中有一个category表,那么我就可以设计一个JavaBean类。
       1 package cn.domain;
       2 
       3 public class category {
       4     private String cid;
       5     private String cname;
       6     public category(){
       7         
       8     }
       9     public String getCid() {
      10         return cid;
      11     }
      12     public void setCid(String cid) {
      13         this.cid = cid;
      14     }
      15     public String getCname() {
      16         return cname;
      17     }
      18     public void setCname(String cname) {
      19         this.cname = cname;
      20     }
      21     
      22 }

      2、三个核心类

      在使用之前我们先学习DBUtils的三个核心类:提供SQL语句操作API的QueryRunner类、用于执行select语句后进行结果集封装的ResultSetHandler接口、提供资源关闭、处理事务等方法的DbUtils工具类

    1. QueryRunner类:
      new QueryRunner(DateSource ds):提供连接数据源(连接池)的方式。
      update(String sql,Object...params):执行更新操作。
      query(String sql,ResultSetHandler<T> rs,Object...paramas):执行查询操作。
    2. ResultSetHandler接口:(重点常用的三个已标红)


    3. DbUtils工具类:
      closeQuietly(Connection  conn):关闭连接,如果有异常try后不抛。
      commitAndCloseQuietly(Connection  conn):提交事务并关闭连接。
      rollbackAndCloseQuietly(Connection  conn):回滚事务并关闭连接。

      3、实现CRUD操作

      这里举一个例子作为参考,其他步骤基本一致:

     1     //使用DBUtils查询数据库
     2     @Test
     3     public void queryAll(){
     4         try {
     5             //1、使用QueryRunner获得数据源(连接池)
     6             QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
     7             //2、编写sql语句
     8             String sql = "select * from category";
     9             //3、执行sql语句
    10             List<category> query = qr.query(sql,new BeanListHandler<category>(category.class));
    11             //4、处理结果
    12             for(category c:query){
    13                 System.out.println(c.getCid()+":"+c.getCname());
    14             }
    15         } catch (SQLException e) {
    16             throw new RuntimeException(e);
    17         }
    18     }
  • 相关阅读:
    白学jquery Mobile《构建跨平台APP:jQuery Mobile移动应用实战》串行4(场景变化)
    Effective C++:规定20: 宁pass-by-reference-to-const更换pass-by-value
    UI布局术语
    The Runtime Interaction Model for Views-UI布局事件处理流程
    布局的核心在于求解位置和尺寸
    Masonry 原理与使用说明
    布局、约束与函数
    Masonry 原理一
    Masonry基础API
    margin与padding如何进行区分
  • 原文地址:https://www.cnblogs.com/fzz9/p/8970210.html
Copyright © 2020-2023  润新知