大数据技术 JDBC&&c3p0、事务、批处理、多线程于一体的经典秘方QueryRunner
沉沙 2018-09-27 来源 : 阅读 1214 评论 0

摘要:本篇教程探讨了大数据技术 JDBC&&c3p0、事务、批处理、多线程于一体的经典秘方QueryRunner,希望阅读本篇文章以后大家有所收获,帮助大家对大数据技术的理解更加深入。

本篇教程探讨了大数据技术 JDBC&&c3p0、事务、批处理、多线程于一体的经典秘方QueryRunner,希望阅读本篇文章以后大家有所收获,帮助大家对大数据技术的理解更加深入。

<

数据库使用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 
 2 
 3     
 4         
 5         jdbc:mysql://localhost:3306/jdbc_test01
 6         com.mysql.jdbc.Driver
 7         root
 8         123456
 9         
10         3
11         10
12         2
13         10
14     

15     
16     
17     
18     
19           oracle的url   
20           oracle的驱动   
21             oracle的用户 
22             密码
23         3
24         10
25         2
26         10
27     

28     
29 


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对于插入数据大小的限制。具体操作 : 在MySQL\MySQL Server 5.5\my.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 
11  */
12 class ThreadLocal{
13     /*
14      * 线程作为key,T作为value
15      *    T是自己指定的泛型类型
16      */
17     private Map map = new HashMap();
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 a1 = new ThreadLocal();
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 rsh = new ResultSetHandler() {            
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 t1 = new ThreadLocal();
 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          */
   

本文由职坐标整理发布,学习更多的大数据技术相关知识,请关注职坐标大技术云计算大技术技术频道!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程