Spring中事务失效的场景
创始人
2024-03-22 20:23:13

文章目录

  • 1 抛出检查异常导致事务不能正确回滚
    • 1.1 异常演示
    • 1.2 解决办法
  • 2 业务方法内自己 try-catch 异常导致事务不能正常回滚
    • 1.1 异常演示
    • 1.2 解决办法
  • 3 aop切面顺序导致事务不能正确回滚
    • 3.1 异常演示
    • 3.2 解决办法
  • 4 非 public 方法导致事务的失效
    • 4.1 异常演示
    • 4.2 解决办法
  • 5 父子容器导致的事务失效
    • 5.1 异常演示
    • 5.2 解决办法
  • 6 调用本类方法导致传播行为失效
    • 6.1 异常演示
    • 6.2 解决办法
  • 7 @Transactional 没有保证原子行为
    • 7.1 异常演示
    • 7.2 解决办法
  • 8 @Transactional 方法导致的 synchronized 失效

Spring中事务失效大致有以下八种场景

导入数据源

drop table if exists account;
create table account
(accountNo int primary key auto_increment,balance   int not null
);insert into account (accountNo, balance)
values (1, 1000),(2, 1000);

1 抛出检查异常导致事务不能正确回滚

并不是任何异常情况下,spring都会回滚事务,默认情况下,RuntimeException和Error的情况下,spring事务才会回滚。

原因:

  • Spring默认只会回滚非检查异常(运行时异常和Error)

解法:

  • 配置rollbackFor属性

1.1 异常演示

 @Transactionalpublic void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);//异常new FileInputStream("aaa");accountMapper.update(to, amount);}}

在这里插入图片描述
可见事务并未回滚

1.2 解决办法

在这里插入图片描述

 @Transactional(rollbackFor = Exception.class)public void transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);//异常new FileInputStream("aaa");accountMapper.update(to, amount);}}

在这里插入图片描述
在这里插入图片描述

可见事务是进行了回滚的,数据库的数据也并未改变

2 业务方法内自己 try-catch 异常导致事务不能正常回滚

原因:

  • 事务通知只有捕捉到了目标异常抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉

解法1:异常原样抛出

解法2:手动设置 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

1.1 异常演示

 @Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount)  {try {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}} catch (FileNotFoundException e) {e.printStackTrace();}}

在这里插入图片描述
可见事务还是进行了提交,并未回滚

1.2 解决办法

@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount)  {try {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);new FileInputStream("aaa");accountMapper.update(to, amount);}} catch (FileNotFoundException e) {e.printStackTrace();
//            TransactionInterceptor.currentTransactionStatus().setRollbackOnly();throw new RuntimeException(e);}}

抛出异常或者手动设置事务状态进行回滚

在这里插入图片描述

3 aop切面顺序导致事务不能正确回滚

原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常,事务就无法回滚

解法1:异常原样抛出

解法2:手动设置 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3.1 异常演示

在这里插入图片描述

 @Aspectstatic class MyAspect {@Around("execution(* transfer(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {LoggerUtils.get().debug("log:{}", pjp.getTarget());try {return pjp.proceed();} catch (Throwable e) {e.printStackTrace();return null;}}}

3.2 解决办法

抛出异常或者手动设置事务状态进行回滚
在这里插入图片描述

4 非 public 方法导致事务的失效

原因:

  • Spring为方法创建代理、添加事务通知、前提条件都是该方法是 pubic 的

解决办法:

  • 改为public方法

4.1 异常演示

 @Transactionalvoid transfer(int from, int to, int amount) throws FileNotFoundException {int fromBalance = accountMapper.findBalanceBy(from);if (fromBalance - amount >= 0) {accountMapper.update(from, -1 * amount);accountMapper.update(to, amount);}}

在这里插入图片描述
无事务的开启和提交

4.2 解决办法

@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {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")
public class WebConfig {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
@ComponentScan("day04.tx.app.service")
@MapperScan("day04.tx.app.mapper")
public class AppConfig {@ConfigurationProperties("jdbc")@Beanpublic DataSource dataSource() {return new HikariDataSource();}@Beanpublic DataSourceInitializer dataSourceInitializer(DataSource dataSource, DatabasePopulator populator) {DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();dataSourceInitializer.setDataSource(dataSource);dataSourceInitializer.setDatabasePopulator(populator);return dataSourceInitializer;}@Beanpublic DatabasePopulator databasePopulator() {return new ResourceDatabasePopulator(new ClassPathResource("account.sql"));}@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);return factoryBean;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}}

然后我们加载的时候将两个配置都加载

 public static void main(String[] args) throws FileNotFoundException {GenericApplicationContext parent = new GenericApplicationContext();AnnotationConfigUtils.registerAnnotationConfigProcessors(parent.getDefaultListableBeanFactory());ConfigurationPropertiesBindingPostProcessor.register(parent.getDefaultListableBeanFactory());parent.registerBean(AppConfig.class);parent.refresh();GenericApplicationContext child = new GenericApplicationContext();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 异常演示

@Service
public class Service6 {@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() throws FileNotFoundException {LoggerUtils.get().debug("foo");System.out.println(this.getClass());bar();}@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)public void bar() throws FileNotFoundException {LoggerUtils.get().debug("bar");}
}

在这里插入图片描述
由运行结果结果可知:

事务只开启了一次,并且打印的this的类型不为代理类

6.2 解决办法

总的来说就是要获取代理类来调用被事务增强的方法才可以保证事务不失效

@Service
public class Service6 {@Autowiredprivate Service6 proxy;@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void foo() 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)public void bar() 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 = new CountDownLatch(2);
new MyThread(() -> {bean.transfer(1, 2, 1000);latch.countDown();}, "t1", "boldMagenta").start();new MyThread(() -> {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

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...