(二十四)JDBC应用的事务管理(转账事例)

目录


利用 Dbutils 进行事务操作(以转账为例)

    我们只在dao层进行增删改查操作。切忌在dao层直接进行转账的业务逻辑 ;


    我们在services层开启一个连接,在连接上开启事务,然后在这个链接上进行多条sql操作 ;



·老方说,其实上面那样写,还是不优雅的;优雅的写法:spring事务管理或者使用 ThreadLocal 类 ;

                        (备注:经常听到spring的大名,很好奇spring到底是怎样的存在呢,现在还没学,很少好奇!)

转账实现方式(不优雅的方式)

dao层代码:

public class EmployeeDao1 {

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    private Connection connection;

    public EmployeeDao1() {
    }

    public EmployeeDao1(Connection connection) {
        this.connection = connection;
    }

    public void update(Employee e) throws SQLException {
//        不要将连接池作为参数,这里我们要用事务
        QueryRunner runner = new QueryRunner();
        String sql = "update employee set money = ? where name = ?";
        Object[] params = {e.getMoney(), e.getName()};
        runner.update(connection, sql, params);
    }

    public Employee find(int id) throws SQLException {

        QueryRunner runner = new QueryRunner();
        String sql = "select * from employee where id =?";
        Employee employee = runner.query(connection,sql,new BeanHandler<Employee>(Employee.class),id);
        return employee;
    }

}

services层进行调用:

/**
     * 转账的基础版本(不优雅的实现)
     *
     * @param sourceId 源账户的id
     * @param targetId 目标账户的id
     * @param money    转换的金额
     */
    public void transaction1(int sourceId, int targetId, int money) throws SQLException {
        Connection connection = null;
        try {
//            获取连接
            connection = JdbcDataSourceUtils.getConnection();
            System.out.println(" --- "+connection);
//            开启事务
            connection.setAutoCommit(false);
//            获取Dao层对象,将开启事务的连接 传递进去
            EmployeeDao1 dao1 = EmployeeFactory.getDao1(connection);
//          查找用户
            Employee s = dao1.find(sourceId);
            Employee t = dao1.find(targetId);
//            转账逻辑 a减b增
            s.setMoney(s.getMoney() - money);
            t.setMoney(t.getMoney() + money);
//            进行转换
            dao1.update(s);
            dao1.update(t);
//            提交事务
            connection.commit();
        } finally {
            connection.close();
        }
    }

我们可以看到,我们在调用dao层,需要传进去连接,还需要在连接上,开启事务,最后还有提交。每次进行类似的逻辑,都需要 重复 这样的逻辑代码。


ThreadLocal 类

    其实内部就是一个Map集合 ;

    ·ThreadLocal 类在 J2EE 中很重要,它可以在线程内共享数据 ;以后在services、dao层之间传递数据,就不需要再利用方法参数传递了

    利用方法参数,太死板了;我们就可以选择 ThreadLocal 类来完成了。用法:写一个工具类,内部维护着一个 ThreadLocl 对象;

    不要说什么域,存数据,我就问下,dao层有request对象吗,能取到域中数据吗。。。

    ·许多框架使用了这个类 ;


    ·以后学框架,不但要学会用法,也要学习下源码,实现原理。。


    ·现在我们学的时事务,是一个services里面的多个dao操作的事务。

    ·以后还要学习多个services为一个事务,就需要用到过滤器了,可以保证一次请求以内的所有操作,都在一个事务里面。

转账实现方式(优雅的方式)

dao层代码:(很简短)

public class EmployeeDao2 {


    public void update(Employee e) throws SQLException {
//        不要将连接池作为参数,这里我们要用事务
        QueryRunner runner = new QueryRunner();
        String sql = "update employee set money = ? where name = ?";
        Object[] params = {e.getMoney(), e.getName()};
        runner.update(JdbcDataSourceUtils.getConnection(), sql, params);
    }

    public Employee find(int id) throws SQLException {

        QueryRunner runner = new QueryRunner();
        String sql = "select * from employee where id =?";
        Employee employee = runner.query(JdbcDataSourceUtils.getConnection(),sql,new BeanHandler<Employee>(Employee.class),id);
        return employee;
    }

}

services层代码:

/**
     * 转账逻辑 优雅的实习方式
     * @param sourceId 源客户id
     * @param targetId 目标客户id
     * @param money 转账金额
     * @throws SQLException
     */
    public void transaction2(int sourceId, int targetId, int money) throws SQLException {
        try{
            //        在线程上绑定已经开启事务的连接
            JdbcDataSourceUtils.startTransaction();
//       获取dao层实例
            EmployeeDao2 dao2 = EmployeeFactory.getDao2() ;
//      查找用户
            Employee s = dao2.find(sourceId) ;
            Employee t = dao2.find(targetId) ;
//            转账逻辑 a减b增
            s.setMoney(s.getMoney() - money);
            t.setMoney(t.getMoney() + money);
//            进行转换
            dao2.update(s);
            dao2.update(t);
//        提交事务
            JdbcDataSourceUtils.commitTransaction();
        }finally {
            JdbcDataSourceUtils.closeConnetion();
        }
    }

其中涉及到的工具类代码:(ThreadLoacl类的使用)

/**
     * 获取连接,如若线程上已经绑定连接,则返回线程上绑定的连接,否则则从连接池中获取一个连接返回
     *
     * @return 连接
     */
    public static Connection getConnection() {
        //      先判断线程上是否有Connection
        Connection connection = tl.get();
        if (null == connection) {
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        return connection;
    }


    /**
     * 为当前线程绑定一个开启事务的连接
     */
    public static void startTransaction() {
//      先判断线程上是否有Connection
//        get 也是由执行这个方法的线程调用
        Connection connection = tl.get();
        if (null == connection) {
            try {
                connection = dataSource.getConnection();
                tl.set(connection);
                //        开启事务
                connection.setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }


    /**
     * 提交事务
     */
    public static void commitTransaction() {
        try {
            Connection connection = tl.get();
            if (connection != null) {
                connection.commit();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 关闭连接
     */
    public static void closeConnetion() {
        try {
            Connection connection = tl.get();
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
//            哪一个线程调用这个方法,就按照线程为key去移除对应的Connection
            tl.remove();
        }
    }
原文地址:https://www.cnblogs.com/young-youth/p/11665697.html