当前位置: 首页 > 图灵资讯 > 技术篇> spring事务注解@Transactional注解失效

spring事务注解@Transactional注解失效

来源:图灵教育
时间:2023-06-11 09:17:27

@Transactional 相信大家都很熟悉注释。它是开发中常用的一种注释,可以保证方法中多个数据库的操作同时成功或失败。使用@transactional注释时,需要注意很多细节,否则你会发现@transactional总是莫名其妙地失败

一、事务

Spring提供了良好的事务管理机制,主要分为编程事务和声明事务。

编程事务:指代码中手动管理事务的提交、回滚等操作,具有较强的代码侵入性,如下:

try {

//TODO something

transactionManager.commit(status);

} catch (Exception e) {

transactionManager.rollback(status);

throw new InvoiceApplyException(“异常失败”);

}

声明事务:基于AOP面向截面,它解耦了特定的业务和事务处理部分,代码侵入性很低,因此在实际开发中使用了更多的声明事务。实现声明事务也有两种方式,一种是基于TX和AOP的XML配置文件,另一种是基于@Transactional注释。

@Transactional

@GetMapping("/test")

public String test() {

int insert = cityInfoDictMapper.insert(cityInfoDict);

}

二、@Transactional介绍

1、@Transactional注释能在哪里发挥作用?

@Transactional 可作用于接口、类、类的方法。

作用于类:当把@Transactional 注解放在类上时,说明所有这类public方法都配置了相同的事务属性信息。

作用于方法:当类配置@Transactional,方法也配置好了@Transactional,该方法的事务将覆盖事务配置信息。

作用于接口:不建议使用这种方法,因为一旦标记在Interface上,配置Spring AOP 使用CGLib动态代理会导致@Transactional注释失败

@Transactional

@RestController

@RequestMapping

public class MybatisPlusController {

@Autowired

private CityInfoDictMapper cityInfoDictMapper;

@Transactional(rollbackFor = Exception.class)

@GetMapping("/test")

public String test() throws Exception {

CityInfoDict cityInfoDict = new CityInfoDict();

cityInfoDict.setParentCityId(2);

cityInfoDict.setCityName("2");

cityInfoDict.setCityLevel("2");

cityInfoDict.setCityCode("2");

int insert = cityInfoDictMapper.insert(cityInfoDict);

return insert + "";

}

}

2、@Transactional注释的属性是什么?

propagation属性

propagation 代表事务的传播行为默认值 Propagation.REQUIRED,其他属性信息如下:

Propagation.REQUIRED:如果目前有事务,则加入事务,如果目前没有事务,则创建新的事务。( 也就是说,如果A方法和B方法都添加注释,在默认传播模式下,A方法内部调用B方法将两种方法的事务合并为一种事务 )

Propagation.SUPPORTS:如果目前有事务,则加入事务;如果目前没有事务,则继续以非事务的形式运行。

Propagation.MANDATORY:若目前有事务,则加入事务;若目前没有事务,则抛出异常。

Propagation.REQUIRES_NEW:如果现在有事务,重新创建新的事务,暂停现在的事务。( 当类A中的 a 默认Propagation.类B中的REQUIRED模式 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 然而,B方法操作数据库, a方法抛出异常后,b方法没有回滚,因为Propagation.REQUIRES_NEW将暂停 a方法的事务 )

Propagation.NOT_SUPPORTED:以非事务的形式经营,如果当前事务存在,暂停当前事务。

Propagation.NEVER:以非事务的形式运行,如果目前有事务,则抛出异常。

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

isolation 属性

isolation :事务的隔离等级,默认值 Isolation.DEFAULT。

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

Isolation.READ_UNCOMMITTED

Isolation.READ_COMMITTED

Isolation.REPEATABLE_READ

Isolation.SERIALIZABLE

timeout 属性

timeout :事务的超时间,默认值 -1.如果超过时间限制但事务尚未完成,则自动回滚事务。

readOnly 属性

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

rollbackFor 属性

rollbackFor :指定可触发事务回滚的异常类型,可指定多种异常类型。

norolbackfor属性**

noRollbackFor:抛出指定的异常类型,不回滚事务,也可指定多种异常类型。

三、@Transactional故障场景

接下来,我们将结合具体的代码来分析哪些场景,@Transactional 注释会失效。

1、@Transactional 应用在非 public 修饰方法

如果Transactional注释应用于非public Transactional将在修改方法上失效。

之所以会失效,是因为Spring AOP 代理时,如上图所示 TransactionInterceptor (事务AOP)在实施对象方法前后拦截,DynamicAdvisedInterceptor(CglibAopProxy 内部类) intercept 方法或 JdkDynamicAopProxy 的 invoke 间接调用方法 Abstractfallbacktransactionattributesource computeTransactionAttribute 方法,Transaction 事务配置信息的注释。

