声明式事务@Transaction失效场景

1. 开启事务支持

只需要通过 @EnableTransactionManagement 注解就可以开启声明式事务的支持,提供的可选值有:

  • proxyTargetClass :表示 AOP 代理是基于接口的还是基于类的, 默认 是基于接口代理。
  • mode :如果是有接口的话可以使用 PROXY ,如果没有接口的话可以使用 ASPECTJ 利用 CgLib 对类做增强。
  • order :指定事务拦截的顺序,默认是最低的优先级,这样可以保证其他的代理类是在事务开启之后执行。

2. @Transactional

开启了对事务注解的支持之后,我们就可以在需要的 或者 方法 上面添加 @Transactional 注解, @Transactional 注解的话也有一些设置的项。 ​

2.1 transactionManager

默认的话就是 DataSourceTransactionManager 。 ​

2.2 propagation

事务的传播机制,默认的话是 REQUIRED ,多个方法调用共享同一个事务。 ​

2.3 isolation

事务的隔离级别,这个跟使用的数据库有关系,默认的话是使用数据库默认的隔离级别。 ​

2.4 timeout

事务超时时间。 ​

2.5 readOnly

是否只读。 ​

2.6 rollbackFor

或者 方法 中抛出的异常与 rollbackFor 中指定的异常 instanceof 返回 true 的情况下会回滚事务。 ​

2.7 rollbackForClassName

rollbackFor 类似,只不过指定的是异常的名称。 ​

2.8 noRollbackFor

遇到指定的异常时候不会回滚事务 ​

2.9 noRollbackForClassName

noRollbackFor 类似,只不过指定的是异常的名称。

3. 没有通过代理对象执行

Spring 当中,声明式事务其实是为类做了一个代理,既然是代理类,也就是说需要去调用代理类才能够执行到需要被增强的方法,如果是在方法内部做调用的话,也就是没有走到代理方法或者说没有做增强处理,例如:

@Component
public class FooServiceImpl implements FooService {
  @Override
  @Transactional(rollbackFor = RollbackException.class)
  public void insertThenRollback() throws RollbackException {
    this.jdbcTemplate.execute("INSERT INTO FOO (BAR) values('BBB')");
    throw new RollbackException();
  }
​
  @Override
  public void invokeInsertThenRollback() throws RollbackException {
    this.insertThenRollback();
  }
}
@Override
public void run(String... args) throws Exception {
    try {
        this.fooService.invokeInsertThenRollback();
    } catch (RollbackException e) {
        // ignore.
    }
    log.info("bbb: {}.", this.countByBAR("BBB"));
}

日志输出如下:

bbb: 1.

要解决这个问题我们可以有以下几种方式: ​

3.1 通过当前代理对象调用自身实例

@Override
public void invokeInsertThenRollback() throws RollbackException {
  ((FooService) AopContext.currentProxy()).insertThenRollback();
}

3.2 注入自身实例

@Autowired
private FooService fooService;
​
@Override
public void invokeInsertThenRollback() throws RollbackException {
  this.fooService.insertThenRollback();
}

3.3 再加一层事务

@Override
@Transactional(rollbackFor = RollbackException.class)
public void invokeInsertThenRollback() throws RollbackException {
    this.insertThenRollback();
}

4. 不能对目标方法增强

@Transactional 只能支持 public 修饰方法,否则事务会失效,官方文档地址:Spring官方文档:声明式事务方法可见性

@Autowired
private FooServiceImpl fooService;
​
public void invokeInsertThenRollback() throws RollbackException {
    this.fooService.insertThenRollback();
}
@Transactional(rollbackFor = RollbackException.class)
protected void insertThenRollback() throws RollbackException {
    this.jdbcTemplate.execute("INSERT INTO FOO (BAR) values('BBB')");
    throw new RollbackException();
}

5. rollbackException与抛出的异常返回false

@Transactional 注解默认回滚异常是 RuntimeException 级别的异常,也就是非受检异常,如果是遇到了受检异常那么事务不会回滚:

public class RollbackException extends Exception {
}
@Transactional
public void insertThenRollback() throws RollbackException {
    this.jdbcTemplate.execute("INSERT INTO FOO (BAR) values('BBB')");
    throw new RollbackException();
}
public void invokeInsertThenRollback() throws RollbackException {
    this.fooService.insertThenRollback();
}

6. progagtion的选择问题

比如以下方法返回统计的 BBB1 ,有一条被回滚,有一条被成功执行。

@Override
@Transactional(rollbackFor = RollbackException.class,propagation = Propagation.REQUIRES_NEW)
public void insertThenRollback() {
    this.jdbcTemplate.execute("INSERT INTO FOO (BAR) values('BBB')");
}
​
@Override
@Transactional(rollbackFor = RollbackException.class)
public void invokeInsertThenRollback() throws RollbackException {
    this.jdbcTemplate.execute("INSERT into FOO (BAR) values('BBB')");
    this.fooService.insertThenRollback();
    throw new RollbackException();
}

作者:shaw
链接:声明式事务@Transaction失效场景 - 掘金