zoukankan      html  css  js  c++  java
  • 《JavaWeb从入门到改行》JDBC经典秘方QueryRunner


    目录:

    基础篇_功能各自回顾

             JDBC基础代码回顾(使用JdbcUtils工具简化)                                                                  

             c3p0数据库连接池的使用(使用JdbcUtils工具简化)

             大数据的插入(使用c3p0+JdbcUtils工具简化)

             批处理操作(使用c3p0+JdbcUtils工具简化)

             数据库中的事务处理(使用c3p0+JdbcUtils工具简化)

            多线程的并发控制

    进阶篇_迈向标准开发
            自己编写dbutils工具( QueryRunner 、TxQueryRunner和JdbcUtils)    (本文核心)

             封装成jar包后的标准开发--common-dbutisl和itcast-tools    (本文核心)


    数据库使用MySQL数据库,使用的表结构 :

    1 CREATE TABLE tab_bin(
    2     id     INT     PRIMARY KEY AUTO_INCREMENT,
    3     filename    VARCHAR(100),
    4     data     MEDIUMBLOB
    5 );
    tab_bin
    1 CREATE TABLE t_user (
    2   username varchar(50) DEFAULT NULL,
    3   password varchar(50) DEFAULT NULL,
    4   age int(11) DEFAULT NULL,
    5   gender varchar(20) DEFAULT NULL
    6 );
    t_user
    1 CREATE TABLE account (
    2    id int(11) NOT NULL AUTO_INCREMENT,
    3    NAME varchar(30) DEFAULT NULL,
    4    balance decimal(10,0) DEFAULT NULL,
    5    PRIMARY KEY (id)
    6 ) ;
    account
    1 CREATE TABLE t_customer (
    2   username VARCHAR(50) DEFAULT NULL,
    3   age INT(11) DEFAULT NULL,
    4   balance DOUBLE(20,5) DEFAULT NULL
    5 );
    t_customer

    JDBC基础代码回顾

    JDBC四大核心对象: 全部来自   java.sql    包下

    DriverManager 注册驱动,获取Connection对象
    Connection   连接对象,获取preparedStatement
    PreparedStatement sql语句发送器,执行更新、查询操作
    ResultSet 结果集,通过next()获取结果

    项目src文件下编写dbconfig.properties配置文件:

    name value
    driverClassName com.mysql.jdbc.Driver
    url jdbc:mysql://localhost:3306/jdbc_test01
    username root
    password 123456

     @演示

     1 package cn.kmust.jdbc.demo3.utils.version1;
     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.SQLException;
     8 import java.util.Properties;
     9 /**
    10  * JdbcUtils工具类
    11  * @功能 从dbconfig.properties配置文件中获取四大参数,加载驱动类,完成连接数据库
    12  *        返回Connection对象
    13  *      
    14  * @author ZHAOYUQIANG
    15  *
    16  */
    17 public class JdbcUtils {
    18     private static Properties props = null ;
    19     /**
    20      * 这些代码都是只执行一次的,在JdbcUtils类被加载时执行
    21      */
    22     static{        
    23         try{
    24             /*
    25              * 1. 加载dbconfig.properties配置文件里面的内容到props中    
    26              */
    27             InputStream in= JdbcUtils.class.getClassLoader()
    28                     .getResourceAsStream("dbconfig.properties");
    29             props = new Properties();
    30             props.load(in);    
    31         }catch(IOException e){
    32             throw new RuntimeException(e);
    33         }
    34         try{
    35             /*
    36              * 2. 加载驱动类
    37              */
    38             Class.forName(props.getProperty("driverClassName"));
    39         }catch(ClassNotFoundException e){
    40             throw new RuntimeException(e);
    41         }
    42     }
    43     public static Connection getConnection() throws  SQLException{                    
    44         /**
    45          * 这些代码可以被反复调用,因为获取数据库连接可能需要多次获取
    46          */
    47         /*
    48          * 3. 调用DriverManager.getConnection()得到Connection
    49          */
    50         return DriverManager.getConnection(
    51                 props.getProperty("url"),
    52                 props.getProperty("username"),
    53                 props.getProperty("password"));                        
    54     }
    55 }
    JdbcUtils类_简化代码小工具
      1 package cn.kmust.jdbc.demo3.utils.version1;
      2 
      3 import java.sql.Connection;
      4 import java.sql.DriverManager;
      5 import java.sql.ResultSet;
      6 import java.sql.SQLException;
      7 import java.sql.PreparedStatement;
      8 
      9 import org.junit.Test;
     10 /**
     11  * 对数据库的操作
     12  * @功能 更新操作 和 查询操作 以及 测试
     13  *
     14  * @author ZHAOYUQIANG
     15  *
     16  */
     17 public class Demo {
     18      /**
     19       * 增、删、改
     20       * @param username
     21       * @param password
     22       * @return
     23       */
     24      public void fun1(String username,String password,int age,String gender)   {
     25            Connection con = null ; 
     26            PreparedStatement pstmt = null ;
     27    
     28             try {
     29                 /*
     30                    * 1. 连接数据库,给con赋值
     31                    * 2. 准备SQL语句
     32                    * 3. 调用 Connection对象的方法来得到PrepareStatement对象
     33                    * 4. 使用PrepareStatement对象给sql参数赋值
     34                    * 5. 使用PrepareStatement对象向数据库发送sql语句,并且返回影响记录的行数
     35                    * 6. 关闭资源。 后创建的先关闭
     36                    */                   
     37                    con = JdbcUtils.getConnection();
     38                    String sql ="INSERT INTO t_user VALUES (?,?,?,?)" ;  //sql语句中不加分号
     39 //                   String sql ="UPDATE t_user SET password=? WHERE username=?";
     40 //                   String sql ="DELETE FROM stu WHERE username=?";
     41                     pstmt = con.prepareStatement(sql);
     42                     pstmt.setString(1, username);
     43                     pstmt.setString(2, password);
     44                     pstmt.setInt(3, age);
     45                     pstmt.setString(4, gender);
     46                     int r = pstmt.executeUpdate();
     47                     System.out.println(r);    //输出影响记录的行数
     48             } catch (Exception e) {
     49                 throw new RuntimeException(e);            
     50             } finally{                
     51                 if (pstmt !=null) 
     52                     try{ pstmt.close();}catch (Exception e){throw new RuntimeException(e);}
     53                 if (con != null) 
     54                     try{ con.close();}catch (Exception e){throw new RuntimeException(e);}
     55             }                           
     56      }
     57      /**
     58       * 查询数据库
     59       */
     60       @Test
     61      public void  fun2() {
     62         Connection con = null ; 
     63         PreparedStatement pstmt = null ;
     64         ResultSet rs = null ;   
     65        try {
     66            /*
     67             * 1. 连接数据库,给con赋值
     68             * 2. 准备SQL语句
     69             * 3. 调用 Connection对象的方法来得到PrepareStatement对象
     70             * 4. 使用PrepareStatement对象给sql参数赋值
     71             * 5. 使用PrepareStatement对象向数据库发送sql语句,并且返回ResultSet对象的结果集(就是一个表)
     72             * 6. 调用ResultSet的boolean next()方法 遍历结果集的每行记录,方法返回的true和false代表光标指针所指的这一行有没有记录
     73             * 7. 调用ResultSet的getXXX(第几列)/getXXX("列名字")方法  返回当前行记录的列数据。其中,getObject和getString 能够获得所有的类型       
     74             * 8. 关闭资源。 后创建的先关闭
     75             */
     76             con = JdbcUtils.getConnection(); 
     77             String sql = "select * from t_user where username=? and password=? " ;
     78             pstmt = con.prepareStatement(sql);
     79             pstmt.setString(1, "zhaoLiu");
     80             pstmt.setString(2, "123456");                      
     81             rs=pstmt.executeQuery();
     82             while(rs.next()){        
     83                 String name = rs.getString(2);//可以用列名字 : String name = rs.getString("sname");
     84                 String password = rs.getString("password");    
     85                 int age = rs.getInt("age");
     86                 String gender = rs.getString("gender");
     87                 System.out.println("名字"+name+",密码"+password+",年龄"+age+",性别"+gender);
     88            }
     89         } catch (Exception e) {
     90             throw new RuntimeException(e);
     91         } finally{
     92             if (rs != null)
     93                 try{ rs.close();}catch (Exception e){throw new RuntimeException(e);}
     94             if (pstmt !=null) 
     95                 try{ pstmt.close();}catch (Exception e){throw new RuntimeException(e);}
     96             if (con != null) 
     97                 try{ con.close();}catch (Exception e){throw new RuntimeException(e);}
     98        }        
     99       }
    100 
    101       /**
    102        *  测试增删改 
    103        */
    104       @Test
    105       public void fun1Test(){
    106           String username="zhaoLiu" ;
    107           String password ="123456";
    108           int age = 12 ;
    109           String gender = "男";
    110           fun1(username,password,age,gender);
    111       }    
    112 }
    Dao类_操作/测试数据库

    c3p0数据库连接池的使用

    c3p0数据库连接池 :   数据库的很多连接对象都放在池中被管理着,谁用谁去租,用完归还就行了。 c3p0就是一个比较不错的池子 。

    DataSource对象也在 java.sql  包下

    项目src文件下编写c3p0-config.xml配置文件(文件中也给出了oracle的配置模版):(@注意:配置文件的名称c3p0-config.xml是一个官方给定的标准,不是随意起的名字)

    @演示

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <c3p0-config>
     3     <default-config><!-- 这是默认的配置信息 -->
     4         <!-- 连接四大参数配置 -->
     5         <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_test01</property>
     6         <property name="driverClass">com.mysql.jdbc.Driver</property>
     7         <property name="user">root</property>
     8         <property name="password">123456</property>
     9         <!-- 池本身的参数配置 -->
    10         <property name="acquireIncrement">3</property>
    11         <property name="initialPoolSize">10</property>
    12         <property name="minPoolSize">2</property>
    13         <property name="maxPoolSize">10</property>
    14     </default-config>
    15     
    16     <!-- 专门为oracle准备的配置信息 -->
    17     <!-- 这也是命名配置信息,在JDBC中创建连接池对象的时候要加 oracle-config这个参数-->
    18     <named-config name="oracle-config">
    19         <property name="jdbcUrl">  oracle的url   </property>
    20         <property name="driverClass">  oracle的驱动   </property>
    21         <property name="user">    oracle的用户 </property>
    22         <property name="password">    密码</property>
    23         <property name="acquireIncrement">3</property>
    24         <property name="initialPoolSize">10</property>
    25         <property name="minPoolSize">2</property>
    26         <property name="maxPoolSize">10</property>
    27     </named-config>
    28     
    29 </c3p0-config>
    c3p0-config.xml
     1 package cn.kmust.jdbc.demo9.c3p0Pool.utils.version2;
     2 
     3 import java.sql.Connection;
     4 import java.sql.SQLException;
     5 
     6 import javax.sql.DataSource;
     7 
     8 import com.mchange.v2.c3p0.ComboPooledDataSource;
     9 /**
    10  * JdbcUtils工具类     [c3p0数据库连接池专用工具类]
    11  *   需要编写c3p0-config.xml配置文件
    12  * @功能 利用c3p0-config.xml配置文件获取连接对象并且返回 。 连接池对象也返回
    13  * @c3p0说明   借用池中的Connection对象,用完归还
    14  *           需要编写c3p0-config.xml文件,里面有四大连接参数和池子本身的参数配置
    15  *           文件不需要说明在什么位置,因为在创建连接池对象时,这个对象会自动加载配置文件
    16  * @author ZHAOYUQIANG
    17  *
    18  */
    19 public class JdbcUtils {
    20     /*
    21      * 1. 创建连接池对象
    22      *      创建时自动加载c3p0-config.xml配置文件
    23      *      如果是配置文件中是命名配置,如oracle,那么构造器的参数指定命名配置为元素的名称,也就是说代码要变成如下形式:
    24      *            ComboPooledDataSource dataSource = new ComboPooledDataSource("oracle-config");
    25      */
    26     private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    27     /**
    28      * 使用连接池返回一个连接对象
    29      * @return
    30      * @throws SQLException
    31      */
    32     public static Connection getConnection() throws SQLException{
    33         return dataSource.getConnection();
    34     }
    35     /**
    36      * 返回池对象
    37      * @return
    38      */
    39     public static DataSource getDataSource(){
    40         return dataSource;
    41     }
    42 }
    JdbcUtis工具类_c3p0版本
     1 package cn.kmust.jdbc.demo9.c3p0Pool.utils.version2;
     2 
     3 import java.beans.PropertyVetoException;
     4 import java.sql.Connection;
     5 import java.sql.SQLException;
     6 
     7 import org.junit.Test;
     8 
     9 import com.mchange.v2.c3p0.ComboPooledDataSource;
    10 
    11 /**
    12  * Test
    13  * @功能  通过JdbcUtils工具获取池子中的一个连接对象,并且输出这个连接,查看是否获取到了
    14  *
    15  * @author ZHAOYUQIANG
    16  *
    17  */
    18 public class Demo01 {
    19     @Test
    20     public void fun() throws SQLException {
    21         /*
    22          * 1. 通过c3p0专属的JdbcUtils工具[版本2]直接获取连接
    23          * 2. 把连接归还给池子
    24          */
    25         Connection con = JdbcUtils.getConnection();
    26         /**
    27          * 此处代码进行sql的操作,本次省略。
    28          */
    29         System.out.println(con);
    30         con.close();
    31     }
    32 }
    Test类_查看获取的连接

    大数据的插入(使用c3p0+JdbcUtls简化代码)

     @解决的问题  如何向mysql中插入一部10M左右的.mp3文件 ???

    MySQL中提供存储大数据的类型如下:(@注意 标准SQL中提供的类型并非如下类型,请自行百度)

    类型  长度
    tinytext 28-1 B(256B)
    text 216-1B(64K)
    mediumtext 224-1B(16M)
    longtext 232-1B(4G)

    待插入文件地址 : D:十年.mp3

    待下载文件地址: E:十年.mp3

    @使用 c3p0数据库连接池的使用 中的c3p0-config.xml 和JdbcUtils工具

    @演示 

     1 package cn.kmust.jdbc.demo4.bigData;
     2 
     3 import java.io.FileInputStream;
     4 import java.io.FileOutputStream;
     5 import java.io.InputStream;
     6 import java.io.OutputStream;
     7 import java.sql.Blob;
     8 import java.sql.Connection;
     9 import java.sql.PreparedStatement;
    10 import java.sql.ResultSet;
    11 
    12 import javax.sql.rowset.serial.SerialBlob;
    13 
    14 import org.apache.commons.io.IOUtils;
    15 import org.junit.Test;
    16 
    17 import cn.kmust.jdbc.demo3.utils.version1.JdbcUtils;
    18 
    19 /**
    20  * 大数据,把mp3保存到mysql数据库的表中
    21  *
    22  * @author ZHAOYUQIANG
    23  *
    24  */
    25 public class BigData{
    26     /**
    27      * 把tmp3保存到数据库中
    28      */
    29     @Test
    30     public void fun1(){
    31         Connection con = null ; 
    32         PreparedStatement pstmt = null ;
    33         try{
    34             con = JdbcUtils.getConnection();
    35             String sql = "insert into tab_bin values(?,?,?)";
    36             pstmt =con.prepareStatement(sql);
    37             pstmt.setInt(1, 1);
    38             pstmt.setString(2,"十年.mp3");
    39             /**
    40              * 向数据库中存入mp3音乐
    41              *    数据库中存储音乐的类型是要用大小是0~16MB的Blob类型
    42              *    要做的事情是把硬盘中.mp3文件变成Blob类型,然后存入到数据库中
    43              */
    44             /*
    45              * 1. 将流文件变成字节数组
    46              *      导入小工具包: Commons-io.jar
    47              * 2. 使用字节数组创建Blob对象
    48              */
    49             byte[] bytes = IOUtils.toByteArray(new FileInputStream("D:/十年.mp3"));    
    50             Blob blob = new SerialBlob(bytes) ; 
    51             pstmt.setBlob(3, blob);
    52             pstmt.executeUpdate();
    53         }catch(Exception e){
    54             throw new RuntimeException(e);
    55         }finally{
    56             if (pstmt !=null) 
    57                 try{ pstmt.close();}catch (Exception e){throw new RuntimeException(e);}
    58             if (con != null) 
    59                 try{ con.close();}catch (Exception e){throw new RuntimeException(e);}        
    60         }
    61         
    62     }
    63     /**
    64      * 从数据库读取mp3
    65      *    抛异常的语句简写
    66      */
    67     @Test
    68     public void fun2()throws Exception{
    69         Connection con = null ;
    70         PreparedStatement pstmt = null ;
    71         ResultSet rs =null ;
    72         con = JdbcUtils.getConnection();
    73         String sql = "select * from tab_bin";
    74         pstmt = con.prepareStatement(sql);
    75         rs = pstmt.executeQuery();
    76         /**
    77          * 获取rs中名为data的列数据,也就是获取MP3
    78          *    先取到数据库中的Blob类型,然后把Blob变成硬盘上的文件
    79          */
    80         System.out.println(rs);
    81         if(rs.next()){            
    82             /*
    83              * 1. 获取数据库中的Blob类型
    84              * 2. 通过Blob得到输入流对象
    85              * 3. 自己创建输出流对象
    86              * 4. 把输入流的数据写入到输出流中
    87              */
    88             Blob blob =  rs.getBlob("data");
    89             InputStream in = blob.getBinaryStream();
    90             OutputStream out =new FileOutputStream("E:/十年.mp3");
    91             IOUtils.copy(in, out);
    92             con.close();
    93         }                
    94     }
    95 }
    BigData类_操作大数据

    @可能出现错误   如果出现以下错误提示,请打开MySQL对于插入数据大小的限制。具体操作 : 在MySQLMySQL Server 5.5my.ini  文件中添加  max_allowed_packet = 16M     

    1 java.lang.RuntimeException: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (8498624 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.

    批处理操作(使用c3p0+JdbcUtls简化代码)

     批处理的意思是: PreparedStatement对象可以成批的发送SQL语句给服务器执行,对象中有集合。 特别是在向数据库中插入很多数据时,开启了批处理后,1万记录只需要不到半秒,如果没有开启,则需要7分钟。

    MySQL中开启批处理需要在url上添加参数,即: jdbc:mysql://localhost:3306/jdbc_test01?rewriteBatchedStatements=true

    @演示

     1                       /**
     2              * 批处理sql语句
     3              *     pstmt对象中有集合
     4              *     用循环向psmt中添加sql参数,然后调用执行,完成向数据库发送
     5              */
     6             for(int i = 0 ;i<10000 ;i++){
     7                 /*
     8                  * 一条记录的4个列参数
     9                  */
    10                 pstmt.setString(1, "name"+i);
    11                 pstmt.setString(2,"password"+i+1);
    12                 pstmt.setInt(3, i+2);
    13                 pstmt.setString(4, i%2==0?"男":"女");
    14                 /*
    15                  * 添加批
    16                  *    将4个列参数添加到pstmt中,经过循环,把100行记录,400个列参数都添加到pstmt中
    17                  */
    18                 pstmt.addBatch();
    19             }
    20             /*
    21              * 执行批
    22              */
    23             pstmt.executeBatch();
    批处理的JDBC代码

    数据库中的事务(使用c3p0+JdbcUtls简化代码)

    转账功能: A向B转账100元,数据库操作是 : 1. A减少100元,2. B增加100。 若A减少100元,硬件崩溃,则B并没有增 加100元,这是不行的,应该回滚到A还没有减少100的时候 。 这就需要事务。

    同一个事务中的多个操作,要么完全成功提交,要么完全失败回滚到原点,不可能存在成功一半的情况,具有原子性、一致性、隔离性、持久性。 所以可以将以上的两步操作放到一个事务中。

    JDBC中与事务相关的对象是Connection 对象,该对象有三个方法来操作事务:

    setAutoCommit(boolean) false表示开启事务
    commit() 提交事务
    rollback() 回滚事务

    @注意  同一事务的所有操作,使用的是同一个Connection对象

     @使用 c3p0数据库连接池的使用 中的c3p0-config.xml 和JdbcUtils工具

    @演示

     1 package cn.kmust.jdbc.demo6.routine;
     2 
     3 import java.sql.Connection;
     4 import java.sql.SQLException;
     5 import org.junit.Test;
     6 import cn.kmust.jdbc.demo3.utils.version1.JdbcUtils;
     7 
     8 /**
     9  * service层
    10  * @功能  演示转账业务  
    11  *       演示事务
    12  * @缺陷  同一个事务必须用同一个Connection,所以所有对Connection的管理都转移到了service中了,
    13  *        污染了Service层,犯了大忌!!! 问题的解决要到TxQueryRunner类中解决
    14  *        
    15  * @author ZHAOYUQIANG
    16  *
    17  */
    18 public class Demo01 {
    19     /**
    20      * 转账的方法
    21      * @param from 从谁那儿转
    22      * @param to   转给谁
    23      * @param money  转多少钱
    24      */
    25     public void transfer(String from,String to,double money){
    26         Connection con = null ;
    27         try{
    28             con = JdbcUtils.getConnection();
    29             /*
    30              * 1. 开启事务
    31              * 2. 调用方法进行转账
    32              *     转账有两步,要么两步都成功,要么都失败
    33              * 3. 提交事务
    34              * 4. 回滚事务
    35              * 5. 关闭连接
    36              */
    37             con.setAutoCommit(false);
    38             AccountDao dao = new AccountDao();
    39             dao.updateBalance(con,from, -money);
    40             /*
    41              * 中间故意设置异常,测试事务回滚
    42              */
    43 //            if(true){
    44 //                throw new RuntimeException("对不起,操作失败!");
    45 //            }
    46             dao.updateBalance(con,to, money);
    47             con.commit();
    48         }catch(Exception  e){
    49             try{con.rollback();}catch(SQLException e1){ throw new RuntimeException(e);}
    50             throw new RuntimeException(e);
    51         }finally{
    52             try{con.close();}catch(Exception e){ throw new RuntimeException(e);}
    53         }        
    54     }
    55     /**
    56      * 测试方法
    57      */
    58     @Test
    59     public void test(){
    60         /*
    61          * zs给ls转账100元
    62          */
    63         transfer("zs", "ls", 100);                
    64     }
    65 }
    AccountService类_转账业务/测试
     1 package cn.kmust.jdbc.demo6.routine;
     2 
     3 import java.sql.Connection;
     4 import java.sql.PreparedStatement;
     5 import cn.kmust.jdbc.demo3.utils.version1.JdbcUtils;
     6 
     7 /**
     8  * Dao层 操作数据库
     9  * @功能  转账: 完成对数据库的操作(A用户减少100,B用户增加增加100。)
    10  * 
    11  * @author ZHAOYUQIANG
    12  *
    13  */
    14 public class AccountDao {
    15     /**
    16      * 修改指定用户的余额
    17      * @param name
    18      * @param balance
    19      */
    20     public void updateBalance(Connection con ,String name , double balance){        
    21         PreparedStatement pstmt = null ;
    22         try{        
    23             String sql = "update account set balance=balance+? where name=?";
    24             pstmt = con.prepareStatement(sql);
    25             pstmt.setDouble(1,balance);
    26             pstmt.setString(2, name);
    27             pstmt.executeUpdate();
    28         }catch(Exception e){
    29             throw new RuntimeException(e);
    30         }
    31     }
    32 }
    AccountDao_sql操作

    @事务并发的缺陷  多个事务并发执行时,因为同时调用Connection对象而引发严重问题,造成脏读、不可重复读、虚读的严重问题。 需要进行多线程并发控制

    多线程并发控制的操作 

    @java中的并发问题  如果多个线程同时访问同一个数据的时候,线程争夺临界资源数据引发问题 。 多个线程可描述为多个任务,例如: 同一个帐号在不同ATM机上取钱,这是两个任务(也是两个线程),两个线程都要调用   Class 取钱{ private String 帐号 ; public void fun1() {}  public void fun2() {} ....  }   中的帐号变量(成员变量),就会出现读写问题。 一般来说,l临界资源都是类中的成员变量,而非方法中的局部变量 。

    JDBC操作中,因为多个事务同时调用Connection对象,会引起并发问题。

    @解决问题  解决的方法可以用synchrozined{}同步块 , 但是此法耗时严重。采用效率能提高150倍的ThreadLocal比较好。ThreadLocal类是一个泛型类,@思想  将临界资源给每个线程都分配一份,来解决多个线程争夺同一个资源而引发的难题。

    ThreadLocal类java.lang包下已经写好的封装类,直接调用即可 。 有三个方法: 分别是set()、get()和remove() 。 以下是自己编写的模拟类,为了更好的理解该类的底层是怎么实现的

     1 /**
     2  * ThreadLocal类的核心代码分析
     3  * @功能  存取删功能
     4  *        一个线程只能存一个数据,所以同一个线程存入多个数据,后一个数据会覆盖掉前一个数据
     5  * @分析   用当前线程存,用当前线程取 ,只能删除当前线程的数据
     6  *           所以当前线程存的东西,其他线程是取不出来的,也删除不掉
     7  *      
     8  * @author ZHAOYUQIANG
     9  *
    10  * @param <T>
    11  */
    12 class ThreadLocal<T>{
    13     /*
    14      * 线程作为key,T作为value
    15      *    T是自己指定的泛型类型
    16      */
    17     private Map<Thread,T> map = new HashMap<Thread,T>();
    18     /**
    19      * 存储数据data
    20      *    使用当前线程做key   
    21      * @param data
    22      */
    23     public void set(T data){
    24         map.put(Thread.currentThread(),data);
    25     }
    26     /**
    27      * 获取数据
    28      *    获取当前线程的数据
    29      * @return
    30      */
    31     public T get(){
    32         return map.get(Thread.currentThread());        
    33     }
    34     /**
    35      * 移除
    36      *    删除当前线程的数据
    37      */
    38     public void remove(){
    39         map.remove(Thread.currentThread());
    40     }
    41 }
    ThreadLocal类 核心功能

    @演示 使用内部类模拟多线程并发争夺临界资源来演示ThreadLocal的作用

     1 /**
     2  * 多线程的测试
     3  * @功能  用内部类模拟多线程竞争临界资源
     4  *         结论是:当前线程存入的值只有当前线程能取出来,其他线程取不出来,
     5  *           ThreadLocal给每个线程分配一个临界资源,解决了并发问题 
     6  *   
     7  * @author ZHAOYUQIANG
     8  *
     9  */
    10 public class TlTestDemo {
    11     @Test
    12     public void fun(){
    13         /*
    14          * 以为a1要被内部类使用,所以加上final关键字
    15          */
    16         final ThreadLocal<Integer> a1 = new ThreadLocal<Integer>();
    17         a1.set(2);
    18         System.out.println(a1.get()); //会输出2 , 当前线程的值
    19         /**
    20          * 内部类
    21          *    尾部加上.start ,
    22          *      意味着程序无需等待内部类中的run()方法执行完即可向下执行 ,
    23          *        实现了块的多线程并发性
    24          */
    25         new Thread(){
    26             public void run(){
    27                 System.out.println("我是内部类"+a1.get()); //输出null,说明此线程不能获取当前线程的数据
    28             }
    29         }.start();       
    30 //        a1.remove();
    31     }
    32 }
    ThreadLocal的测试

     用ThreadLocal类解决JDBC中事务的并发问题放到TxQueryRunner类中解决

    QueryRunner、TxQueryRunner和JdbcUtils(重点、本文核心)

     QueryRunner的思想是: 把java中对数据库的处理代码写到单独的类QueryRunner中,Dao层只提供SQL模版和sql参数,而TxQueryRunner负责继承QueryRunner类辅助事务的处理 。JdbcUtils起到了真正的工具作                                               用,负责连接,事务等 

    @演示  

    @使用 c3p0数据库连接池的使用 中的c3p0-config.xml 

     1 package cn.kmust.jdbcDemo.domain;
     2 /**
     3  * 实体类
     4  *    变量名字最好与数据库中对应字段名一样,便于封装操作
     5  * @author ZHAOYUQIANG
     6  *
     7  */
     8 public class Customer {
     9     private String username ; //用户
    10     private int age ;  //年龄
    11     private double balance ; //资金
    12     public String getUsername() {
    13         return username;
    14     }
    15     public void setUsername(String username) {
    16         this.username = username;
    17     }
    18     public int getAge() {
    19         return age;
    20     }
    21     public void setAge(int age) {
    22         this.age = age;
    23     }
    24     public double getBalance() {
    25         return balance;
    26     }
    27     public void setBalance(double balance) {
    28         this.balance = balance;
    29     }
    30     @Override
    31     public String toString() {
    32         return "Customer [username=" + username + ", age=" + age + ", balance="
    33                 + balance + "]";
    34     }
    35     public Customer(String username, int age, double balance) {
    36         super();
    37         this.username = username;
    38         this.age = age;
    39         this.balance = balance;
    40     }
    41     public Customer() {
    42         super();
    43         // TODO Auto-generated constructor stub
    44     }
    45     
    46     
    47     
    48 
    49 }
    Customer
     1 package cn.kmust.jdbcDemo.service;
     2 
     3 import java.sql.SQLException;
     4 
     5 import cn.kmust.jdbcDemo.dao.CustomerDao;
     6 import cn.kmust.jdbcDemo.domain.Customer;
     7 import cn.kmust.jdbcDemo.utils.JdbcUtils;
     8 
     9 /**
    10  * service 层   处理业务
    11  * @功能  1. 增加新客户
    12  *       2. 查询客户信息(根据客户的username)
    13  *       3. 转账操作  需要事务
    14  * @author ZHAOYUQIANG
    15  *
    16  */
    17 public class CustomerService {
    18     /*
    19      * 依赖CustomerDao
    20      */
    21     CustomerDao cstmDao = new CustomerDao();
    22     /**
    23      * 增加新客户
    24      * @param cstm
    25      * @throws SQLException 
    26      */
    27     public void add(Customer cstm) throws SQLException{
    28         cstmDao.add(cstm);
    29     }
    30     /**
    31      * 查询客户信息(根据用户名字)
    32      * @param name
    33      * @return
    34      * @throws SQLException 
    35      */
    36     public Customer query(String name) throws SQLException{
    37         Customer cstm = cstmDao.query(name);
    38         return cstm ;
    39     }
    40     /**
    41      * 转账
    42      *   A用户转给B用户money钱
    43      * @param nameA
    44      * @param nameB
    45      * @param money
    46      * @throws Exception 
    47      */
    48     public void transfer(String nameA,String nameB,double money) throws Exception{
    49         try{
    50             /*
    51              * 1. 事务1 : 开启事务
    52              * 2. 处理业务
    53              * 3. 事务2: 提交事务
    54              * 4. 事务3 :回滚事务
    55              */
    56             JdbcUtils.beginTransaction();
    57             cstmDao.transfer(nameA,-money);
    58             cstmDao.transfer(nameB,money);
    59             JdbcUtils.commitTransaction();
    60         }catch(Exception e){
    61             try{
    62                 JdbcUtils.rollbackTransaction();
    63             }catch(SQLException e1){        
    64             }
    65             throw e;
    66         }        
    67     }
    68 }
    CustomerService
     1 package cn.kmust.jdbcDemo.dao;
     2 
     3 import java.sql.ResultSet;
     4 import java.sql.SQLException;
     5 
     6 import cn.kmust.jdbcDemo.domain.Customer;
     7 import cn.kmust.jdbcDemo.utils.QueryRunner;
     8 import cn.kmust.jdbcDemo.utils.ResultSetHandler;
     9 import cn.kmust.jdbcDemo.utils.TxQueryRunner;
    10 
    11 /**
    12  *dao层  
    13  *    对数据库的操作 
    14  * @author ZHAOYUQIANG
    15  *
    16  */
    17 public class CustomerDao {
    18     /**
    19      * 添加新客户
    20      * @param cstm
    21      * @throws SQLException 
    22      */
    23     public void add(Customer cstm) throws SQLException {
    24         /*
    25          * 1. 创建TxQueryRunner对象 
    26          * 2. 准备SQL模版     
    27          * 3. 将参数存入参数数组 
    28          * 4. 调用TxQueryRunner类中update方法  进行插入操作
    29          */
    30         QueryRunner qr =  new TxQueryRunner();
    31         String sql ="insert into t_customer values(?,?,?)";
    32         Object[] params = {cstm.getUsername(),
    33                            cstm.getAge(),
    34                            cstm.getBalance()};
    35         qr.update(sql, params);                    
    36     }
    37     /**
    38      * 转账 
    39      * @param nameA
    40      * @param d
    41      * @throws SQLException 
    42      */
    43     public void transfer(String name, double money) throws SQLException {
    44         /*
    45          * 1. 创建TxQueryRunner对象 
    46          * 2. 准备SQL模版     
    47          * 3. 将参数存入参数数组 
    48          * 4. 调用TxQueryRunner类中update方法  进行更改操作
    49          */
    50         QueryRunner qr = new TxQueryRunner();
    51         String sql = "update t_customer set balance=balance+? where username=?";
    52         Object[] params = {money,name};
    53         qr.update(sql,params);        
    54     }
    55     /**
    56      * 查询
    57      * @param name
    58      * @return
    59      * @throws SQLException 
    60      */
    61     public Customer query(String name) throws SQLException {
    62         /*
    63          * 1. 创建TxQueryRunner对象 
    64          * 2. 准备SQL模版     
    65          * 3. 将参数存入参数数组 
    66          * 4. 编写ResultSetHandler接口的实现,把查询结果封装到Customer对象中
    67          * 5. 调用TxQueryRunner类中query方法  进行查询操作,并且返回查询出的对象
    68          */
    69         QueryRunner qr = new TxQueryRunner();
    70         String sql ="select * from t_customer where username=?";
    71         Object[] params = {name} ;
    72         /**
    73          * 内部类  _结果集处理器的实现类              
    74          *  @功能  实现ResultSetHandler接口
    75          *          把查询到的结果集封装到实体类对象中,并且返回这个实体类,供给其他类使用
    76          */
    77         ResultSetHandler<Customer> rsh = new ResultSetHandler<Customer>() {            
    78             public Customer handle(ResultSet rs) throws SQLException {
    79                 //查询不到,返回null
    80                 if(!rs.next())
    81                     return null ;
    82                 Customer cstm = new Customer();
    83                 /*
    84                  * 把查询到的内容封装到Customer对象中,并且返回
    85                  */
    86                  cstm.setUsername(rs.getString("username"));
    87                  cstm.setAge(rs.getInt("age"));
    88                  cstm.setBalance(rs.getDouble("balance"));
    89                 return cstm;
    90             }
    91         };                
    92         return (Customer)qr.query(sql,rsh,params);
    93     }   
    94 }
    CustomerDao
      1 package cn.kmust.jdbcDemo.utils;
      2 
      3 import java.sql.Connection;
      4 import java.sql.SQLException;
      5 
      6 import javax.sql.DataSource;
      7 
      8 import com.mchange.v2.c3p0.ComboPooledDataSource;
      9 
     10 /**
     11  * JdbcUtisl工具类
     12  * @功能  1. 返回数据库连接对象 和 数据库连接池对象
     13  *       2. 事务的开启、提交、关闭
     14  *       3. 释放数据库连接对象
     15  * 
     16  * @author ZHAOYUQIANG
     17  *
     18  */
     19 public class JdbcUtils {
     20     /*
     21      * 创建连接池对象
     22      *    创建时自动加载c3p0-config.xml配置文件
     23      */
     24     private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
     25     /*
     26      * 提供一个事务专用的连接
     27      *    事务专用连接 :  service层开启事务的时候创建连接,
     28      *         在该事务结束(提交和回滚)之前的所有操作中都使用这同一个连接对象
     29      *    防止多个事务产生并发问题,用ThreadLocal加以控制
     30      */
     31     private static ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
     32     /**
     33      * 使用连接池返回一个数据库连接对象
     34      * @return
     35      * @throws SQLException
     36      */
     37     public static Connection getConnection() throws SQLException{
     38         /*
     39          * 1. 取出当前线程的连接
     40          * 2. 当con != null时,说明已经调用过本类的beginTransaction()方法了,
     41          *        正是该方法给con赋的值,  表示开启了事务,所以把这个事务专用con返回,保证是同一个连接
     42          * 3. 如果con = null ,说明没有调用过本类的beginTransaction()方法,
     43          *        说明并没有开启事务,接下来的操作也不需要使用同一个连接对象,所以返回一个新的非事务专用的连接对象
     44          */
     45         Connection con = t1.get();
     46         if(con != null)
     47             return con ;
     48         return dataSource.getConnection();
     49     }
     50     /**
     51      * 返回池对象
     52      * @return
     53      */
     54     public static DataSource getDataSource(){
     55         return dataSource;
     56     }
     57     /**
     58      * 事务处理一: 开启事务
     59      *   获取一个新的Connection对象,设置setAutoCommit(false)开启事务
     60      *     这个Connection对象将会一直贯穿被该事务所包裹的所有操作,直到提交或者回滚才会关闭
     61      * @throws SQLException 
     62      */
     63     public static void beginTransaction() throws SQLException{
     64         /*
     65          * 1. 取出当前线程的连接
     66          * 2. 防止重复开启
     67          * 3. 给con赋值,并且设con为手动提交,
     68          *       这个con对象是事务专用的连接对象,以后进行的事务提交、回滚和在该事务中的操作所用的连接
     69          *            全是这同一个连接
     70          * 4. 把当前线程的连接保存起来,供给下面的提交和回滚以及开启事务的多个类使用
     71          *        这样就能保证同一个事务中的多个操作用的连接对象都是这一个对象,是同一个
     72          */
     73         Connection con = t1.get();
     74         if(con != null )
     75             throw new SQLException("已经开启事务,就不要重复开启!");
     76         con = getConnection();
     77         con.setAutoCommit(false);
     78         t1.set(con);
     79     }
     80     /**
     81      * 事务处理二:提交事务
     82      *   获取beginTransaction提供的Connection ,调用commit
     83      * @throws SQLException 
     84      */
     85     public static void commitTransaction() throws SQLException{
     86         /*
     87          * 1. 获取当前线程的连接
     88          * 2. 防止还没有开启事务就调用提交事务的方法,判断的依据是如果开启事务,con一定有值
     89          * 3. 提交事务
     90          * 4. 提交事务后整个事务结束,贯穿整个事务操作的连接 走到了最后,所以需要 关闭连接
     91          * 5. 但是con.close后,con中还是有值,防止其他类开启了事务后得到这个连接对象吗,
     92          *       需要把多线程中的值移除
     93          */
     94         Connection con = t1.get();
     95         if(con == null )
     96             throw new SQLException("还没开启事务,不能提交!");        
     97         con.commit();
     98         con.close();
     99         t1.remove() ;
    100     }
    101     /**
    102      * 事务处理三:回滚事务
    103      *    获取beginTransaction提供的Connection ,调用rollback
    104      * @throws SQLException 
    105      */
    106     public static void rollbackTransaction() throws SQLException{
    107         /*
    108          * 1. 获取当前线程的连接
    109          * 2. 防止还没有开启事务就调用回滚事务的方法
    110          * 3. 回滚事务
    111          * 4. 提交事务后整个事务结束,贯穿整个事务操作的连接 走到了最后,所以需要 关闭连接
    112          * 5. 但是con.close后,con中还是有值,防止其他类开启了事务后得到这个连接对象吗,
    113          *       需要把多线程中的值移除
    114          */
    115         Connection con = t1.get();
    116         if(con == null )
    117             throw new SQLException("还没开启事务,不能回滚!");
    118         con.rollback();
    119         con.close();
    120         t1.remove() ;        
    121     }
    122     /**
    123      * 释放连接
    124      *  所有Connection对象连接的关闭都要调用该方法
    125      *  如果是事务专用的连接: 则不能关闭,被事务包裹的所有操作都得用同一个连接,所以其他操作还要用
    126      *  如果不是事务专用,则需要关闭
    127      *      
    128      * @param connection
    129      * @throws SQLException 
    130      */
    131     public static void releaseConnection(Connection connection) throws SQLException{
    132         /*
    133          * 1. 获取当前线程的连接
    134          * 2. 本类的con==null 说明一定不是事务专用的,可以直接关闭,并不需要保存起来给别的操作用
    135          * 3. 如果con!=null,说明有事务,但是不一定是同一个连接
    136          *       所以判断,
    137          *          1. 如果本类的连接和传递过来的连接是同一个连接,说明是同一个事务的连接,这个连接还不能关闭,
    138          *               因为同事务中的其他操作还要用,事务的提交和回滚也需要用
    139          *          2. 如果不是同一个连接,说明不是事务专用连接,也得关闭 
    140          */
    141         Connection con = t1.get();
    142         if(con == null )
    143             connection.close();
    144         if(con != connection)
    145             connection.close();
    146     }
    147 }
    JdbcUtils_工具类
      1 package cn.kmust.jdbcDemo.utils;
      2 
      3 import java.sql.Connection;
      4 import java.sql.PreparedStatement;
      5 import java.sql.ResultSet;
      6 import java.sql.SQLException;
      7 
      8 import javax.sql.DataSource;
      9 
     10 
     11 
     12 /**
     13  * QueryRunner工具类  
     14  *    对数据库操作的辅助类
     15  * @功能   1.插入、删除、编辑
     16  *            SQL模版所需要的参数来自Dao层,获取的参数通过initParams()给SQL中的参数赋值
     17  *        2. 查询
     18  *             SQL模版所需要的参数来自Dao层,获取的参数通过initParams()给SQL中的参数赋值
     19  *             查询出的结果集封装成JavaBean返回,封装的方法是在Dao层中实现本类的 
     20  *                  ResultSetHandler<T>接口
     21  * @注意 方法都有一个没有连接对象参数的,一个有的。
     22  *            没有的方法是为了Dao层中调用不用自己添加con参数,而是通过TxQueryRunner类来添加,
     23  *                   采用 TxQueryRunner类的目的也是为了同一事务中的操作必须得用相同的连接对象                
     24  * @author ZHAOYUQIANG
     25  *
     26  */
     27 public class QueryRunner<T> {
     28     /*
     29      * 创建DataSource类型的变量 
     30      *    该与数据库连接池的池对象有关
     31      */
     32     private DataSource dataSource ;
     33     /**
     34      * 提供有参和无参的构造器
     35      *    目的是获取外界传递过来的池,赋给本类的dataSource。
     36      */
     37 
     38     public QueryRunner(DataSource dataSource) {
     39         this.dataSource = dataSource;
     40     }
     41     public QueryRunner() {
     42         super();
     43     }
     44     /**
     45      * 插入 、更改、删除   
     46      *   为了Dao层调用时不用加con参数
     47      * @param sql
     48      * @param params
     49      * @return
     50      * @throws SQLException 
     51      */
     52     public int update(String sql ,Object...params) throws SQLException{
     53         /*
     54          * 在TxQueryRunner类中复写该方法
     55          */
     56         return 0 ;
     57     }
     58     /**
     59      * 插入 、更改、删除   
     60      *    params是一个Object类型的数组
     61      * @param sql
     62      * @param params
     63      * @return
     64      * @throws SQLException 
     65      */
     66     public int update(Connection conn,String sql ,Object...params) throws SQLException{
     67         Connection con = null ;
     68         PreparedStatement pstmt = null ;    
     69         try{
     70             /*
     71              * 1. 把传递过来的连接对象赋给con,保证是同一个连接对象             
     72              * 2. 用sql来创建pstmt对象 
     73              * 3. 调用initParams给sql中的参数赋值 
     74              * 4. 执行并且返回影响的数据库表记录的行数   
     75              */
     76             con = conn;    
     77             pstmt = con.prepareStatement(sql);
     78             initParams(pstmt,params);
     79             return pstmt.executeUpdate();
     80         }catch(Exception e){
     81             throw new RuntimeException(e);
     82         }        
     83     }
     84     
     85     /**
     86      * 查询
     87      *       为了Dao层调用时不用加con参数
     88      * @param sql
     89      * @return
     90      * @throws SQLException 
     91      */
     92     public T query(String sql,ResultSetHandler<T> rsh,Object...params) throws SQLException{
     93         /*
     94          * 在TxQueryRunner类中复写该方法
     95          */
     96         return null ;
     97     }
     98     /**
     99      * 查询
    100      *    通过sql和params得到一个结果集,然后把结果集给RsHandler对象的handle()方法处理。
    101      *    handle()方法的作用就是把结果集(rs)变成一个对象
    102      *       
    103      * @param sql
    104      * @return
    105      */
    106     public T query(Connection conn,String sql,ResultSetHandler<T> rsh,Object...params){
    107         Connection con = null ;
    108         PreparedStatement pstmt = null ;
    109         ResultSet rs = null ;
    110         try{
    111             /*
    112              * 1. 把传递过来的连接对象赋给con,保证是同一个连接对象
    113              * 2. 用sql来创建pstmt对象     
    114              * 3. 调用initParams给sql中的参数赋值
    115              * 4. 执行并返回结果集的行指针
    116              * 5. 调用接口的handle()方法,该结果集处理器会把rs变成一个javaBean对象
    117              *       并且返回      
    118              */
    119             con = conn;    
    120             pstmt = con.prepareStatement(sql);
    121             initParams(pstmt,params);
    122             rs = pstmt.executeQuery();
    123             return rsh.handle(rs);
    124         }catch(Exception e){
    125             throw new RuntimeException(e);
    126         }
    127     }
    128         
    129     /**
    130      * 用pstmt对象给sql中的参数赋值
    131      *      params中存放着外界传递过来的参数
    132      * @param pstmt
    133      * @param params
    134      * @throws SQLException 
    135      */
    136     public void initParams(PreparedStatement pstmt,Object...params) throws SQLException{
    137         for(int i=0;i<params.length;i++){
    138             pstmt.setObject(i+1, params[i]);
    139         }
    140     }        
    141 }
    QueryRunner_工具类
     1 package cn.kmust.jdbcDemo.utils;
     2 
     3 import java.sql.Connection;
     4 import java.sql.SQLException;
     5 
     6 import cn.kmust.jdbcDemo.domain.Customer;
     7 
     8 /**
     9  * TxQueryRunner工具类
    10  *     继承QueryRunner,完善QueryRunner
    11  * @功能  1. 重写父类中不带Connection参数的方法
    12  *       2. 添加获取数据库连接对象  和 关闭连接 的功能
    13  * @author ZHAOYUQIANG
    14  *
    15  */
    16 public class TxQueryRunner extends QueryRunner {
    17 
    18     /**
    19      * 重写父类的update方法
    20      *   添加获取数据库连接对象  和 关闭连接 的功能
    21      * @throws SQLException 
    22      * 
    23      */
    24     public int update(String sql, Object... params) throws SQLException {
    25         /*
    26          * 1. 通过JdbcUtils直接得到连接
    27          *      如果已经开启了事务,获得的这个连接就是事务开启的时创建的那个连接
    28          *      如果没有开启事务,获得的这个连接就是新创建的连接
    29          * 2. 执行父类方法,传递该连接对象,
    30          *        因为同一个事务的多个操作必须使用相同的连接对象
    31          * 3. 调用JdbcUtils的releaseConnection()释放连接
    32          *        如果是事务专用连接,不会关闭
    33          *        如果不是事务专用,则会关闭
    34          * 4. 返回值
    35          */
    36         Connection con = JdbcUtils.getConnection();
    37         int result = super.update(con,sql,params);        
    38         JdbcUtils.releaseConnection(con);
    39         return result;
    40     }
    41 
    42     /**
    43      * 重写父类的query方法
    44      *   添加获取数据库连接对象  和 关闭连接 的功能
    45      * @throws SQLException 
    46      */
    47     public Object query(String sql, ResultSetHandler rsh, Object... params) throws SQLException {
    48         /*
    49          * 1. 通过JdbcUtils直接得到连接
    50          *      如果已经开启了事务,获得的这个连接就是事务开启的时创建的那个连接
    51          *      如果没有开启事务,获得的这个连接就是新创建的连接
    52          * 2. 执行父类方法,传递该连接对象,
    53          *        因为同一个事务的多个操作必须使用相同的连接对象
    54          * 3. 调用JdbcUtils的releaseConnection()释放连接
    55          *        如果是事务专用连接,不会关闭
    56          *        如果不是事务专用,则会关闭
    57          * 4. 返回值
    58          */
    59         Connection con = JdbcUtils.getConnection();        
    60         Customer result = (Customer)super.query(con,sql,rsh, params);
    61         JdbcUtils.releaseConnection(con);
    62         return result;
    63     }
    64 }
    TxQueryRunner_工具类
     1 package cn.kmust.jdbcDemo.utils;
     2 
     3 import java.sql.ResultSet;
     4 import java.sql.SQLException;
     5 
     6 /**
     7  * 接口——结果集处理器  [查询的辅助方法]    
     8  * @功能   用来把结果集转换成需要的对象类型,即把查询到的结果封装到实体类对象中
     9  *          由rs---->javaBean的转换
    10  *   接口的实现原则是 : 谁调用谁提供handle方法的实现
    11  * @author ZHAOYUQIANG
    12  * @author ZHAOYUQIANG
    13  *
    14  * @param <T>
    15  */
    16 public interface ResultSetHandler<T> {
    17     public T handle(ResultSet rs)throws SQLException;
    18 
    19 }
    ResultSetHandler_工具类

    @download 项目下载http://files.cnblogs.com/files/zyuqiang/jdbcStudy_finalSummary.rar

    封装后的标准开发-------common-dbutils.jar和itcast-tools.jar(重点、本文核心)

    commons-dbutils 是 Apache 组织提供的一个开源 JDBC 工具类库,上述自己写的QueryRunner工具类也是我对此官方的工具类库做了一个简单的核心功能的抽出,当然官方的工具类库功能更加丰富 。 从此以后,我们开发不需要自己编写工具类,而是导入common-dbutils.jar 来直接使用即可 . @注意 官网提供的 工具类库 不包含TxQueryRunner和JdbcUtils工具,这两个工具依然还需要我们自己动手实现,不过也有非官方提供的封装包,如传智的itcast-tools.jar 。  可以拿来直接使用

    @结果集处理器 common-dbutils包中提供了各种各样的结果集处理器,可以处理查询到的结果集,如封装成实体类对象等。有如下的五种结果集处理器

    BeanHandler 结果集为单行 构造器需要一个Class类型的参数,用来把查询到的一行结果集转换成指定类型的javaBean对象 常用 
    BeanListHandler 结果集为多行 构造器也是需要一个Class类型的参数,用来把查询到的多行记录一个记录转成一个javaBean对象,多个JavaBean构成转List对象 常用
    MapHandler 结果集为单行 把一行结果集转换Map对象 不常用
    MapListHandler 结果集为多行 把一行记录转换成一个Map,多行就是多个Map 不常用
    ScalarHandler 结果集是单行单列 它返回一个Object,多用在求一共有多少个记录上 常用

    @演示 导入common-dbutils.jar 和 itcast-tools.jar(jar包在项目中,可以下载该项目找到),仍然@使用 c3p0数据库连接池的使用 中的c3p0-config.xml 

     1 package cn.kmust.jdbc.commons.domain;
     2 /**
     3  * 实体类
     4  *    变量名字最好与数据库中对应字段名一样,便于封装操作
     5  * @author ZHAOYUQIANG
     6  *
     7  */
     8 public class Customer {
     9     private String username ; //用户
    10     private int age ;  //年龄
    11     private double balance ; //资金
    12     public String getUsername() {
    13         return username;
    14     }
    15     public void setUsername(String username) {
    16         this.username = username;
    17     }
    18     public int getAge() {
    19         return age;
    20     }
    21     public void setAge(int age) {
    22         this.age = age;
    23     }
    24     public double getBalance() {
    25         return balance;
    26     }
    27     public void setBalance(double balance) {
    28         this.balance = balance;
    29     }
    30     @Override
    31     public String toString() {
    32         return "Customer [username=" + username + ", age=" + age + ", balance="
    33                 + balance + "]";
    34     }
    35     public Customer(String username, int age, double balance) {
    36         super();
    37         this.username = username;
    38         this.age = age;
    39         this.balance = balance;
    40     }
    41     public Customer() {
    42         super();
    43         // TODO Auto-generated constructor stub
    44     }
    45     
    46     
    47     
    48 
    49 }
    Customer
     1 package cn.kmust.jdbc.commons.service;
     2 
     3 import java.sql.SQLException;
     4 import java.util.List;
     5 
     6 import cn.itcast.jdbc.JdbcUtils;
     7 import cn.kmust.jdbc.commons.dao.CustomerDao;
     8 import cn.kmust.jdbc.commons.domain.Customer;
     9 
    10 
    11 /**
    12  * service 层   处理业务
    13  * @功能  1. 增加新客户
    14  *       2. 转账操作  需要事务
    15  *       3. 查询  BeanHandler(根据客户的username)
    16  *       4. 查询  BeanListHandler 
    17  *       7. 查询  ScalarHandler
    18  *  关于Map的结果集处理器不常用,就不再演示
    19  * @author ZHAOYUQIANG
    20  *
    21  */
    22 public class CustomerService {
    23     /*
    24      * 依赖CustomerDao
    25      */
    26     CustomerDao cstmDao = new CustomerDao();
    27     /**
    28      * 增加新客户
    29      * @param cstm
    30      * @throws SQLException 
    31      */
    32     public void add(Customer cstm) throws SQLException{
    33         cstmDao.add(cstm);
    34     }
    35 
    36     /**
    37      * 转账
    38      *   A用户转给B用户money钱
    39      * @param nameA
    40      * @param nameB
    41      * @param money
    42      * @throws Exception 
    43      */
    44     public void transfer(String nameA,String nameB,double money) throws Exception{
    45         try{
    46             /*
    47              * 1. 事务1 : 开启事务
    48              * 2. 处理业务
    49              * 3. 事务2: 提交事务
    50              * 4. 事务3 :回滚事务
    51              */
    52             JdbcUtils.beginTransaction();
    53             cstmDao.transfer(nameA,-money);
    54             cstmDao.transfer(nameB,money);
    55             JdbcUtils.commitTransaction();
    56         }catch(Exception e){
    57             try{
    58                 JdbcUtils.rollbackTransaction();
    59             }catch(SQLException e1){        
    60             }
    61             throw e;
    62         }        
    63     }
    64     /**
    65      * 查询  BeanHandler(根据客户的username)
    66      * @param name
    67      * @return
    68      * @throws SQLException 
    69      */
    70     public Customer query(String name) throws SQLException{
    71         Customer cstm = cstmDao.query01(name);
    72         return cstm ;
    73         
    74     }
    75     /**
    76      * 查询   BeanListHandler 
    77      * @return
    78      * @throws SQLException
    79      */
    80     public List<Customer> queryAll() throws SQLException{
    81         List<Customer> cstmList= cstmDao.queryAll02();
    82         return cstmList;
    83     } 
    84     /**
    85      * 查询  ScalarHandler
    86      * @return
    87      * @throws SQLException
    88      */
    89     public Number queryAllNum() throws SQLException{
    90         Number cnt= cstmDao.queryAllNum();
    91         return cnt ;
    92     }
    93     
    94 }
    CustomerService
      1 package cn.kmust.jdbc.commons.dao;
      2 
      3 import java.sql.ResultSet;
      4 import java.sql.SQLException;
      5 import java.util.List;
      6 import java.util.Map;
      7 
      8 import org.apache.commons.dbutils.QueryRunner;
      9 import org.apache.commons.dbutils.ResultSetHandler;
     10 import org.apache.commons.dbutils.handlers.BeanHandler;
     11 import org.apache.commons.dbutils.handlers.BeanListHandler;
     12 import org.apache.commons.dbutils.handlers.MapHandler;
     13 import org.apache.commons.dbutils.handlers.MapListHandler;
     14 import org.apache.commons.dbutils.handlers.ScalarHandler;
     15 
     16 import cn.itcast.jdbc.TxQueryRunner;
     17 import cn.kmust.jdbc.commons.domain.Customer;
     18 
     19 
     20 /**
     21  *dao层  
     22  *    对数据库的操作 
     23  * @author ZHAOYUQIANG
     24  *
     25  */
     26 public class CustomerDao {
     27     /*
     28      * QueryRunner类是已经写好的类,里面有对数据库的操作方法,如query、update、delete等
     29      *    TxQueryRunner继承了QueryRunner类,添加了Dao层对con的关闭
     30      */
     31     private QueryRunner qr = new TxQueryRunner();
     32     /**
     33      * 添加新客户
     34      * @param cstm
     35      * @throws SQLException 
     36      */
     37     public void add(Customer cstm) throws SQLException {
     38         /*
     39          * 1. 创建TxQueryRunner对象 
     40          * 2. 准备SQL模版     
     41          * 3. 将参数存入参数数组 
     42          * 4. 调用TxQueryRunner类中update方法  进行插入操作
     43          */
     44         
     45         String sql ="insert into t_customer values(?,?,?)";
     46         Object[] params = {cstm.getUsername(),
     47                            cstm.getAge(),
     48                            cstm.getBalance()};
     49         qr.update(sql, params);                    
     50     }
     51     /**
     52      * 转账 
     53      * @param nameA
     54      * @param d
     55      * @throws SQLException 
     56      */
     57     public void transfer(String name, double money) throws SQLException {
     58         /*
     59          * 1. 创建TxQueryRunner对象 
     60          * 2. 准备SQL模版     
     61          * 3. 将参数存入参数数组 
     62          * 4. 调用TxQueryRunner类中update方法  进行更改操作
     63          */
     64         String sql = "update t_customer set balance=balance+? where username=?";
     65         Object[] params = {money,name};
     66         qr.update(sql,params);        
     67     }
     68     /**
     69      * 查询  (按照username查询) 
     70      * 结果集处理器1.  BeanHandler演示  查询结果是一个记录 
     71      *      结果集处理器把返回的查询结果封装成JavaBean
     72      * @param name
     73      * @return  
     74      * @throws SQLException 
     75      */
     76     public Customer query01(String name) throws SQLException {
     77         /*
     78          * 1. 创建TxQueryRunner对象 
     79          * 2. 准备SQL模版     
     80          * 3. 将参数存入参数数组 
     81          * 4. 调用query(sql,BeanHandler,params),并且把查询结果封装到Customer对象中
     82          * 5. 返回对象
     83          */
     84         String sql ="select * from t_customer where username=?";
     85         Object[] params = {name} ;
     86         Customer cstm =qr.query(sql,
     87                  new BeanHandler<Customer>(Customer.class),params);
     88         return cstm ;
     89     }   
     90     /**
     91      * 查询  (查询所有用户,查询结果返回的是多个记录)
     92      *  结果集处理器2.  BeanListHandler演示  
     93      *     使用封装好的结果集处理器把返回的结果一条封装成一个Customer对象,多个对象构成List集合
     94      * @param name
     95      * @return 
     96      * @throws SQLException 
     97      */
     98     public List<Customer> queryAll02() throws SQLException {
     99         /*
    100          * 1. 创建TxQueryRunner对象 
    101          * 2. 准备SQL模版     
    102          * 3. 将参数存入参数数组 
    103          * 4. 调用query(sql,BeanListHandler),
    104          *        并且把查询结果一条记录封装到一个对象中,多个对象构成一个List集合
    105          * 5. 返回对象集合
    106          */
    107         String sql ="select * from t_customer ";        
    108         List<Customer> cstmList =  qr.query(sql,
    109                  new BeanListHandler<Customer>(Customer.class));
    110         return cstmList ;
    111     } 
    112     /**
    113      * 查询  按照username查询 (查询结果是一个记录)
    114      *  结果集处理器3.   MapHandler演示  
    115      *     使用封装好的结果集处理器把返回的一条记录封装成一个Map
    116      * @param name
    117      * @return
    118      * @throws SQLException 
    119      */
    120     public Map query03(String name) throws SQLException {
    121         /*
    122          * 1. 创建TxQueryRunner对象 
    123          * 2. 准备SQL模版     
    124          * 3. 将参数存入参数数组 
    125          * 4. 调用query(sql,Map,params),并且把查询结果封装到一个map中
    126          * 5. 返回Map对象
    127          */
    128         String sql ="select * from t_customer where username=?";
    129         Object[] params = {name} ;
    130         Map cstmMap =qr.query(sql,
    131                 new MapHandler(),params);
    132         return cstmMap ;
    133     }
    134     /**
    135      * 查询   所有记录 (查询结果返回的是多个记录)
    136      * 结果集处理器4.   MapListHandler演示  
    137      *   使用封装好的结果集处理器把返回的结果一条封装成一个Map,多个Map构成List集合
    138      * @param name
    139      * @return
    140      * @throws SQLException 
    141      */
    142     public List<Map<String,Object>> queryAll04() throws SQLException {
    143         /*
    144          * 1. 创建TxQueryRunner对象 
    145          * 2. 准备SQL模版     
    146          * 3. 将参数存入参数数组 
    147          * 4. 调用query(sql,MapListHandler),并且把查询结果一条封装到一个map,多个map构成一个List集合
    148          * 5. 返回MapList对象
    149          */
    150         String sql ="select * from t_customer";
    151         List<Map<String,Object>> cstmMapList =qr.query(sql,
    152                 new MapListHandler());
    153         return cstmMapList ;
    154     }
    155     /**
    156      * 查询 所有记录的行数    (返回的是一个数)
    157      * 
    158      * 结果集处理器5.   ScalarHandler演示  
    159      *    
    160      * @param name
    161      * @return
    162      * @throws SQLException 
    163      */
    164     public Number queryAllNum() throws SQLException {
    165         /*
    166          * 1. 创建TxQueryRunner对象 
    167          * 2. 准备SQL模版     
    168          * 3. 将参数存入参数数组 
    169          * 4. 调用query(sql,ScalarHandler)
    170          *       查询的结果先放在Number中,转成int
    171          * 5. 返回Number对象
    172          */
    173         String sql ="select count(*) from t_customer";
    174         Number cnt =(Number)qr.query(sql,
    175                 new ScalarHandler());
    176         int c = cnt.intValue();
    177 //        long c= cnt.longValue();
    178         return c;
    179     }
    180 }
    CustomerDao
     1 package cn.kmust.jdbc.commons.Test;
     2 
     3 import java.sql.SQLException;
     4 import java.util.List;
     5 
     6 import org.junit.Test;
     7 
     8 import cn.kmust.jdbc.commons.domain.Customer;
     9 import cn.kmust.jdbc.commons.service.CustomerService;
    10 
    11 /**
    12  * 测试类
    13  * @功能 1. 测试添加
    14  *      2. 测试查找(条件查找、全部查找、查找所有记录行数)
    15  *      3. 测试转账(事务)
    16  * @author ZHAOYUQIANG
    17  *
    18  */
    19 public class TestDemo {
    20     /*
    21      * 依赖Service
    22      */
    23     CustomerService cstmService = new CustomerService();
    24     /**
    25      * 测试   添加新客户
    26      * @throws SQLException
    27      */
    28     @Test
    29     public void test1() throws SQLException{
    30         Customer cstm1 = new Customer("王五",18,1000.000);
    31         Customer cstm2 = new Customer("赵六",98,1000.000);
    32         cstmService.add(cstm1);
    33         cstmService.add(cstm2);
    34     }
    35     /**
    36      * 测试  转账
    37      * @throws Exception
    38      */
    39     @Test
    40     public void test2() throws Exception{
    41         cstmService.transfer("张三", "李四", 100.00);
    42     }
    43     /**
    44      * 测试 条件查询
    45      * @throws SQLException
    46      */
    47     @Test
    48     public void test3() throws SQLException{
    49         Customer cstm = cstmService.query("张三");
    50         System.out.println(cstm.toString());
    51 
    52     }
    53     /**
    54      * 测试 全部查询
    55      * @throws SQLException
    56      */
    57     @Test
    58     public void test4() throws SQLException{
    59         List<Customer> cstmList = cstmService.queryAll();
    60         for(int i =0 ;i<cstmList.size();i++){
    61             System.out.println(cstmList.get(i).toString());
    62         }
    63 
    64     }
    65     /**
    66      * 测试 查询记录数
    67      * @throws SQLException
    68      */
    69     @Test
    70     public void test5() throws SQLException{
    71         Number num = cstmService.queryAllNum();
    72         System.out.println(num);
    73 
    74     }
    75 
    76 }
    TestDemo

    也可以批量添加数据

     1 private QueryRunner qr = new TxQueryRunner();
     2     /**
     3      * 添加新客户
     4      *   批处理
     5      * @param cstm,num是批量插入记录的行数
     6      * @throws SQLException 
     7      */
     8     public void add(Customer cstm , int num) throws SQLException {
     9         /*
    10          * 1. 创建TxQueryRunner对象 
    11          * 2. 准备SQL模版     
    12          * 3. 将参数存入参数数组 
    13          * 4. 调用TxQueryRunner类中batch方法  进行插入操作
    14          */
    15         String sql ="insert into t_customer values(?,?,?)";
    16         Object[][] params = new Object[num][];
    17         for(int i=0;i<params.length;i++){
    18             params[i]=new Object[]{cstm.getUsername()+i,
    19                                    cstm.getAge()+i,
    20                                    cstm.getBalance()};
    21         }
    22 //        Object[] params = {cstm.getUsername(),
    23 //                           cstm.getAge(),
    24 //                           cstm.getBalance()};
    25         qr.batch(sql, params);                    
    26     }

    @download 项目下载http://files.cnblogs.com/files/zyuqiang/jdbcStudy_finalSummary_Commons.rar


  • 相关阅读:
    原生js实现 table表格列宽拖拽
    vue.js 利用SocketCluster实现动态添加数据及排序
    angular-websocket.js 使用
    判断当前页面是否是激活状态。
    remove ---会报错discard不会报错
    .pop ----remove 删除
    add添加
    #将相同值输出,取一个值
    集合
    __delattr__\__delitem__
  • 原文地址:https://www.cnblogs.com/zyuqiang/p/7218083.html
Copyright © 2011-2022 走看看