在WEB中应用MyBatis--事务回滚

虽然我们实现了这个银行转账的功能 ,但是在这个项目中,我们其实还存在一个很严重的问题 , 那就是事务回滚, 我们来看service层的这段代码:

 public String transform(String fromActno , String toActno , double money) {
//        在进行逻辑判断之前 , 首先要获取到转出账户和转入账户的信息
//        需要通过传来的账户同伙dao层进行查找数据
        Bank fromAct = dao.findAccountByActno(fromActno);//转账账户
        Bank toAct = dao.findAccountByActno(toActno); //收款账户
//        首先判断转账账户的金额是否充足
        if(fromAct.getBalance()<money){
//            余额不足
            return "余额不足" ;
        }
//        更新账户信息
//        先更新数据
        fromAct.setBalance(fromAct.getBalance()-money);
//        然后执行更新sql语句
        dao.updataAccountByActno(fromAct);

//        这里没有进行事务回滚,所以这里报错之后,会导致上面的数据已经更新,而下面收款的数据却没有更新,从而损失
//        所以我们的思路就是:让它的sqlsession是同一个对象,只有两个数据都完成更新后才提交事务。
//        那么如何让sqlSession对象是同一个呢?我们在创建sqlsession的对象的时候用ThreadLocal获取存储的对象
        
        String str = null ;
        str.toString();
       
        toAct.setBalance(toAct.getBalance()+money);
        dao.updataAccountByActno(toAct);
//        当整个转账逻辑执行完之后 , 我们在统一提交和关闭
        sqlSession.commit();
        SqlSessionUtil.close(sqlSession);//这里使用工具类的close方法关闭sqlSession对象
//        返回更新成功的信息
        return "更新成功" ;
    }

从上图中我们可以看见,如果在第一次更新转账账户数据后,发生了异常,而第二次接收账户用于发生了异常导致没有更新数据 , 那么就造成了钱已经扣掉了,但是收款账户却没有收到钱,从而白白损失掉了这部分钱。

所以,我们应该如何解决这个问题呢? 

首先 , 我们先来看看这两次调用dao层更新数据中的dao层的代码:

    public void updataAccountByActno(Bank bank) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        sqlSession.update("updataActno" , bank) ;

        sqlSession.commit();
    }

其实,我们会发现 , 之所以发生这种情况,就是因为在这两次更新数据调用dao层的过程中,dao层都会重新获取一个SqlSession对象 , 然后更新数据并提交,导致service层第一次更新数据就已经提交了。

所以 , 我们只需要在service创建一个SqlSession对象 , 然后让这个SqlSession对象去执行整个更新数据的方法,比如当他执行往第一次数据更新后,并不直接提交数据 , 而是继续执行第二次更新数据的操作 , 如果两个数据更新的操作都执行完成之后 , 再去提交数据 , 这样就算中间发生了异常 , 因为第一次更新数据后SqlSession对象并没有提交数据 , 所以也不会造成损失。

那么,我们现在思路就很清晰 , 首先我们需要一个SqlSession对象去执行service层的更新方法 , ,所以我们就不能在dao层去创建SqlSession对象,同时也不能再dao层提交数据 , 而要在service层当整个方法都执行完之后再去提交。

所以 ,这里就有一个问题 , 我们因为我们在dao层需要创建一个SqlSession对象去执行sql语句 , 但是每次调用SqlSession.opensession()都会创建一个新的对象 , 我们如何保证在这两次更新数据中用的都是同一个sqlSession对象呢?

其实,一个有两种方法 , 第一种就是在service创建一个SqlSession对象 , 然后在dao层不再创建新的SqlSession对象 , 而是把service层的SqlSession对象通过参数传过去 ,代码如下:

dao层就不需要再创建Sqlsession对象, 只需要接收service传来的对象来执行sql语句 , 这样就能保证整个过程都是同一个SqlSession对象。

dao层代码如下:

 public void updataAccountByActno(Bank bank , SqlSession sqlSession) {
//        SqlSession sqlSession = SqlSessionUtil.openSession();
        sqlSession.update("updataActno" , bank) ;
//        因为涉及到事务 , 所以我们不能在dao进行提交
//        sqlSession.commit();
    }

 虽然这种方法可以解决这个问题 , 但是dao层的接口都是写好的,大部分都不会让你私自去改参数的 , 你总不能自己去给接口加个参数 , 那么后面所有于此相关的代码都要改 , 显然实际开发中并不会用这种方式去做事务回滚。

所以这就需要我们使用另外一种方法:使用ThreadLocal。

其实我们的思路还是没有变 , 就是要使用一个SqlSession对象去执行这个数据更新。只不过换了一种方式。

现在我们需要使用ThreadLocal去存储一个sqlSession对象,我们在service层创建好一个sqlSession对象 , 然后把它存到ThreadLocal中去,然后在dao层中我们还是继续创建一个sqlSession对象 , 但是创建方法我们需要改,并不是直接调用SqlSession.opensession()方法去创建 , 因为这样一定是一个新的sqlSession对象 , 我们从ThreadLocal中获取 , 因为刚刚在service层我们已经创建了一个sqlSession对象并且存进去了,所以我们就是直接取这个sqlSession对象 , 这样就导致我们dao层每次获得的sqlSession对象都是同一个。

工具类中的获取SqlSession对象方法代码如下:

然后service层代码如下:

最后,在关闭的时候也需要重新写一下 , 代码以及注释如下:

 public static void close(SqlSession sqlSession){
//        为什么不直接使用sqlSession中的close()方法去关闭对象 , 还要在工具类里面专门写一个close方法呢?
//        因为我们选择的sqlSession对象是存储在ThreadLocal里面了, 所以我们关闭了SqlSession对象后 ,
//        我们还需要在ThreadLocal中清除它 , 否则会造成sqlSession复用 , 但是因为它关闭了,从而用了一个没用的
        sqlSession.close();//关闭
        local.remove();//同时移除
    }

 整个事务回滚到此就结束了,希望通过这个案例对事务有一个更深层的理解 , 加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值