简介

@Transactional声明式事务基于AOP实现,基本结构如下:

  • 开启事务
  • 执行方法体
  • 提交或回滚

锁失效问题

如果方法体中使用了锁,同步块中存在对相同记录的读写操作。由于锁的获取与释放在事务内部,因此锁是否有效受到事务隔离级别的影响。

事务隔离级别

  • Read uncommitted(读未提交):多个事务可同时读写,事务提交前写操作对读操作可见。
  • Read committed(读提交):多个事务可同时读写,事务提交前写操作对读操作不可见。
  • Repeatable read(可重复读取):多个事务可同时读;读/写事务提交前,其它事务不能写;写事务提交前,其它事务不能读写。
  • Serializable(可序化):多个事务串行执行。

锁失效原因

假设线程A、B同时操作一个事务方法。线程A开启事务a,执行方法体获得锁,执行完同步块后释放锁。在事务a提交之前,线程B开启事务b,执行方法体获得锁,执行同步块。最终事务a、b的提交顺序无法确定,在不同的事务隔离级别下可能出现以下情况:

  • Read uncommitted(读未提交) 事务b读到事务a未提交的数据。事务a、b先后提交,数据一致;事务b、a先后提交,数据不一致。
  • Read committed(读提交) 事务b读不到事务a未提交的数据,读到的是事务a修改之前的数据,不管事务提交顺序如何,数据不一致。
  • Repeatable read(可重复读) 事务b读不到事务a未提交的数据。
  • Serializable(串行化) 事务串行执行。线程A、B先后获得锁,事务A、B先后提交,数据一致;线程B、A先后获得锁,事务A、B先后提交,数据不一致。

小结

综上所述,在@Transactional方法中使用锁,由于事务隔离级别的不同以及事务提交顺序不确定,导致锁失效。如果需要使用锁+事务,则应该使用编程式事务,并且事务在同步块中开启。

回滚问题

一个线程嵌套执行多个由@Transactional注释的方法时,因事务传播行为的不同,可能出现意想不到的回滚问题。

事务传播行为

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务;否则创建一个新的事务。
    • 方法A调用方法B,如果两个方法都添加了@Transactional注解,在该传播行为下,会把两个方法的事务合并为一个事务。
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务的方式执行。
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;否则抛出异常。
  • Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则暂停该事务。
  • Propagation.NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则暂停该事务。
  • Propagation.NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
  • Propagation.NESTED:如果当前存在事务,则创建一个内部事务;否则创建一个新的事务。
    • 内部事务开始执行时,取得一个savepoint,执行异常时,回滚到savepoint。
    • 内部事务作为外部事务的子事务,内部事务正常执行后,随着外部事务一起提交。
    • 内部事务结束前,暂停外部事务。
    • 内部事务回滚不会影响外部事务;外部事务回滚,则内部事务一同回滚。

事务状态分析

假设有两个被@Transactional注解的方法A、B。方法A调用了方法B,当方法A开启事务,方法B有如下传播行为时的事务状态:

  • Propagation.REQUIRED、Propagation.SUPPORTS、Propagation.MANDATORY:方法A、B处于一个事务中。
  • Propagation.REQUIRES_NEW:方法A、B分别处于不同事务中。
  • Propagation.NOT_SUPPORTED:方法A处于事务中,方法B非事务方式执行。
  • Propagation.NEVER:方法A处于事务中,方法B抛异常。
  • Propagation.NESTED:方法A处于外部事务a中,方法B处于内部事务b中。
    • 事务b正常,事务a正常,事务b与事务a一同提交。
    • 事务b正常,事务a异常,事务a与事务b一同回滚。
    • 事务b异常,事务a正常,事务b回滚,事务a提交。
    • 事务b异常,事务a异常,事务b回滚,事务a回滚。

小结

综上所述,方法嵌套调用时,如果不同方法具有不同事物传播行为,那么出现异常时具有不同的事务回滚情况。