聊聊Spring事务失效场景!

事务方法被Final、Static关键字修饰:

如果一个方法被声明为final或者static,则该方法不能被子类重写。

  • 也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class ServiceImpl {

@Autowired
private TestMapper1 mapper1;

@Autowired
private TestMapper2 mapper2;

@Transactional
public final void add(Test test) {
mapper1.save();
mapper2.save();
}
}

同一个类中,方法内部调用:

事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时。

  • 调用方法直接调用目标方法的代码,而不是通过代理类进行调用。
    • 即调用目标add方法不是通过代理类进行的,因此事务不生效。

解决方案:可以新建多一个类,让这两个方法分开,分别在不同的类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class ServiceImpl implements Service {

@Autowired
private TestMapper1 mapper1;

@Autowired
private TestMapper2 mapper2;

public void add(Test test){
// 调用内部的事务方法
this.add(test);
}

@Transactional
public void add(Test test) {
mapper1.save();
mapper2.save();
}
}

方法的访问权限不是Public:

代理的事务方法不是public的话,springcomputeTransactionAttribute()就会返回null。

  • 这时事务属性不存在了。

RollbackFor属性配置错误:

rollbackFor属性指定的异常必须是Throwable或者其子类。

  • 默认情况下,RuntimeExceptionError两种异常都是会自动回滚的。

代码例子:

  • 指定了rollbackFor = Error.class,但是抛出的异常又是Exception,因此事务就不生效。
1
2
3
4
5
6
7
@Transactional(rollbackFor = Error.class)
public void add(Test test) {
mapper1.save(test);
mapper2.save(test);
//模拟异常抛出
throw new Exception();
}

事务注解被覆盖导致事务失效:

MyTestServiceMyService的子类,并且覆盖了doSomething()方法。

  • 在该方法中,使用了不同的传播行为(REQUIRES_NEW)来覆盖父类的@Transactional注解。

在这种情况下,当调用MyTestServicedoSomething()方法时

  • 由于子类方法中的注解覆盖了父类的注解,Spring框架将不会在父类的方法中启动事务。

因此,当MyRepositorysave()方法被调用时,事务将不会被启动,也不会回滚。

  • 这将导致数据不一致的问题,因为在MyRepositorysave()方法中进行的数据库操作将不会回滚。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface MyRepository {
@Transactional
void save(String data);
}

public class MyRepositoryImpl implements MyRepository {
@Override
public void save(String data) {
// 数据库操作
}
}

public class MyService {

@Autowired
private MyRepository myRepository;

@Transactional
public void doSomething(String data) {
myRepository.save(data);
}
}

public class MyTestService extends MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething(String data) {
super.doSomething(data);
}
}

嵌套事务:

如果saveFlow出现运行时异常,会继续往上抛,到外层add的方法。

  • 导致mapper.save也会回滚啦。

如果不想因为被内部嵌套的事务影响,可以用try-catch包住。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class ServiceInOutService {

@Autowired
private FlowService flowService;
@Autowired
private Mapper mapper;

@Transactional
public void add(Test test) throws Exception {
mapper.save(test);
flowService.saveFlow(test);
}
}

@Service
public class FlowService {

@Autowired
private Mapper mapper;

@Transactional(propagation = Propagation.NESTED)
public void saveFlow(Test test) {
mapper.save(test);
throw new RuntimeException();
}
}
1
2
3
4
5
6
7
8
9
@Transactional
public void add(Test test) throws Exception {
mapper.save(test);
try {
flowService.saveFlow(test);
} catch (Exception e) {
log.error("save flow fail,message:{}",e.getMessage());
}
}

事务多线程调用:

Spring事务是基于线程绑定的,每个线程都有自己的事务上下文

  • 而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务失效。

Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class Service {

@Autowired
private Mapper1 mapper1;

@Mapper1
private FlowService flowService;

@Transactional
public void add(Test test) {
mapper1.save(test);
//多线程调用
new Thread(() -> {
flowService.save(test);
}).start();
}
}

@Service
public class FlowService {

@Autowired
private Mapper1 mapper1;

@Transactional
public void save(Test test) {
mapper1.saveFlow(test);
}
}

异常被捕获并处理了,没有重新抛出:

事务中的异常已经被业务代码捕获并处理。

  • 而没有被正确地传播回事务管理器,事务将无法回滚。

spring事务方法中,当我们使用了try-catchcatch住异常,一定要重新把异常抛出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class ServiceImpl implements Service {

@Autowired
private Mapper1 mapper1;

@Autowired
private Mapper2 mapper2;

@Transactional
public void add(Test test) {
try {
mapper1.save();
mapper2.save();
} catch (Exception e) {
log.error("add error,id:{},message:{}", tianluo.getId(),e.getMessage());
//throw e;
}
}

}

手动抛了别的异常:

如果手动抛了Exception异常,但是是不会回滚的

  • 因为Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚
    • 除非,用rollbackFor属性指定配置。

@Transactional加不加RollbackFor=Exception.Class的区别

@Transactional只能回滚RuntimeExceptionRuntimeException下面的子类抛出的异常,不能回滚Exception异常。

如果需要支持回滚Exception异常请用@Transactional(rollbackFor = Exception.class)

这里如果是增删改的时候我建议都使用@Transactional(rollbackFor = Exception.class)