《JavaWeb从入门到改行》JDBC经典秘方QueryRunner
目录:
基础篇_功能各自回顾
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 );
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 );
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 ) ;
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 );
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 }
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 }
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>
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 }
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 }
大数据的插入(使用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 }
@可能出现错误 如果出现以下错误提示,请打开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();
数据库中的事务(使用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 }
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 }
@事务并发的缺陷 多个事务并发执行时,因为同时调用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的作用
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类解决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 }
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 }
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 }
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 }
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 }
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 }
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 }
@download 项目下载https://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 }
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 }
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 }
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 }
也可以批量添加数据
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 项目下载https://files.cnblogs.com/files/zyuqiang/jdbcStudy_finalSummary_Commons.rar