关于@Transactionsal注解

一 . 关于事务

  1 .事务的特性:

    1) 原子性(Atomicity) 事务是数据库的逻辑工作单位,它对数据库的修改要么全部执行,要么全部不执行。
    2) 一致性(Consistemcy) 事务前后,数据库的状态都满足所有的完整性约束。
    3) 隔离性(Isolation) 并发执行的事务是隔离的,一个不影响一个。通过设置数据库的隔离级别,可以达到不同的隔离效果
    4) 持久性(Durability) 在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

  2 .@Transactional注解的配置项:

    @Transactional(propagation=Propagation.REQUIRED) //控制事务传播。默认是Propagation.REQUIRED
    @Transactional(isolation=Isolation.DEFAULT) //控制事务隔离级别。默认跟数据库的隔离级别相同
    @Transactional(readOnly=false) //控制事务可读写、只可读。默认可读写
    @Transactional(timeout=30) //控制事务的超时时间,单位秒。默认跟数据库的事务控制系统相同
    @Transactional(rollbackFor=RuntimeException.class) //控制事务遇到哪些异常会回滚。默认是RuntimeException
    @Transactional(rollbackForClassName=RuntimeException) //同上
    @Transactional(noRollbackFor=NullPointerException.class) //控制事务遇到哪些异常不会回滚。默认遇到RuntimeException回滚
    @Transactional(noRollbackForClassName=NullPointerException)//同上

  3 .事务的7种传播特性:

    1) REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
    2) SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
    3) MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
    4) NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行
    5) NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
    6) REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个已经存在的事务挂起
    7) NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务

 

二 . 关于方法调用引起的事务

  1. 方法a()调用带有事务的方法b()

  方法a()没有事务 方法a()有事务
REQUIRED  b()创建自己的事务 b()接受a()的事务
SUPPORTS  b()不创建自己的事务  b()接受a()的事务
MANDATORY  b()报异常 b()接受a()的事务
NESTED  b()创建自己的事务 b()接受a()的事务,成为a()嵌套的子事务
NEVER  b()不创建自己的事务 b()报异常
REQUIRES_NEW  b()创建自己的事务 b()不接受a()的事务,b()先执行,内层事务失败不会影响外层事务
NOT_SUPPORTED  b()不创建自己的事务 b()不接受a()的事务,b()先执行

以上详细内容参考:https://www.cnblogs.com/lukelook/p/11246180.html

三 . 使用@Transactional注解,在方法中抛出异常时,回滚不生效

  1. 可能的原因:
    1) 数据库引擎要支持事务
          如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的

    2) 是否开启了对注解的解析

          配置文件必须加<tx:annotation-driven />,否则不解析@Transactional

  2. 解决办法:

    1) 如果是MySQL数据库,查看引擎是否是支持事务的引擎

    2) 抛出的异常是否是注解的回滚异常,默认是RuntimeException或者Error

      在业务代码中,有如下两种情况,比如:

      throw new RuntimeException("xxxxxxxxxxxx"); 事务回滚
      throw new Exception("xxxxxxxxxxxx"); 事务没有回滚

      此时需要将@Transactional注解的rollbackFor参数赋值为Exception.class(或者某个特定异常的class),此时将支持该异常回滚

    3) 如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚,

     “将异常捕获,并且在catch块中不对事务做显式提交=生吞掉异常”,如果非要catch,在做完你想做的工作后一定要抛出可引起回滚的异常,否则spring会将你的操作commit

  3. 针对是否捕获异常那么现在有两个情况:

    情况1:如果没有在程序中手动捕获异常

1 @Transactional(rollbackFor = { Exception.class })
2 public void test() throws Exception {
3      doDbStuff1();
4      doDbStuff2();
5     //假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作会回滚。
6 }

    情况2:如果在程序中自己捕获了异常

1 @Transactional(rollbackFor = { Exception.class })
2 public void test() {
3      try {
4         doDbStuff1();
5         doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作  不会回滚。
6      } catch (Exception e) {
7            e.printStackTrace();   
8      }
9 }

    现在如果我们需要手动捕获异常,并且也希望抛异常的时候能回滚肿么办呢?
    方法一:

 1 @Transactional(rollbackFor = { Exception.class })
 2 public void test() {
 3      try {
 4         doDbStuff1();
 5         doDbStuff2();
 6      } catch (Exception e) {
 7           e.printStackTrace();   
 8           TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//就是这一句了,加上之后,如果doDbStuff2()抛了异常,                                                                                       //doDbStuff1()是会回滚的
 9      }
10 }

以上内容详细参考:https://blog.csdn.net/qq_24084925/article/details/53116846

    需要注意的是:在方法一中,使用的 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 会将方法事务设置为只回滚,无法commit

如果想要实现正常的回滚和提交,可以使用 savepoint 方式:

 1 @Transactional(rollbackFor = { Exception.class })
 2 public void test() {
 3     Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
 4     try {
 5         doDbStuff1();
 6         doDbStuff2();
 7      } catch (Exception e) {
 8           e.printStackTrace();   
 9           TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);//如果抛了异常,会回滚到savepoint
10      }
11 }

    方法二:

      在捕获异常后,再抛出一个可以被事务识别的异常,使其回滚

 1 @Transactional(rollbackFor = { Exception.class })
 2 public void test() {
 3      try {
 4         doDbStuff1();
 5         doDbStuff2();
 6      } catch (Exception e) {
 7           e.printStackTrace();   
 8           throw new Exception();
 9      }
10 }

      需要注意的是:此种方法在捕获后进行的操作,只有在回滚后不影响其效果时使用,如,打印日志操作,回滚后不会将已经打印好的日志删除

  4 . spring动态代理为事务埋下的坑

    描述:spring中的事务控制是通过动态代理实现的, 在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.由于spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!通俗点讲就是一个类的AOP动态代理,只有在其方法被其他类中的方法调用时才会触发(使用触发是否恰当留待后续

),后续的通过本类的方法调用本类的方法便不会再次触发动态代理,也就无法再创建事务

    解决办法:

      办法一:可以把方法B放到另外一个service或者dao,然后把这个server或者dao通过@Autowired注入到方法A的bean里面,这样即使方法A没用事务,方法B也可以执行自己的事务了。(将方法b(抽离到其他bean中,这样在调用时会重新触发动态代理))

      办法二:通过AOP代理调用b方法即可走事务切面

1 public class TestService{
2     public void a() {  
3         TestService aopProxy = (TestService)AopContext.currentProxy();
4         aopProxy.b();//即调用AOP代理对象的b方法即可执行事务切面进行事务增强  
5     }
6 }

详情参考:https://www.iteye.com/topic/35907/

https://www.cnblogs.com/sung1024/archive/2004/01/13/11700449.html

如何获取代理对象,可以参考:

https://blog.csdn.net/dapinxiaohuo/article/details/52092447

 

每天一点点

 

posted @ 2021-01-05 20:42  xiao_lin  阅读(197)  评论(0编辑  收藏  举报