聊⼀聊Spring中@Transactional注解及其失效的七种场景
⽂章⽬录
说明:当我准备写我知道的那⼏个场景时,我发现有⼈⽐我写的更好,关键是好得多,于是我就⽤了这位博主的⽂章,由于不是⼀个平台我就直接将有些内容复制加以修改,并附上⾃⼰的理解,感谢这位博主!他地址:
前置知识:
1、Spring的事务⽀持是由AOP(⾯向切⾯编程)这⼀思想做⽀撑,然⽽⾯向切⾯编程底层的落地实现是Spring⼯⼚的动态代理,⽽Spring底层⼜对JDK或cgLib这两种实现动态代理的⽅式做了封装,Spring可以利⽤AOP思想实现事务、⽇志、性能监控、接⼝限流。
代理的作⽤或好处:
作⽤:为原始对象增加额外功能;
好处:解耦合、便于原始对象的维护。
2、什么是代理:代理就相当于租房中介,他为房东提供发放⼴告和提供看房的额外功能。
3、什么是代理类:代理类就是为原始类添加额外功能的类,这个类是由JVM通过动态字节码技术⽣成的。
4、什么是动态字节码技术:是利⽤类加载器动态的在JVM中,由原始类和额外功能的字节码⾃动⽣成代理类的字节码,从⽽使⽤在堆空间中创建Class对象。
5、代理实现的三要素:①提供原始对象(为其增加额外功能);②额外功能;③要求代理类要与原始类拥有相同的⽅法(可以是实现其接⼝,也可以继承)
6、JDK提供动态代理:是利⽤Proxy类中的⼀个静态⽅法newProxyInstance创建代理类对象
// 1、创建原始对象
UserServiceImpl userService =new UserServiceImpl();
// 2、提供额外功能
InvocationHandler handler =new InvocationHandler(){
/** @param proxy 代理实例,⼀般不会⽤到
* @param method 原始⽅法
* @param args 原始⽅法的参数
* @return 原始对象返回值 */
@Override
public Object invoke(Object proxy, Method method, Object[] args){
System.out.println("在原始⽅法执⾏之前执⾏的逻辑 -- 额外功能");// 2.1 这其实就是Spring提供的前置通知-MethodBeforeAdvice
Object invoke =null;
try{
invoke = method.invoke(userService, args);// 调⽤原始⽅法,执⾏原始⽅法逻辑
}catch(Exception e){
System.out.println("在原始⽅法执⾏报错后执⾏的逻辑 -- 额外功能");// 2.2 这其实就是Spring提供的异常通知-ThrowsAdvice }
System.out.println("在原始⽅法执⾏之后执⾏的逻辑 -- 额外功能");// 2.3 这其实就是Spring提供的后置通知-AfterReturningAdvice return invoke;//
}
spring aop应用场景};
// 创建代理对象
UserServiceImpl userServiceProxy =(wProxyInstance(Thread.currentThread
().getContextClassLoader(),
handler);
userServiceProxy.save(new User());// 使⽤代理对象调⽤原始类中的⽅法,在执⾏此⽅法时会按照代理对象的逻辑执⾏额外功能
7、newProxyInstance⽅法的三个参数说明:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
① loader类加载器:利⽤该类加载器去⽣成字节码,创建Class对象;
② interfaces接⼝集合:这就是为了使⽣成的代理类和原始类的⽅法⼀致;
③ InvocationHandler额外功能接⼝的接⼝:额外功能的逻辑
利⽤这三个参数就可以创建代理类对象,cgLib的实现与以上步骤⾼度吻合,区别就是cgLib是使⽤继承原始类的⽅式去让代理类与原始类⽅法相同的。
Spring对JDK动态代理的封装:
Spring就是利⽤JDK的动态代理来实现AOP思想的,它封装之后提供最重要的接⼝叫MethodInterceptor.
public class proxyTest implements MethodInterceptor {
/**
* @param methodInvocation 原始对象
* @return 原始对象返回值
* @throws Throwable 异常处理
*/
@Override
public Object invoke(MethodInvocation methodInvocation)throws Throwable {
System.out.println("之前");
Object proceed = methodInvocation.proceed();// 调⽤原始⽅法,执⾏原始⽅法逻辑
System.out.println("之后");
return proceed;
}
}
你会发现,Spring提供的这个MethodInterceptor 接⼝的实现⽅法invoke和代理newProxyInstance⽅法⾼度吻合,Spring就是把这个⽅法给封装了,但是最底层还是完全⼀样的。
那么,现在我们再来看Spring的事务。
⼀、事务(基于AOP)
1、事务: 事务是⽤来保证业务的完整性的⼀种数据库机制,在数据库层⾯它是⼀组sql的集合,要保证这组sql集合要么同时发⽣,要么同时失败,以此来保证数据的安全和⼀致。
2、事务管理在系统开发中是不可缺少的⼀部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。
(1)编程式事务:是指在代码中⼿动的管理事务的提交、回滚等操作,其中也会⽤到try-catch。
缺点:代码侵⼊性⽐较强,如下⽰例:
try{
//TODO something
transactionManagermit(status);
}catch(Exception e){
throw new InvoiceApplyException("异常");
}
其实这个⽅式,我觉得Spring封装的⼒度并不是很⼤,只是利⽤Spring为我们sqlSession以及创建了DAO对象。还没有⽤到Spring提供的事务封装(事务封装:封装动态代理的切⼊点和连接connection),还是由我们⼿动调⽤connection连接的commit和rollback。
(2)声明式事务:基于AOP⾯向切⾯的,它将具体业务与事务处理部分解耦,代码侵⼊性很低,所以
在实际开发中声明式事务⽤的⽐较多。
声明式事务也有两种实现⽅式,⼀是基于tx和AOP的xml配置⽂件⽅式,⼆是基于@Transactional注解。
<!--事务--额外功能-->
<bean id="dataSourceTransactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--组装切⾯--组装切⼊点+额外功能(也就是让Spring知道哪⼀个包、类、⽅法要是哟个和哪⼀个额外功能,
此时的额外功能是事务的⽀持)-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<aop:aspectj-autoproxy proxy-target-class="false"/>
⼆、@Transactional介绍
1、@Transactional注解可以作⽤于哪些地⽅?
@Transactional 可以作⽤在接⼝、类、类⽅法。
(1)作⽤于类:当把@Transactional 注解放在类上时,表⽰所有该类的 public ⽅法 都配置相同的事务属性信息。
(2)作⽤于⽅法:当类配置了@Transactional,⽅法也配置了@Transactional,⽅法的事务会 覆盖类的事务配置信息。
(3)作⽤于接⼝:不推荐这种使⽤⽅法,因为⼀旦标注在Interface上并且配置了Spring AOP 使⽤CGLib动态代理,将会导致
@Transactional注解失效
2、@Transactional注解有哪些属性?
@RestController
publicclass MybatisPlusController {
@Autowired
private CityInfoDictMapper cityInfoDictMapper;
@Transactional( isolation = Isolation.DEFAULT,
propagation = Propagation.SUPPORTS, timeout =2,
readOnly =true, rollbackFor = RuntimeException.class)
@GetMapping("/test")
public String test()throws Exception {
return"";
}
}
(1)isolation 属性
isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。这个事务的隔离级别可以理解成Mysql中的事务隔离级别。
TransactionDefinition.ISOLATION_DEFAULT :使⽤数据库默认的隔离级别,Mysql 默认采⽤的 REPEATABLE_READ(不可重复读)隔离级别, Oracle 默认采⽤的 READ_COMMITTED(读已提交)隔离级别.
①TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
②TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣
③TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本⾝事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。
④TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别(是因为这样的效率很低,并发性⼤⼤的降低,⽽且很多时候也没有必要去解决幻读)。
(2)propagation属性
propagation 代表事务的传播⾏为**,默认值为 Propagation.REQUIRED,⼀共是7种前⾯三种得掌握。其他的属性信息如下:**
①Propagation.REQUIRED:如果当前存在事务,则加⼊该事务,如果当前不存在事务,则创建⼀个新的事务。( 也就是说如果A⽅法和B⽅法都添加了注解,在默认传播模式下,A⽅法内部调⽤B⽅法,会把两个⽅法的事务合并为⼀个事务 )。注意:这适⽤于增删改.
②Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前不存在事务,则以⾮事务的⽅式继续运⾏。(有事务就继续执⾏当前事务,要是开启事务也会去创建事务,⽽是继续以没有事务的⽅式去执⾏任务。)注意:这适⽤于查询.
③Propagation.REQUIRES_NEW:重新创建⼀个新的事务,如果当前存在事务,挂起当前的事务。( A⽅法有事务,去调⽤没有事务的B⽅法,那么B⽅法就会去创建⼀个新的事务,然后将A的事务挂起,去执⾏B的事务。
④Propagation.MANDATORY:如果当前存在事务,则加⼊该事务;如果当前不存在事务,则抛出异常。
⑤Propagation.REQUIRED模式,类B中的 b⽅法加上采⽤ Propagation.REQUIRES_NEW模式,然后在 a ⽅法中调⽤ b⽅法操作数据库,然⽽ a⽅法抛出异常后,b⽅法并没有进⾏回滚,因为Propagation.REQUIRES_NEW会暂停 a⽅法的事务 )
⑥Propagation.NOT_SUPPORTED:以⾮事务的⽅式运⾏,如果当前存在事务,暂停当前的事务。
⑦Propagation.NEVER:以⾮事务的⽅式运⾏,如果当前存在事务,则抛出异常。
Propagation.NESTED :和 Propagation.REQUIRED 效果⼀样。
(3)timeout 属性
timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则⾃动回滚事务。
值得注意的是:这个属性⼀般都不会去设置。原因是在真实的环境中,由于⽹络环境和其他不确定性因素在⾥⾯,很难去确定⼀个时间段内哪⼀个事务是要超时的。
(4)readOnly 属性
readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的⽅法,⽐如读取数据,可以设置 read-only 为 true。
(5)rollbackFor 属性
rollbackFor :⽤于指定能够触发事务回滚的异常类型,可以指定多个异常类型。多个异常之间⽤逗号隔开。
(6)noRollbackFor属性
noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
三、@Transactional失效场景
接下来我们结合具体的代码分析⼀下哪些场景下,@Transactional 注解会失效。
1、没有将使⽤该注解的类交给Spring管理,也就是该对象不是bean对象。
在添加@Transactional 的注解的类交给Spring管理,要使⽤像@service类似的注解。
2、@Transactional 应⽤在⾮ public 修饰的⽅法上
如果@Transactional注解应⽤在⾮public 修饰的⽅法上,Transactional将会失效。
之所以会失效是因为在Spring AOP 代理时, TransactionInterceptor (事务)在⽬标⽅法执⾏前后进⾏拦
截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept ⽅法或 JdkDynamicAopProxy 的 invoke ⽅法会间接调⽤ AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute ⽅法,获取Transactional 注解的事务配置信息。(这句话当我没说,记住结论就⾏。)
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass){
// Don't allow no-public methods as required.
if(allowPublicMethodsOnly()&&!Modifier.Modifiers())){
return null;
}
此⽅法会检查⽬标⽅法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意:protected、private 修饰的⽅法上使⽤ @Transactional 注解,虽然事务⽆效,但不会有任何报错,这是我们很容犯错的⼀点。
⼩结:其实简单理解就是,⽐如通过cgLib的⽅式去⽣成代理类对象,它的底层原理是去继承原始类(或者说⽬标类),然⽽我们都知道,要被继承的⽅法是不能⽤⾮public修饰的,假如原始类中的⽬标⽅法被private修饰,那么该⽅法就不能被继承下去,spring使⽤动态代理就不能为其⽣成代理对象,⼜因为 @Transactional 注解标注的这个⽅法必须被代理类对象调⽤才可以⽣效以此被代理。
3、@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发⽣回滚。
(1)TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论