文章目录
Spring中事务失效大致有以下八种场景
导入数据源
droptableifexists account;createtable account
(
accountNo intprimarykeyauto_increment,
balance intnotnull);insertinto account (accountNo, balance)values(1,1000),(2,1000);
1 抛出检查异常导致事务不能正确回滚
并不是任何异常情况下,spring都会回滚事务,默认情况下,RuntimeException和Error的情况下,spring事务才会回滚。
原因:
- Spring默认只会回滚非检查异常(运行时异常和Error)
解法:
- 配置rollbackFor属性
1.1 异常演示
@Transactionalpublicvoidtransfer(int from,intto,int amount)throwsFileNotFoundException{int fromBalance = accountMapper.findBalanceBy(from);if(fromBalance - amount >=0){
accountMapper.update(from,-1* amount);//异常newFileInputStream("aaa");
accountMapper.update(to, amount);}}
可见事务并未回滚
1.2 解决办法
@Transactional(rollbackFor =Exception.class)publicvoidtransfer(int from,intto,int amount)throwsFileNotFoundException{int fromBalance = accountMapper.findBalanceBy(from);if(fromBalance - amount >=0){
accountMapper.update(from,-1* amount);//异常newFileInputStream("aaa");
accountMapper.update(to, amount);}}
可见事务是进行了回滚的,数据库的数据也并未改变
2 业务方法内自己 try-catch 异常导致事务不能正常回滚
原因:
- 事务通知只有捕捉到了目标异常抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解法1:异常原样抛出
解法2:手动设置
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
1.1 异常演示
@Transactional(rollbackFor =Exception.class)publicvoidtransfer(int from,intto,int amount){try{int fromBalance = accountMapper.findBalanceBy(from);if(fromBalance - amount >=0){
accountMapper.update(from,-1* amount);newFileInputStream("aaa");
accountMapper.update(to, amount);}}catch(FileNotFoundException e){
e.printStackTrace();}}
可见事务还是进行了提交,并未回滚
1.2 解决办法
@Transactional(rollbackFor =Exception.class)publicvoidtransfer(int from,intto,int amount){try{int fromBalance = accountMapper.findBalanceBy(from);if(fromBalance - amount >=0){
accountMapper.update(from,-1* amount);newFileInputStream("aaa");
accountMapper.update(to, amount);}}catch(FileNotFoundException e){
e.printStackTrace();// TransactionInterceptor.currentTransactionStatus().setRollbackOnly();thrownewRuntimeException(e);}}
抛出异常或者手动设置事务状态进行回滚
3 aop切面顺序导致事务不能正确回滚
原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常,事务就无法回滚
解法1:异常原样抛出
解法2:手动设置
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
3.1 异常演示
@AspectstaticclassMyAspect{@Around("execution(* transfer(..))")publicObjectaround(ProceedingJoinPoint pjp)throwsThrowable{LoggerUtils.get().debug("log:{}", pjp.getTarget());try{return pjp.proceed();}catch(Throwable e){
e.printStackTrace();returnnull;}}}
3.2 解决办法
抛出异常或者手动设置事务状态进行回滚
4 非 public 方法导致事务的失效
原因:
- Spring为方法创建代理、添加事务通知、前提条件都是该方法是 pubic 的
解决办法:
- 改为public方法
4.1 异常演示
@Transactionalvoidtransfer(int from,intto,int amount)throwsFileNotFoundException{int fromBalance = accountMapper.findBalanceBy(from);if(fromBalance - amount >=0){
accountMapper.update(from,-1* amount);
accountMapper.update(to, amount);}}
无事务的开启和提交
4.2 解决办法
@Transactionalpublicvoidtransfer(int from,intto,int amount)throwsFileNotFoundException{int fromBalance = accountMapper.findBalanceBy(from);if(fromBalance - amount >=0){
accountMapper.update(from,-1* amount);
accountMapper.update(to, amount);}}
5 父子容器导致的事务失效
原因:
- 子容器扫描范围过大,把未加事务的配置的 service 扫描进来
解法:
- 各扫各的,不要图简便
- 不要用父子容器,所有bean放在同一容器(
SpringBoot项目就只有一个容器,不会发生该情况
)
5.1 异常演示
以下是两个配置类:
@ComponentScan("day04.tx.app")publicclassWebConfig{}
@Configuration@PropertySource("classpath:jdbc.properties")@EnableTransactionManagement@EnableAspectJAutoProxy(exposeProxy =true)@ComponentScan("day04.tx.app.service")@MapperScan("day04.tx.app.mapper")publicclassAppConfig{@ConfigurationProperties("jdbc")@BeanpublicDataSourcedataSource(){returnnewHikariDataSource();}@BeanpublicDataSourceInitializerdataSourceInitializer(DataSource dataSource,DatabasePopulator populator){DataSourceInitializer dataSourceInitializer =newDataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
dataSourceInitializer.setDatabasePopulator(populator);return dataSourceInitializer;}@BeanpublicDatabasePopulatordatabasePopulator(){returnnewResourceDatabasePopulator(newClassPathResource("account.sql"));}@BeanpublicSqlSessionFactoryBeansqlSessionFactory(DataSource dataSource){SqlSessionFactoryBean factoryBean =newSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);return factoryBean;}@BeanpublicPlatformTransactionManagertransactionManager(DataSource dataSource){returnnewDataSourceTransactionManager(dataSource);}}
然后我们加载的时候将两个配置都加载
publicstaticvoidmain(String[] args)throwsFileNotFoundException{GenericApplicationContext parent =newGenericApplicationContext();AnnotationConfigUtils.registerAnnotationConfigProcessors(parent.getDefaultListableBeanFactory());ConfigurationPropertiesBindingPostProcessor.register(parent.getDefaultListableBeanFactory());
parent.registerBean(AppConfig.class);
parent.refresh();GenericApplicationContext child =newGenericApplicationContext();AnnotationConfigUtils.registerAnnotationConfigProcessors(child.getDefaultListableBeanFactory());
child.setParent(parent);
child.registerBean(WebConfig.class);
child.refresh();AccountController bean = child.getBean(AccountController.class);
bean.transfer(1,2,500);}
5.2 解决办法
放在一个容器里面或者各扫各的
6 调用本类方法导致传播行为失效
原因:
- 本类方法调用不经过代理,因此无法增强
解法:
- 依赖注入自己(代理)来调用
- 通过AopContext拿到代理对象,来调用
6.1 异常演示
@ServicepublicclassService6{@Transactional(propagation =Propagation.REQUIRED, rollbackFor =Exception.class)publicvoidfoo()throwsFileNotFoundException{LoggerUtils.get().debug("foo");System.out.println(this.getClass());bar();}@Transactional(propagation =Propagation.REQUIRES_NEW, rollbackFor =Exception.class)publicvoidbar()throwsFileNotFoundException{LoggerUtils.get().debug("bar");}}
由运行结果结果可知:
事务只开启了一次,并且打印的this的类型不为代理类
6.2 解决办法
总的来说就是要获取代理类来调用被事务增强的方法才可以保证事务不失效
@Service
publicclassService6{
@Autowired
privateService6 proxy;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)publicvoidfoo()throws FileNotFoundException {
LoggerUtils.get().debug("foo");
System.out.println(proxy.getClass());// System.out.println(this.getClass());
proxy.bar();}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)publicvoidbar()throws FileNotFoundException {
LoggerUtils.get().debug("bar");}}
由运行结果可知,事务开启了两次,并且打印的也是代理对象
另一种方法同理,只不过要在配置时加上
@EnableAspectJAutoProxy(exposeProxy = true)
,暴露对象才可以获取
((Service6)AopContext.currentProxy()).bar();
7 @Transactional 没有保证原子行为
原因:
- 事务的原子性仅覆盖
[insert、update、delete、select] ... for update
语句,select方法并不阻塞
7.1 异常演示
CountDownLatch latch =newCountDownLatch(2);newMyThread(()->{
bean.transfer(1,2,1000);
latch.countDown();},"t1","boldMagenta").start();newMyThread(()->{
bean.transfer(1,2,1000);
latch.countDown();},"t2","boldBlue").start();
latch.await();System.out.println(bean.findBalance(1));
可知数据库的数据已经出现了问题
7.2 解决办法
- synchronized 范围应扩大至代理方法调用
- 使用
select ... for update
替换 select
select balance from account where accountNo=#{accountNo} for update
8 @Transactional 方法导致的 synchronized 失效
原因:
- synchronized保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,他们并不处于 sync 块内
解法:
- synchronized 范围应扩大至代理方法调用
- 使用
select ... for update
替换 select
版权归原作者 似梦的苏烟 所有, 如有侵权,请联系我们删除。