SpringBoot中的事务处理问题
作者:龙域、白泽
介绍
在实际的业务场景中,不光数据库要实现事务,我们的Service层业务也要实现事务。
例如:要实现数据的删除,删除数据库中的数据并删除Redis缓存中的数据,他们在一个ServiceImpl的方法中,我们要实现这两个操作放在一个事务中,两个操作同时成功 / 失败,所有,要使用Spring事务。
SpringBoot中事务实现
ServiceImpl的方法 / ServiceImpl类 上加上@Transactional。
- 方法:注解只对public方法有效(因为@Transactional 工作原理是基于AOP实现的),如果在 protected、private 或者默认方法上使用,没有作用,也不报错。
- 类:该类的所有 public 方法将都具有该类型的事务属性
注意:在方法级别使用该注解可以覆盖类级别使用该注解。
// 这个类中的public方法如果抛出Exception,则该方法会回滚 @Transactional(rollbackFor = Exception.class) public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { // do something } // these settings have precedence for this method //方法上注解属性会覆盖类注解上的相同属性 @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateFoo(Foo foo) { // do something } }
@EnableTransactionManagement
SpringBoot启动类上加上不用加@EnableTransactionManagement注解,SpringBoot自动装配已经帮我们处理了,SpringBoot项目默认支持事务。
@Transactional
作用位置:接口、接口方法、类以及类方法
Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
注意:
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
Transactional类
package org.springframework.transaction.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; import org.springframework.transaction.TransactionDefinition; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
注解的属性
属性 | 类型 | 描述 |
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
在Spring中定义了五种隔离级别常量
一般用默认就可以。
package org.springframework.transaction.annotation; import org.springframework.transaction.TransactionDefinition; public enum Isolation { DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); private final int value; Isolation(int value) { this.value = value; } public int value() { return this.value; } }
常量 | 说明 |
---|---|
TransactionDefinition.ISOLATION_DEFAULT | 数据库默认的隔离级别,MySQL默认采用的 REPEATABLE_READ隔离级别 |
TransactionDefinition.ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取未提交的数据变更,可能会导致脏读、幻读或不可重复读。 |
TransactionDefinition.ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。 |
TransactionDefinition.ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL中通过MVCC解决了该隔离级别下出现幻读的可能。 |
TransactionDefinition.ISOLATION_SERIALIZABLE | 串行化隔离级别,该级别可以防止脏读、不可重复读以及幻读,但是串行化会影响性能。 |
Spring定义了七种事务传播行为
类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
Spring事务什么情况下会失效
【1】@Transactional 应用在非 public 修饰的方法上
@Transactional 工作原理是基于AOP实现的
所以,注解必须作用在public的方法上,否则失效。
【2】数据库引擎是否支持事务(MySql的MyIsam引擎不支持事物)
【3】@Transactional注解属性 propagation 设置错误
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异。
【4】@Transactional 注解属性 rollbackFor 设置错误
- Spring默认是回滚RuntimeException才回滚。
- 当然自定义的RuntimeException异常类也是可以的。
- 如果希望Spring能够回滚别类型的异常,那就需要使用rollbackFor去指定(当然如果是指定异常的子类,也同样会回滚)。
【5】同一个类中方法调用,导致@Transactional失效
在同一个类中,没有加事务的方法A调用加事务的方法B,方法B的事务失效。
( 其实这还是由于使用Spring AOP代理造成的),所以:建议把整个类加上事务注解。
【6】异常被捕获了
这个就比较简单了,就是你自己捕捉到了异常,并且自己处理,并不会抛出到上层的方法调用。
那就不会生效了。
@Transactional public void contextLoads() { try { int i = 0; }catch (Exception e){ System.out.println("不可以回滚"); } }
可以手动设置回滚
@Transactional public void contextLoads() { try { int i = 0; }catch (Exception e){ // 手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); System.out.println("可以回滚"); } }
【7】新开启一个线程
Spring实现事务的原理是通过ThreadLocal把数据库连接绑定到当前线程中,新开启一个线程获取到的连接就不是同一个了。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。