@Transactional 注解失效场景

1 篇文章 0 订阅

前言

今天早上同事去面试的时候被面试官问到了一个问题,就是@Transactional注解用在什么地方事务会失效,一时之间没有回答上来,因为我们在用这个注解的时候没有过多的去关系它到底在什么情况下会失效,所以今天我利用一点时间来跟大家分享一下@Transactional相关的知识。

对于开发人员来说 @Transactional 已经不陌生了,当我们需要保证数据执行前后都要一致的时候就需要用到事务,它可以保证方法中数据库操作要么同时成功、要么同时失败,使用 @Transactional 有很多需要注意的地方,不然会发现 @Transactional 为什么会失效了。

事务

Spring 提供了很好的事务管理机制,Spring 事务管理分为编码式和声明式的两种方式。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。 本文介绍的是基于 @Transactional 注解的事务管理。

	@Transactional(rollbackFor = Exception.class)
	public void transactional(String msg) throws Exception{
			System.out.println("执行事务操作");
}

@Transactional 作用接口、类、类方法

1、作用于类:当把 @Transactional 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。

2、作用于方法:当类配置了 @Transactional ,方法也配置了 @Transactional,方法的事务会覆盖类的事务配置信息。

3、作用于接口:不推荐这种使用方法,因为一旦标注在 Interface 上并且配置了Spring AOP 使用 CGLib 动态代理,将会导致 @Transactional 注解失效。

@Transactional 属性

propagation 属性

1、Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )

2、Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

3、Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

4、Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用

5、Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

6、Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

7、Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

8、Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

isolation 属性

1、isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。

2、Isolation.DEFAULT:使用底层数据库默认的隔离级别。

3、Isolation.READ_UNCOMMITTED:读未提交数据

4、Isolation.READ_COMMITTED:读已提交数据

5、Isolation.REPEATABLE_READ:可重复读

6、Isolation.SERIALIZABLE:串型化

timeout 属性

timeout :事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

rollbackFor :用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔,例如:
@Transactional(rollbackFor==Exception.class)

@Transactional(rollbackFor={Exception.class, IOException.class})

noRollbackFor 属性

noRollbackFor:抛出 no-rollback-for 指定的异常类型,不回滚事务,例如:
@Transactional(noRollbackFor=Exception.class)

@Transactional(noRollbackFor={Exception.class, IOException.class})

@Transactional 失效场景

1、底层数据库引擎不支持事务

如果数据不支持事务,则 Spring 自然无法支持事务。例如我们常用的 MySQL,如果引擎采用的是MyISAM,是不支持事务操作的。改成 InnoDB 就可以了。

2、常被 catch 掉

1、在整个事务方法中使用 try-catch,导致异常无法抛出,自然会导致事务失效。代码如下:

@Transactional
public void test(){
    try {
        //逻辑操作数据
    }catch (Exception e){
        return;
    }
}

解决方法:
1、异常原样抛出

在 catch 块添加 throw new RuntimeException(e);

2、手动设置 TransactionStatus.setRollbackOnly()

在 catch 块添加 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

2、调用事务

3、方法中调用同类方法

事务的管理是通过代理执行的方式生效的,如果是方法内部调用,将不会走代理逻辑,也就调用不到,例如:

@Transactional(rollbackFor = Exception.class)
	@Transactional(rollbackFor = Exception.class)
	public void createUser(){
		this.createUser1();
		throw new RuntimeException();
	}

	@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
	public void createUser1(){
		User user = new User();
		user.setAccount("123456");
		user.setRealName("阿三");
		user.setSex(1);
		user.setBirthday(new Date());
		save(user);
	}

在 createUser 中调用了内部方法 createUser1,并且 createUser1 方法上设置了事务传播策略为:REQUIRES_NEW,但是因为是内部直接调用,createUser1 不能被代理处理,无法进行事务管理。在createUser1 方法抛出异常后就插入数据失败了。