protected TransactionAttribute computeTransactionAttribute(Method method,

Class targetClass) {

// Don't allow no-public methods as required.

if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {

return null;

}

该方法将检查目标方法的修饰符是否为 public,不是 public不会获得@Transactional 属性配置信息。

注意:protected、private 使用修饰方法 @Transactional 注意,虽然事务无效,但不会有任何错误,这是我们容易犯的错误。

2、@Transactional 注解属性 propagation 设置错误

这种失败是由于配置错误造成的。如果配置错误,有三种 propagation,事务不会回滚。

TransactionDefinition.PROPAGATION_SUPPORTS:如果目前有事务,则加入事务;如果目前没有事务,则继续以非事务的形式运行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务的形式运行,如果当前事务存在,则挂起当前事务。

TransactionDefinition.PROPAGATION_NEVER:以非事务的形式运行,如果目前有事务,则抛出异常。

3、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可指定能触发事务回滚的异常类型。Spring默认抛出未检查unchecked异常(继承自 Runtimeexception异常)或 只有Error才能回滚;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但期望 Spring 如果能够回滚,则需要指定 rollbackFor属性。

1// 希望自定义的异常可以回滚

2@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

在目标方法中抛出的异常是 rollbackFor 指定的异常子类,事务也会回滚。Spring源代码如下:

1private int getDepth(Class exceptionClass, int depth) {

2 if (exceptionClass.getName().contains(this.exceptionName)) {

3 // Found it!

4 return depth;

5}

6 // If we've gone as far as we can go and haven't found it...

7 if (exceptionClass == Throwable.class) {

8 return -1;

9}

十return getDepth(exceptionClass.getSuperclass(), depth + 1);

11}

4、在同一类中调用方法会导致@Transactional失效

在开发过程中,不可避免地会调用同一类的方法。例如,有一个类Test,它的一个方法A,然后调用此类方法B(无论方法B是用public还是private进行修改),但方法A没有声明解释事务,而B方法是。在外部调用方法A之后,方法B的事务将不起作用。这也是一个经常犯错误的地方。

那为什么会发生这种情况呢?其实这还是因为Spring的使用? AOP代理是由Spring生成的代理对象管理的,因为只有当事务方法被当前类以外的代码调用时。

//@Transactional

@GetMapping("/test")

private Integer A() throws Exception {

CityInfoDict cityInfoDict = new CityInfoDict();

cityInfoDict.setCityName("2");

/**

* B 插入字段为 3的数据

*/

int insert=insertB();

/**

* A 插入字段为 2的数据

*/

int insert = cityInfoDictMapper.insert(cityInfoDict);

return insert;

}

@Transactional()

public Integer insertB() throws Exception {

CityInfoDict cityInfoDict = new CityInfoDict();

cityInfoDict.setCityName("3");

cityInfoDict.setParentCityId(3);

return cityInfoDictMapper.insert(cityInfoDict);

}

5、异常被你的 catch“吃”导致@Transactional失效

这种情况是最常见的@Transactional注释失效场景

@Transactional

private Integer A() throws Exception {

int insert = 0;

try {

CityInfoDict cityInfoDict = new CityInfoDict();

cityInfoDict.setCityName("2");

cityInfoDict.setParentCityId(2);

/**

* A 插入字段为 2的数据

*/

insert = cityInfoDictMapper.insert(cityInfoDict);

/**

* B 插入字段为 3的数据

*/

b.insertB();

} catch (Exception e) {

e.printStackTrace();

}

}

如果B方法内部抛出异常,此时a方法try catchb方法异常,这件事还能正常回滚吗?

答:不能!

会抛出异常:

因为当ServiceB抛出异常时,ServiceB识别当前事务需要rollback。但在ServiceA中,ServiceA认为目前的事务应该是正常的commit,因为你手动捕获并处理这种异常。这时就出现了前后不一致,正因为如此,前面的Unexpectedrolbackexception被抛出。

spring的事务是在调用业务方法之前开始的,业务方法完成后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtimee。 exception 如果你的业务方法中没有catch,事务就会回滚。

在业务方法中,一般不需要catch异常。如果非要catch,必须抛出throww new RuntimeException()或在注释中指定异常类型的抛掷@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit会导致数据不一致,所以有时候try 相反,catch会画蛇添足。

6、数据库引擎不支持事务

这种情况的概率不高,数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换到不支持事务的myisam,事务将从根本上失败。

总结

@Transactional 注释看似简单易用,但如果对它的用法知之甚少,还是会踩很多坑。