前面九节JDBC的学习,知识点已经很丰富了,现在将这些应用起来,完善JDBC工具类。
之前几节有对JDBCUtils工具类介绍:
1.0版:JDBC学习(八)数据库连接池
2.0版:JDBC学习(九)dbUtils原理
这次是3.0版
首先导jar包:
c3p0-0.9.2-pre1.jar
mchange-commons-0.2.jar
mysql-connector-java-5.1.46.jar
commons-dbutils-1.4.jar
c3p0配置文件(src目录下)
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 这是默认配置信息 -->
<default-config>
<!-- 连接四大参数配置 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/sql_test</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 池参数配置 -->
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</default-config>
</c3p0-config>
这里以转账案例来展开:
在2.0的基础上,我们需要实现,在业务层(service)不能出现Connection连接对象:
- jdbcUtils.java中添加事务操作
- Dao层使用事务时,保证是jdbcUtils里面创建的。
JDBCUtils.java
/**
* 使用c3p0连接池
*/
public class JDBCUtils {
//创建一个连接池对象,这里在配置文件中设置参数 c3p0-config.xml
private static ComboPooledDataSource cpds=new ComboPooledDataSource();
//事务专用连接
private static Connection cn=null;
/**
* 返回连接对象
* @return
* @throws SQLException
*/
public static Connection getConnction() throws SQLException {
//如果cn不为null,表示已经开启了一个事务,直接返回这个连接就行
if (cn != null) {
return cn;
}
return cpds.getConnection();
}
/**
* 返回连接池对象
* @return
*/
public static ComboPooledDataSource getDataSource(){
return cpds;
}
/**
* 开启事务
* 1、创建一个连接,并开启事务:cn.setAutoCommit(false);
* 2、这个连接需要给dao层用
* 3、这个连接还要给commitTransaction或rollbackTransaction用
*/
public static void beginTransaction() throws SQLException {
//如果事务已经存在,不能重复开启
if (cn != null) {
throw new SQLException("事务已经开启,不需要重复开启!");
}
//创建连接对象
cn=getConnction();
//开启事务
cn.setAutoCommit(false);
}
/**
* 提交事务
*/
public static void commitTransaction() throws SQLException {
if (cn == null) {
throw new SQLException("未开启事务,不可以提交!");
}
//提交事务
cn.commit();
//关闭连接
cn.close();
//将cn置为null,防止其他事务使用
cn=null;
}
/**
* 回滚事务
*/
public static void rollbackTransaction() throws SQLException {
if (cn == null) {
throw new SQLException("未开启事务,不可以回滚!");
}
//回滚事务
cn.rollback();
//关闭连接
cn.close();
//置空
cn=null;
}
}
Dao层:AccountDaoQuery .java
/**
* 转账数据库操作
*/
public class AccountDaoQuery {
public void updateAccount(String name,int money) throws SQLException {
//使用dbutils工具
QueryRunner qr=new QueryRunner();
//创建sql模板,通过名字查询记录,并修改与余额
String sql="update account set balance=balance+? where name=?";
//参数
Object [] params={money,name};
Connection cn=JDBCUtils.getConnction();
//执行
qr.update(cn,sql,params);
}
}
测试:Demo1.java
ublic class Demo1 {
//依赖dao层
AccountDaoQuery accountDAOQuery =new AccountDaoQuery();
@Test
public void func1() throws SQLException {
try{
//开启事务
JDBCUtils.beginTransaction();
//两部完成转账
accountDAOQuery.updateAccount("lisi",-100);
accountDAOQuery.updateAccount("zhangsan",100);
//结束事务
JDBCUtils.commitTransaction();
}catch (Exception e){
//回滚事务
JDBCUtils.rollbackTransaction();
}
}
}
完成上面的功能后,我们思考:如果是事务的连接,提交或是回滚后就会关闭。但是如果是普通的连接,上面的代码没有关闭(归还给数据库连接池),这样会造成资源浪费。所以我们需要在dao层操作完毕后判断如果是普通连接就关闭。
对于连接是否是事务的,JDBCUtils来操作更合理:
改进:JDBCUtils.java
/**
* 使用c3p0连接池
*/
public class JDBCUtils {
//创建一个连接池对象,这里在配置文件中设置参数 c3p0-config.xml
private static ComboPooledDataSource cpds=new ComboPooledDataSource();
//事务专用连接
private static Connection cn=null;
/**
* 返回连接对象
* @return
* @throws SQLException
*/
public static Connection getConnction() throws SQLException {
//如果cn不为null,表示已经开启了一个事务,直接返回这个连接就行
if (cn != null) {
return cn;
}
return cpds.getConnection();
}
/**
* 返回连接池对象
* @return
*/
public static ComboPooledDataSource getDataSource(){
return cpds;
}
/**
* 开启事务
* 1、创建一个连接,并开启事务:cn.setAutoCommit(false);
* 2、这个连接需要给dao层用
* 3、这个连接还要给commitTransaction或rollbackTransaction用
*/
public static void beginTransaction() throws SQLException {
//如果事务已经存在,不能重复开启
if (cn != null) {
throw new SQLException("事务已经开启,不需要重复开启!");
}
//创建连接对象
cn=getConnction();
//开启事务
cn.setAutoCommit(false);
}
/**
* 提交事务
*/
public static void commitTransaction() throws SQLException {
if (cn == null) {
throw new SQLException("未开启事务,不可以提交!");
}
//提交事务
cn.commit();
//关闭连接
cn.close();
//将cn置为null,防止其他事务使用
cn=null;
}
/**
* 回滚事务
*/
public static void rollbackTransaction() throws SQLException {
if (cn == null) {
throw new SQLException("未开启事务,不可以回滚!");
}
//回滚事务
cn.rollback();
//关闭连接
cn.close();
//置空
cn=null;
}
/**
* 释放非事务连接对象
* @param connection
*/
public static void releaseConnection(Connection connection) throws SQLException {
/**
* 判断传入的连接是否是事务连接
* 1、如果连接为null,则关闭连接:因为null一定不是事务
* 2、如果传入的连接不等于cn,关闭连接
*/
if (connection == null) {
connection.close();
}
if(connection!=cn) connection.close();
}
}
这样在dao层,的updateAccount方法最后添加:
//释放连接
JDBCUtils.releaseConnection(cn);
现在我们进一步思考:在dao层中每次操作都需要连接,并且完了都要判断释放连接,代码重复太多。如果我们能重写apache的DBUtils工具类里面没有连接对象作为参数的方法,把创建连接和释放连接放进去:
继承QueryRunner:TrQueryRunner.java
/**
* 自定义TrQueryRunner,继承QueryRunner,实现其中没有连接对象参数的函数
* 实现自动获取连接和释放连接
*/
public class TrQueryRunner extends QueryRunner {
@Override
public int[] batch(String sql, Object[][] params) throws SQLException {
//得到连接对象
Connection cn=JDBCUtils.getConnction();
//调用含有连接对象参数的函数
int []result=batch(cn,sql,params);
//释放连接
JDBCUtils.releaseConnection(cn);
return result;
}
@Override
public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
//得到连接对象
Connection cn=JDBCUtils.getConnction();
//调用含有连接对象参数的函数
T result=query(cn,sql,rsh,params);
//释放连接
JDBCUtils.releaseConnection(cn);
return result;
}
@Override
public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
//得到连接对象
Connection cn=JDBCUtils.getConnction();
//调用含有连接对象参数的函数
T result=query(cn,sql,rsh);
//释放连接
JDBCUtils.releaseConnection(cn);
return result;
}
@Override
public int update(String sql) throws SQLException {
//得到连接对象
Connection cn=JDBCUtils.getConnction();
//调用含有连接对象参数的函数
int result=update(cn,sql);
//释放连接
JDBCUtils.releaseConnection(cn);
return result;
}
@Override
public int update(String sql, Object param) throws SQLException {
//得到连接对象
Connection cn=JDBCUtils.getConnction();
//调用含有连接对象参数的函数
int result=update(cn,sql,param);
//释放连接
JDBCUtils.releaseConnection(cn);
return result;
}
@Override
public int update(String sql, Object... params) throws SQLException {
//得到连接对象
Connection cn=JDBCUtils.getConnction();
//调用含有连接对象参数的函数
int result=update(cn,sql,params);
//释放连接
JDBCUtils.releaseConnection(cn);
return result;
}
}
应用改进后的TrQueryRunner
转账dao层:AccountDaoTr.java
public class AccountDaoTr {
public void updateAccount(String name,int money) throws SQLException {
//使用自定义的TrQueryRunner类
QueryRunner qr=new TrQueryRunner();
String sql="update account set balance=balance+? where name=?";
//参数
Object [] params={money,name};
//执行重写的update方法,内部已经创建了连接对象
qr.update(sql,params);
}
}
测试类:Demo2.java
public class Demo2 {
AccountDaoTr accountDAOTr =new AccountDaoTr();
@Test
public void func1() throws SQLException {
try{
JDBCUtils.beginTransaction();
accountDAOTr.updateAccount("lisi",-100);
accountDAOTr.updateAccount("zhangsan",100);
JDBCUtils.commitTransaction();
}catch (Exception e){
JDBCUtils.rollbackTransaction();
}
}
}
上面的JDBCUtils工具不能满足多线程的任务,我们应用ThreadLocal来改进
改进:JDBCUtils.java
/**
* 使用c3p0连接池
*/
public class JDBCUtils {
//创建一个连接池对象,这里在配置文件中设置参数 c3p0-config.xml
private static ComboPooledDataSource cpds=new ComboPooledDataSource();
//通过将Connection放在ThreadLocal中,来满足多线程操作
private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
/**
* 返回连接对象
* @return
* @throws SQLException
*/
public static Connection getConnction() throws SQLException {
//首先从ThreadLocal中获取连接对象
Connection cn=tl.get();
//如果cn不为null,表示已经开启了一个事务,直接返回这个连接就行
if (cn != null) {
return cn;
}
return cpds.getConnection();
}
/**
* 返回连接池对象
* @return
*/
public static ComboPooledDataSource getDataSource(){
return cpds;
}
/**
* 开启事务
* 1、创建一个连接,并开启事务:cn.setAutoCommit(false);
* 2、这个连接需要给dao层用
* 3、这个连接还要给commitTransaction或rollbackTransaction用
*/
public static void beginTransaction() throws SQLException {
Connection cn=tl.get();
//如果事务已经存在,不能重复开启
if (cn != null) {
throw new SQLException("事务已经开启,不需要重复开启!");
}
//创建连接对象
cn=getConnction();
//开启事务
cn.setAutoCommit(false);
//将创建的事务连接放入threadLocal中
tl.set(cn);
}
/**
* 提交事务
*/
public static void commitTransaction() throws SQLException {
Connection cn=tl.get();
if (cn == null) {
throw new SQLException("未开启事务,不可以提交!");
}
//提交事务
cn.commit();
//关闭连接
cn.close();
//将cn置为null,防止其他事务使用
//cn=null;
//现在只需要从threadLocal中移除即可
tl.remove();
}
/**
* 回滚事务
*/
public static void rollbackTransaction() throws SQLException {
Connection cn=tl.get();
if (cn == null) {
throw new SQLException("未开启事务,不可以回滚!");
}
//回滚事务
cn.rollback();
//关闭连接
cn.close();
//置空
//cn=null;
tl.remove();
}
/**
* 释放非事务连接对象
* @param connection
*/
public static void releaseConnection(Connection connection) throws SQLException {
Connection cn=tl.get();
/**
* 判断传入的连接是否是事务连接
* 1、如果连接为null,则关闭连接:因为null一定不是事务
* 2、如果传入的连接不等于cn,关闭连接
*/
if (connection == null) {
connection.close();
}
if(connection!=cn) connection.close();
}
}