Service 事务(JdbcUtils 升级)

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

  1. 把对事物的开启和关闭放到 JdbcUtils 中,在 Service 中调用 JdbcUtils 的方法来完成事务的处理,
    但在 Service 中就不会再出现 Connection 了.
  2. 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<Connection> tl = new ThreadLocal<Connection>();

    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);
    }
}

参考资料:

原文地址:https://www.cnblogs.com/linkworld/p/7627253.html