1. DAO 事务
// 在 DAO 中处理事务真是"小菜一碟"
public void xxx(){
Connection con = null;
try{
con = JdbcUtils.getConnection();
con.setAutoCommit(false); // 开启事务
QueryRunner qr = new QueryRunner();
String sql = ...;
Object[] params = ...;
qr.update(con,sql,params);
sql = ...;
Object[] params = ...;
qr.update(con,sql,params);
con.commit(); // 提交事务
} catch(Exception e){
try{
// 回滚事务
if(con != null) {con.rollback();}
}catch(Exception e){}
}finally{
try{
con.close();
}catch(Exception e){}
}
}
2. Service 才是处理事务的地方
DAO 中不是处理事务的地方,因为 DAO 中的每个方法都是对数据库的一次操作, 而 Service 中的方法才是
对应一个业务逻辑,也就是我们需要在 Service 中的一方法中调用 DAO 的多个方法,而这些方法应该在一个
事务中.
// 事务需要保证为同一个 Connection
// 可以通过向 DAO 中传递 Connection, 来保证 DAO 的多个方法使用相同的 Connection
public class XXXService(){
private XXXDao dao = new XXXDao();
public void serviceMethod(){
// 但是 Connection 对象只能出现在 DAO 中, 因为它是 JDBC 的东西,
// JDBC 的东西是用来连接数据库的, 连接数据库是由 DAO负责, 而事务却
// 应该由 Service 负责.
Connection con = null;
try{
con = JdbcUtils.getConnection();
con.setAutoCommit(false);
// 向 DAO 中传递 Connection
dao.daoMethod2(con,...);
dao.datMethod3(con,...);
con.commit();
}catch(Exception e){
try{
con.rollback();
} catch(Exception e){}
}finally{
try{
con.close();
}catch(Exception e){}
}
}
}
3. 修改 JdbcUtils
把对事物的开启和关闭放到 JdbcUtils 中,在 Service 中调用 JdbcUtils 的方法来完成事务的处理,
但在 Service 中就不会再出现 Connection 了.
DAO 中的方法不用再让 Service 来传递 Connection 了, DAO 会主动从 JdbcUtils 中获取 Connection
对象, 这样, JdbcUtils 成为了 DAO 和 Service 的中介!
// Service 中的代码
public class XXXService(){
private XXXDao dao = new XXXDao();
public void serviceMethod(){
try{
JdbcUtils.beginTransaction();
dao.daoMethod2();
dao.daoMethod3();
JdbcUtils.commitTransaction();
}catch(Exception e){
JdbcUtils.rollbackTransaction();
}
}
}
// JdbcUtils 代码
public class JdbcUtils{
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 它是事务专用连接, 并且每个线程分配一个Connection
private static ThreadLocal tl = new ThreadLocal();
public static Connection getConnection() throws SQLException{
Connection con = tl.get(); // 获取当前线程的 con
// 当 con 不等于 null, 说明已经调用过 beginTransaction() 方法了.表示开启了事务.
if(con != null) return con;
return dataSource.getConnection();
}
public static DataSource getDataSource(){
return dataSource();
}
// 添加开启事务的方法
// 获取一个 Connection, 设置它的 setAutoCommit(false)
// 还要保证 DAO 中使用的连接是我们刚刚创建的!!
/*
* 1. 创建一个 Connection, 设置为手动提交
* 2. 把这个 Connection 给 DAO 用!
* 3. 还要让 commitTransaction 或 rollbackTransaction 可以获取到!!
*/
public static void beginTransaction() throws SQLException {
Connection con = tl.get();
if(con != null) throw new SQLException("已经开启了事务,就不要重复开启了!");
con = getConnection(); // 给 con 赋值, 表示事务已经开启了.
con.setAutoCommit(false);
tl.set(con); // 把当前线程的连接保存起来.
}
// 添加提交事务的方法
// 获取 beginTransaction 提供的 Connection, 然后调用 commit 方法
public static void commitTransaction() throws SQLException {
Connection con = tl.get(); // 获取当前线程的专用连接
if(con == null) throw new SQLException("还没有开启事务,不能提交!");
con.commit();
con.close();
// 把它设置为 null, 表示事务已经结束了.
// 下次再去调用 getconnection(),返回的就不是 con 了.
// con = null; 因为有了线程, 所以将 con 直接从线程中移除即可
tl.remove(); // 从 tl 中移除连接
}
// 添加回滚事务的方法
// 获取 beginTransaction 提供的 Connection, 然后调用 rollback 方法.
public static void rollbackTransaction() throws SQLException {
Connection con = tl.get(); // 获取当前线程的专用连接
if(con == null) throw new SQLException("还没有开启事务,不能回滚!");
con.rollback();
con.close();
tl.remove();
}
// 释放连接
public static void releaseConnection(Connection con) throws SQLException{
Connection con = tl.get(); // 获取线程中的事务
// 判断 connection 是不是事务专用, 如果是, 就不关闭
// 如果不是事务专用, 那么就要关闭
// con 是事务专用连接, 如果 con 为 null,表示没有事务.
// 那么, connection 肯定不是事务专用的.
if(con == null) connection.close();
// 如果 con != null, 说明有事务,那么需要判断参数连接是否与 con 相等,
// 如果不相等, 说明 connection 不是事务专用连接.
if(con != connection) connection.close();
}
}
// DAO 层代码
public class AccountDao{
public void update(String name, double money) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "UPDATE account SET balance=balance+? WHERE name=?";
Object[] params = {money,name};
// 我们需要自己来提供连接, 保证在同一事务中, 多次调用使用的是同一个连接!!
Connection con = JdbcUtils.getConnection();
qr.update(con,sql,params);
// 关闭连接
JdbcUtils.releaseConnection(connection);
}
}
参考资料: