聊聊Spring事务失效场景!
发表于更新于
框架Spring聊聊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
的话,spring
的computeTransactionAttribute()
就会返回null。
RollbackFor
属性配置错误:
rollbackFor
属性指定的异常必须是Throwable
或者其子类。
- 默认情况下,
RuntimeException
和Error
两种异常都是会自动回滚的。
代码例子:
- 指定了
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(); }
|
事务注解被覆盖导致事务失效:
MyTestService
是MyService
的子类,并且覆盖了doSomething()
方法。
- 在该方法中,使用了不同的传播行为
(REQUIRES_NEW)
来覆盖父类的@Transactional
注解。
在这种情况下,当调用MyTestService
的doSomething()
方法时
- 由于子类方法中的注解覆盖了父类的注解,
Spring
框架将不会在父类的方法中启动事务。
因此,当MyRepository
的save()
方法被调用时,事务将不会被启动,也不会回滚。
- 这将导致数据不一致的问题,因为在
MyRepository
的save()
方法中进行的数据库操作将不会回滚。
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
的方法。
如果不想因为被内部嵌套的事务影响,可以用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-catch
,catch
住异常,一定要重新把异常抛出来。
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()); } }
}
|
手动抛了别的异常:
如果手动抛了Exception
异常,但是是不会回滚的
- 因为Spring默认只处理
RuntimeException和Error
,对于普通的Exception
不会回滚
@Transactional加不加RollbackFor=Exception.Class的区别
@Transactional
只能回滚RuntimeException
和RuntimeException
下面的子类抛出的异常,不能回滚Exception
异常。
如果需要支持回滚Exception
异常请用@Transactional(rollbackFor = Exception.class)
。
这里如果是增删改的时候我建议都使用@Transactional(rollbackFor = Exception.class)
。