手写IOC和AOP(三)----添加事务控制

问题:
转账案例中,service层只是简单的实现了转账过程中变更数据库数据的操作,并没有进行相关的事务控制。如果在两个更新账户信息方法中间添加一个异常,第一个更新方法正常执行,而第二个方法则不会执行,造成了数据的不一致性。所以我们需要对业务处理过程添加事务控制,保证其满足事务的ACID原则。

	accountDao.updateAcctByCardId(fromAcctInfo);
	int i = 1 / 0;
	accountDao.updateAcctByCardId(toAcctInfo);

问题分析:
(1)在dao层,是存在事务控制的,也就是JDBC中的事务控制,归根结底也就是Connection的事务,Connection.commit()提交事务,Connection.rollback()回滚事务。不过该事务默认是自动提交的。我们可以通过connection.setAutoCommit(false)来关闭JDBC的自动提交事务控制。
(2)在service层,调用了两次updateAcctByCardId方法,也就是创建了两个不同的connection对象,这样肯定就不属于同一个事务控制了。
解决思路:
(1)两次updateAcctByCardId方法必须使用同一个connection对象。在tomcat项目中,每一次请求就是一个单独的线程,贯穿始终,我们可以将connection对象与线程进行绑定,什么时候需要,就在当前线程获取就可以保证connection对象唯一了。所以,我们可以用ThreadLocal来进行解决
(2)将事务控制添加到service层,对业务处理的过程添加事务控制。
代码修改:
(1)采用ThreadLocal将connection对象与当前线程绑定
新建一个ConnectionUtils来管理connection对象,进而获取当前线程所绑定的连接。注意的是,该工具类必须要保证单例,如果直接采用new的方式来获取工具类对象,在创建多个ConnectionUtils 对象的同时,也创建了多个连接。

public class ConnectionUtils {
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //构造方法私有化,防止创建ConnectionUtils对象
    private ConnectionUtils() {
    }

    private static ConnectionUtils connectionUtils = new ConnectionUtils();
    //采用恶汉式单例设计模式,保证ConnectionUtils为单例
    public static ConnectionUtils getInstance() {
        return connectionUtils;
    }

    public Connection getCurConnection() throws SQLException {
        //如果当前线程没有连接与其绑定,则从druid连接池中获取一个连接,并与当前线程绑定
        if (threadLocal.get() == null) {
            DruidPooledConnection connection = DruidUtils.getInstance().getConnection();
            threadLocal.set(connection);
            return connection;
        } else {
            //如果有连接,则直接返回与此线程绑定的连接
            return threadLocal.get();
        }
    }
}

AccountDaoImpl中,将之前直接从连接池获取对象,改成通过工具类从线程中获取。注意的是,connection对象在用完后不要关闭。

public class AccountDaoImpl implements AccountDao {
    @Override
    public Account queryAcctByCardId(String cardId) throws Exception {
        //使用阿里druid连接池,获取连接
//        Connection connection = DruidUtils.getInstance().getConnection();
        //修改为:从当前线程中获取connection
        Connection connection = ConnectionUtils.getInstance().getCurConnection();

        String sql = "select * from account where cardId = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1,cardId);
        ResultSet resultSet = preparedStatement.executeQuery();

        Account account = new Account();
        while (resultSet.next()){
            account.setName(resultSet.getString("cardId"));
            account.setMoney(resultSet.getInt("money"));
            account.setCardId(resultSet.getString("cardId"));
        }
        //关闭连接,归还连接到连接池
        resultSet.close();
        preparedStatement.close();
        //连接不可以关闭,否则下次调用的时候就又需要创建新的连接
//        connection.close();

        return account;
    }

    @Override
    public int updateAcctByCardId(Account account) throws Exception {
        //使用阿里druid连接池,获取连接
//        Connection connection = DruidUtils.getInstance().getConnection();
        //修改为:从当前线程中获取connection
        Connection connection = ConnectionUtils.getInstance().getCurConnection();

        String sql = "update account set money = ? where cardId = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,account.getMoney());
        preparedStatement.setString(2,account.getCardId());
        int result = preparedStatement.executeUpdate();

        //关闭连接,归还连接到连接池
        preparedStatement.close();
        //连接不可以关闭,否则下次调用的时候就又需要创建新的连接
//        connection.close();

        return result;
    }

(2)在service层,添加事务
创建事务管理器,用于管理事务,还是说这里的事务归根结底还是connection的事务控制。

/**
 * 事务管理器:用于手动管理事务的开启、提交、回滚
 */
public class TransactionManager {
    private TransactionManager() {
    }

    private static TransactionManager transactionManager = new TransactionManager();

    //饿汉式实现单例
    public static TransactionManager getInstance() {
        return transactionManager;
    }

    public void startTransaction() throws SQLException {
        //关闭JDBC中自动事务提交
        ConnectionUtils.getInstance().getCurConnection().setAutoCommit(false);
    }

    public void commit() throws SQLException {
        ConnectionUtils.getInstance().getCurConnection().commit();
    }

    public void rollBack() throws SQLException {
        ConnectionUtils.getInstance().getCurConnection().rollback();
    }
}

在transfer中,我们可以再加一个finally(),在finally中进行连接的关闭以及threadLocal.remove()移除绑定的连接。

public class TransferServiceImpl implements TransferService {
    //    private AccountDao accountDao = new AccountDaoImpl();
    //通过工厂获取对象
//    private AccountDao accountDao = (AccountDaoImpl)BeanFactory.getBean("accountDao");
    //最佳的方式
    private AccountDao accountDao;

    //set方法进行传值
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void transfer(String fromCardId, String toCardId, int money) throws Exception {
        try {
            //开启事务
            TransactionManager.getInstance().startTransaction();
            Account fromAcctInfo  = accountDao.queryAcctByCardId(fromCardId);
            Account toAcctInfo = accountDao.queryAcctByCardId(toCardId);
            fromAcctInfo.setMoney(fromAcctInfo.getMoney() - money);
            toAcctInfo.setMoney(toAcctInfo.getMoney() + money);
            accountDao.updateAcctByCardId(fromAcctInfo);
            int i = 1 / 0;
            accountDao.updateAcctByCardId(toAcctInfo);
            //提交事务
            TransactionManager.getInstance().commit();
        } catch (Exception e) {
            //回滚事务
            TransactionManager.getInstance().rollBack();
            e.printStackTrace();
            //抛出异常便于上层servlet捕获
            throw e;
        }
    }
}

如果service中有几十个或者更多的方法,需要添加事务控制,每个方法都这样去写是不是太麻烦了?如何去解决呢?下一篇将采用动态代理的方式进行实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值