解决方法:
1、在当前类注入自己,调用 createUser1 时通过注入的 userService 调用

@Autowired
	private UserService userService; // 本质上是一种循环依赖

	@Transactional(rollbackFor = Exception.class)
	public void createUser(){
		userService.createUser1();
		throw new RuntimeException();
	}

	@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
	public void createUser1(){
		User user = new User();
		user.setAccount("123456");
		user.setRealName("阿三");
		user.setSex(1);
		user.setBirthday(new Date());
		save(user);
	}

2、通过 AopContext.currentProxy() 获取代理对象

@Transactional(rollbackFor = Exception.class)
	public void createUser(){
		((UserService) AopContext.currentProxy()).createUser1();
		throw new RuntimeException();
	}

	@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
	public void createUser1(){
		User user = new User();
		user.setAccount("123456");
		user.setRealName("阿三");
		user.setSex(1);
		user.setBirthday(new Date());
		save(user);
	}

3、@Transactional 应用在非 public 修饰的方法上

如果 @Transactional 注解应用在非 public 修饰的方法上,@Transactional 将会失效,下面我们在标有 @Transactional 的任意方法上打个断点,在 idea 内能看到事务切面点如下图所示:

在这里插入图片描述
点击去这个方法,在方法里面点击红框中的方法:

在这里插入图片描述
继续点击红框中的方法:
在这里插入图片描述就能看到如下红框中的这么一句话,不支持非 public 修饰的方法进行事务管理。
在这里插入图片描述

4、rollbackFor 属性设置错误

源码中已经有注释说明清楚,如下图:
在这里插入图片描述
默认情况下事务仅回滚运行时异常和 Error,不回滚检查异常(例如 IOException),因此如果方法中抛出了 IO 异常,默认情况下事务也会回滚失败。我们可以通过指定 @Transactional(rollbackFor = Exception.class) 的方式进行全异常捕获。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@Transactional注解Spring框架中用于开启事务的注解,但是在某些情况下,@Transactional注解可能会失效,导致事务无法正常工作。以下是一些可能导致@Transactional注解失效场景: 1. 在同一个类中的两个@Transactional方法之间的调用:如果在同一个类中的两个@Transactional方法之间进行调用,那么事务注解将被忽略,因为Spring无法拦截这样的调用。 2. 异常被catch后没有重新抛出:如果在@Transactional方法中捕获了异常并在catch块中处理了它,但是没有重新抛出异常,那么事务将被提交,而不是回滚。 3. 事务方法中使用了try-catch块:如果在@Transactional方法中使用了try-catch块,并且在catch块中处理了异常,那么事务将被提交,而不是回滚。 4. 事务方法中使用了ThreadLocal:如果在@Transactional方法中使用了ThreadLocal,那么事务将被提交,而不是回滚。 5. 事务方法中使用了static方法:如果在@Transactional方法中使用了static方法,那么事务将被提交,而不是回滚。 6. 事务方法中使用了private方法:如果在@Transactional方法中使用了private方法,那么事务将被提交,而不是回滚。 7. 事务方法中使用了同步方法:如果在@Transactional方法中使用了同步方法,那么事务将被提交,而不是回滚。 8. 事务方法中使用了非公共方法:如果在@Transactional方法中使用了非公共方法,那么事务将被提交,而不是回滚。 9. 事务方法中使用了final方法:如果在@Transactional方法中使用了final方法,那么事务将被提交,而不是回滚。 10. 事务方法中使用了接口默认方法:如果在@Transactional方法中使用了接口默认方法,那么事务将被提交,而不是回滚。 11. 事务方法中使用了lambda表达式:如果在@Transactional方法中使用了lambda表达式,那么事务将被提交,而不是回滚。 12. 事务方法中使用了异步方法:如果在@Transactional方法中使用了异步方法,那么事务将被提交,而不是回滚。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值