有意识的在涉及事务相关方法上加@Transactional注解,是个好习惯。不过,很多同学只是下意识地添加这个注解,一旦功能正常运行,很少有人会深入验证异常情况下事务是否能正确回滚。@Transactional 注解虽然用起来简单,但这货总是能在一些你意想不到的情况下失效,防不胜防!
我把这些事务问题归结成了三类:不必要、不生效、不回滚,接下用一些demo演示下各自的场景。
一:不必要
1、 无需事务的业务
在没有事务操作的业务方法上使用 @Transactional 注解,比如:用在仅有查询或者一些 HTTP 请求的方法,虽然加上影响不大,但从编码规范的角度来看还是不够严谨,建议去掉。
@Transactional
public String testQuery() {
standardBak2Service.getById(1L);
return "testB";
}
2、事务范围过大
有些同学为了省事直接将 @Transactional 注解加在了类上或者抽象类上,这样做导致的问题就是类内的方法或抽象类的实现类中所有方法全部都被事务管理。增加了不必要的性能开销或复杂性,建议按需使用,只在有事务逻辑的方法上加@Transactional。
@Transactional
public abstract class BaseService {
}
@Slf4j
@Service
public class TestMergeService extends BaseService{
private final TestAService testAService;
public String testMerge() {
testAService.testA();
return "ok";
}
}
如果在类中的方法上添加 @Transactional 注解,它将覆盖类级别的事务配置。例如,类级别上配置了只读事务,方法级别上的 @Transactional 注解也会覆盖该配置,从而启用读写事务
@Transactional(readOnly = true)
public class TestMergeService {
private final TestBService testBService;
private final TestAService testAService;
@Transactional
public String testMerge() {
testAService.testA();
testBService.testB();
return "ok";
}
}
二:不生效
3. 方法权限问题
不要把 @Transactional注解加在 private 级别的方法上!
我们知道 @Transactional 注解依赖于Spring AOP切面来增强事务行为,这个 AOP 是通过代理来实现的,而 private 方法恰恰不能被代理的,所以 AOP 对 private 方法的增强是无效的,@Transactional也就不会生效。
@Transactional
private String testMerge() {
testAService.testA();
testBService.testB();
return "ok";
}
那如果我在 testMerge() 方法内调用 private 的方法事务会生效吗?
答案:事务会生效
@Transactional
public String testMerge() throws Exception {
ccc();
return "ok";
}
private void ccc() {
testAService.testA();
testBService.testB();
}
4. 被用 final 、static 修饰方法
和上边的原因类似,被用 final
、static
修饰的方法上加 @Transactional 也不会生效。
-
static 静态方法属于类本身的而非实例,因此代理机制是无法对静态方法进行代理或拦截的
-
final 修饰的方法不能被子类重写,事务相关的逻辑无法插入到 final 方法中,代理机制无法对 final 方法进行拦截或增强。
这些都是java基础概念了,使用时要注意。
@Transactional
public static void b() {
}
@Transactional
public final void b() {
}
5. 同类内部方法调用问题
注意了,这种情况经常发生啊!
同类内部方法间的调用是 @Transactional 注解失效的重灾区,网上你总能看到方法内部调用另一个同类的方法时,这种调用是不会经过代理的,因此事务管理不会生效。但这说法比较片面,要分具体情况。
比如:testMerge() 方法开启事务,调用同类非事务的方法 a() 和 b() ,此时 b() 抛异常,根据事务的传播性 a()、b() 事务均生效。
@Transactional
public String testMerge() {
a();
b();
return "ok";
}
public void a() {
standardBakService.save(testAService.buildEntity());
}
public void b() {
standardBak2Service.save(testBService.buildEntity2());
throw new RuntimeException("b error");
}
如果 testMerge() 方法未开启事务,并且在同类中调用了非事务方法 a() 和事务方法 b(),当 b() 抛出异常时,a() 和 b() 的事务都不会生效。因为这种调用直接通过 this
对象进行,未经过代理,因此事务管理无法生效。这经常出问题的!
public String testMerge() {
a();
b();
return "ok";
}
public void a() {
standardBakService.save(testAService.buildEntity());
}
@Transactional
public void b() {
standardBak2Service.save(testBService.buildEntity2());
throw new RuntimeException("b error");
}
解决办法:
5.1 独立的 Service 类
要想 b() 方法的事务生效也容易,最简单的方法将它剥离放在独立的Service类注入使用,交给spring管理就行了。不过,这种方式会创建很多类
@Slf4j
@Service
public class TestBService {
@Transactional
public void b() {
standardBak2Service.save(testBService.buildEntity2());
throw new RuntimeException("b error");
}
}
5.2 自注入方式
或者通过自己注入自己的方式解决,尽管解决了问题,逻辑看起来很奇怪,它破坏了依赖注入的原则,虽然 spring 支持我们这样用,还是要注意下循环依赖的问题。
@Slf4j
@Service
public class TestMergeService {
@Autowired
private TestMergeService testMergeService;
pub