高效开发:如何使用@Tranactional注解保证本地事务一致性

237 阅读4分钟

文章目录

@Tranactional保证事务一致性

写dao层代码的时候,一个方法里面可能包含对多个表的update或insert,要保证本地事务的一致性,就要用到@Tranactional注解,这个注解的意思是:如果使用这个注解标注的方法抛出异常,就回滚该方法已经执行的所有逻辑,从而保证本地事务的一致性,即使用@Transactional注解标注的方法,要么不执行,要么全部正常执行。

值得注意的是,@Tranactional 只会回滚运行时异常Runtime Exception,不会回滚编译时异常。即使是运行时异常,大概也有两种情况:sql本身抛出的异常(insert主键冲突或唯一索引冲突)、程序员抛出的异常(update返回更新行数为0,这种sql不会抛出异常,要自己手动抛出 throw new RuntimeException)

在curd中,select如果where条件找不到就返回空,找到了返回指定记录,不管是否找到,都不会有异常;update如果指定where条件找不到就无法达到更新行记录的效果,但是不会抛出异常,如果where条件能找到就更新行记录,更不会抛出异常;insert语句如果主键或唯一性索引冲突会抛出运行时异常,插不进去,如果正常插入不会抛异常。delete一般用不到物理删除,就是设置一个字段,做一个update的伪删除。

select不会有异常,也不会抛出异常;insert有异常的时候会抛出来,@Tranactional捕获到就会回滚;但是,update如果没更新到不会抛出异常,这样@Transactional不会捕获,就达不到回滚的目的。这是程序员需要考虑到的。

如果是throw new MyBusinessException(select找不到抛出,或update更新不到抛出)或者内部运行时异常,只要没有被方法的catch处理,都会先触发本地事务回滚,然后被全局拦截器包装成一个错误返回值,发送出去给前端。

值得注意的是,这里的MyBusinessException必须直接或间接继承RuntimeException,保证是运行时异常。

举例一:throw new MyBusinessException (select找不到抛出)可以保证后面的update不会被执行到,从而保证事务的一致性。

Person person = personDao.getPersonByAge(age);
if(person==null)
   throw new  MyBusinessException();
updatePersonByAge(age);

throw new MyBusinessException(update抛出),保证update可以保证事务一致性

Integer row  = updatePersonByAge(age);
if (row < 1)
    throw new  MyBusinessException();  

举例二:

insertPerson(person);

如果insertPerson失败,sql内部会抛出异常,会被@Tranactional捕获,本地回滚,然后全局拦截器组装错误报文给前端,从而保证事务一致性。

见过有人的代码这样写,如下:

if(!insertPerson(person)){
    return resultMap;
}

没有必要,如果insertPerson正常,返回为true,不会执行到 return resultMap;
如果insertPerson内部错误,sql内部会抛出异常,会被@Tranactional捕获,本地回滚,然后全局拦截器组装错误报文给前端,从而保证事务一致性,也不会执行到 return resultMap;

所以,对insertPerson这个返回结果判断是没有任何意义的,永远不会执行到 return resultMap;

return 和 throw

一般来说,直接用throw,不要用return。

如果某个return之前没有update,可以用return,如果某个return之前有update,必须用 throw ,如果后面当前出错,可以回滚之前的update,保证事务一致性。

对应常见的curd的业务中,insert是不会有问题的,如果因为主键重复导致问题,@Transactional注解一定可以回滚,其他insert问题也可回滚,一定可以保证事务的一致性。

重点关心update:

(1) 如果是第一个update,执行前直接用select for update + return + update(或者update + return),select没找到就return整个方法,由于是第一个update,前面也没有update,不需要回滚。

(2) 如果不是第一个update,只能使用select for update + throw + update (或者 update + throw),当前update没有更新到就直接throw运行时异常且不能被catch块吃掉,保证前面的update得到回滚。

王道在于:无论对于哪个update,取出返回值int,不合法就throw,让整个方法失败,保证其前面得到回滚,从而保证事务一致性。