把事务处理的方法从DAO层中抽离出来,写到Service层中,还有就是Service层中不能有Connection相关的东西,所以要对得到同一连接做相应的封装处理。
进一步封装JdbcUtils,使其具有事务的功能(即,有开启事务,提交事务,回滚事务功能),并且支持多线程(ThreadLocal的应用)。
该数据库连接池使用c3p0
1.JdbcUtils.java:
package com.xjs.jdbcutils; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class JdbcUtils { //使用文件的默认配置,要求必须给出c3p0-config.xml private static ComboPooledDataSource dataSource=new ComboPooledDataSource(); //它是事务专用连接---允许多线程访问 private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>(); //使用连接池返回一个连接对象 public static Connection getConnection() throws SQLException{ //得到自己线程的con,解决多线程问题 Connection con=tl.get(); //当con不为null时,说明已经调用过beginTransaction方法,表示开启了事务 if(con != null){ return con; } //如果是简单的获取连接的话,就返回连接池创建的连接 return dataSource.getConnection(); } //返回连接池对象 public static DataSource getDataSource(){ return dataSource; } /** * 开启事务 * 1.获取一个connection,设置它的setAutoCommit(false) * 2.还有保证DAO中使用的连接是我们刚刚创建的! * ------------------ * 1.创建一个Connection,设置为手动提交(事务) * 2.把这个Connection给DAO用! * 3.还要让commitTransaction或rollbackTransaction可以获取到! * @throws SQLException */ public static void beginTransaction() throws SQLException{ Connection con=tl.get(); if(con != null) throw new SQLException("已经开启了事务,就不要重复开启了!"); /* * 1.给con赋值! * 2.给con设置手动提交! */ con=getConnection(); con.setAutoCommit(false); tl.set(con);//把当前线程的;连接保存起来 } /** * 提交事务 * 1.获取beginTransaction提供的Connection,然后调用commit方法 * @throws SQLException */ public static void commitTransaction() throws SQLException{ Connection con=tl.get();//获取当前线程的专用连接 if(con == null) throw new SQLException("还没有开启了事务,不能提交!"); //直接使用con.commit() con.commit(); con.close();//在这只是把连接归还给连接池了,这时con不为null;防止后面再次使用时出错,把con赋为null tl.remove();//从tl中移除连接 } /** * 回滚事务 * 1.获取beginTransaction提供的Connection,然后调用rollback方法 * @throws SQLException */ public static void rollbackTransaction() throws SQLException{ Connection con=tl.get(); if(con == null) throw new SQLException("还没有开启了事务,不能关闭!"); //直接使用con.rollback() con.rollback(); con.close(); //把它设置为null,表示事务已经结束了!下次在调用getConnection()返回的就不是con了! tl.remove(); } /** * 释放连接 * @param connection * @throws SQLException */ public static void releaseConnection(Connection connection) throws SQLException{ Connection con=tl.get(); /* * 判断它是不是事务专用,如果是,就不关闭! * 如果不是事务专用,那么就要关闭! */ //如果con=null,说明现在没有事务,那么connection一定不是事务专用! if(con == null) connection.close(); //如果con!=null,说明有事务,那么需要判断参数连接是否与con相等,若不等,说明参数连接不是事务专用连接 if(con != connection) connection.close(); } }
QueryRunner类是apache对数据库操作进行的封装,对事务的实现不完善,自己写一个TxQueryRunner类继承QueryRunner类,重写父类中(参数中带Connection)的方法。在开启事务之后,TxQueryRunner类中的方法都已经得到事务专用的connection(使用时都需要传递参数Connection)都支持事务。
2.TxQueryRunner.java:
package com.xjs.jdbcutils; import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.ResultSetHandler; /** * 这个类中的方法,自己类处理连接的问题 * 无需外界传递! * 怎样处理的呢? * 通过JdbcUtils.getConnection()得到连接!又可能是事务连接,也有可能是普通连接! * JdbcUtils.releaseConnection()完成对连接的释放!如果是普通连接,关闭之(即把con归还给池)! * @author hp * */ public class TxQueryRunner extends QueryRunner{ /** * 继承父类,重写父类中不带connection参数的方法,自己给那些不带con参数的方法加上con, * 在方法前面得到con,方法后面释放con * 这样能保证这个类中的所有方法都使用Connection对象 */ @Override public int[] batch(String sql, Object[][] params) throws SQLException { /* * 1.得到连接 * 2.执行父类方法,传递连接对象 * 3.释放连接 * 4.返回值 */ Connection con=JdbcUtils.getConnection(); int[] result=super.batch(con, sql, params); JdbcUtils.releaseConnection(con); return result; } @Override public <T> T query(String sql, Object param, ResultSetHandler<T> rsh) throws SQLException { Connection con=JdbcUtils.getConnection(); T result=super.query(con, sql, param, rsh); JdbcUtils.releaseConnection(con); return result; } @Override public <T> T query(String sql, Object[] params, ResultSetHandler<T> rsh) throws SQLException { Connection con=JdbcUtils.getConnection(); T result=super.query(con, sql, params, rsh); JdbcUtils.releaseConnection(con); return result; } @Override public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { Connection con=JdbcUtils.getConnection(); T result=super.query(con, sql, rsh, params); JdbcUtils.releaseConnection(con); return result; } @Override public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException { Connection con=JdbcUtils.getConnection(); T result=super.query(con, sql, rsh); JdbcUtils.releaseConnection(con); return result; } @Override public int update(String sql) throws SQLException { Connection con=JdbcUtils.getConnection(); int result=super.update(con, sql); JdbcUtils.releaseConnection(con); return result; } @Override public int update(String sql, Object param) throws SQLException { Connection con=JdbcUtils.getConnection(); int result=super.update(con, sql, param); JdbcUtils.releaseConnection(con); return result; } @Override public int update(String sql, Object... params) throws SQLException { Connection con=JdbcUtils.getConnection(); int result=super.update(con, sql, params); JdbcUtils.releaseConnection(con); return result; } }
3.模拟Dao层:
package com.xjs.jdbcutils; import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; //Dao层--转账 public class AccountDao { public static void update(String name,double money) throws SQLException{ //使用QueryRunner(对操作数据库一些相同步骤进行了封装),完成对数据库的操作---使用commons-dbutils包中的类 QueryRunner qr=new TxQueryRunner(); String sql="update account set balance=balance+? where name=?"; Object[] params={money,name}; qr.update(sql, params);//没有con,在方法的内部有con } }
该Dao层中使用的TxQueryRunner类中的方法,其方法中有事务专用的连接con,以及con的释放。注意:(其实在这的这个事务专用连接con没有关闭,也没有归还给池。因为在下边的其他方法中还需用到它对数据库操作。)
4.模拟service层:---调用dao层
package com.xjs.jdbcutils; import java.sql.SQLException; import org.junit.Test; //模拟service层 public class Demo1 { private AccountDao dao = new AccountDao(); @Test public void serviceMethod() throws Exception { try { JdbcUtils.beginTransaction(); dao.update("zs", -100); /*if (true){ throw new RuntimeException("抛出异常。。。"); }*/ dao.update("ls", 100); JdbcUtils.commitTransaction(); } catch (Exception e) { try { JdbcUtils.rollbackTransaction(); } catch (SQLException e1) { } throw e; } } }
这样可以完成一次转账的操作。
数据库:
各类的使用: