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     }
原文地址:https://www.cnblogs.com/fzz9/p/8970210.html