@Transactional注解失效了?!
最近在做一个表与表关联性很强的项目。越发觉得事务的重要性。
什么是事务?一言蔽之,事务就是逻辑上的一组操作,要么都执行,要么都不执行。
在后端写关联性强的复杂的时候,我们往往同样会遇到如果有一条sql失效,之前执行的所有sql都需要回滚的情况。
SpringBoot实现事务
在springboot中,我们往往有两种策略去实现事务,分别是编程式事务和声明式事务。
声明式事务
我们往往用@Trasactional注解去实现。先看一下该注解的源码
//该注解作用于类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
//设定生命周期为全周期,支持利用反射机制的代码读取和阅读
@Retention(RetentionPolicy.RUNTIME)
//表示,如果被该注解修饰的父类被子类继承,那么子类也具备事务特性
@Inherited
//表示该注解应该被javaDoc工具记录
@Documented
public @interface Transactional {}
该注解有几个常用的参数
1.isolation
- Isolation.DEFAULT:使用各个数据库默认的隔离级别【默认】
- Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读, 不可重复读)(基本不使用)
- Isolation.READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)
- Isolation.REPEATABLE_READ:可重复读(会出现幻读)
- Isolation.SERIALIZABLE:串行化
MYSQL:默认为REPEATABLE_READ
2.propagation
- Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择,也是Spring【默认】的传播机制
- Propagation.SUPPORTS:持当前事务,如果当前有事务,就以事务方式执行;如果当前没有事务,就以非事务方式执行
- Propagation.MANDATORY:使用当前的事务,且必须在一个已有的事务中执行,如果当前不存在事务,否则抛出异常
- Propagation.REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
- Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起
- Propagation.NEVER:以非事务方式执行,且必须在一个没有的事务中执行,如果当前存在事务,则抛出异常【与Propagation.MANDATORY相反】
- Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与【Propagation.REQUIRED 】类似的操作
3.readOnly
默认是false,设置为true的含义是,告诉spring,该方法是只读操作,如果出现写就抛异常
4.noRollbackFor 和 noRollbackForClassName(遇到时不回滚)
用来指明不回滚的条件是哪些异常类或者异常类名。
5.rollbackFor 和 rollbackForClassName(遇到时回滚)
Spring默认情况下会对运行期异常(RunTimeException)进行事务回滚,如果遇到checked异常就不回滚。
6.timout(超时时间)
用于处理事务处理的事件长度,阻止可能长时间阻塞系统或者占用系统资源,单位是s,如果超时事务回滚,抛出Transaction TimedOutException
7.value(指定使用的事务管理器)
你可能在spring中有多个事务管理器,用来控制多数据源,此时需要指定。
编程式事务
编程式事务的优点是可以在方法里细粒度控制事务,缺点是代码侵入性强。
在spring中,我们如果需要自己配置事务管理器可以这么写。
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
注意:我这里的PlatformTransactionManager是面向mybatis的事务管理器。
@Resource
private PlatformTransactionManager transactionManager;
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
//细粒度事务处理
try {
int update = faSaasGamerecordMapper.update(faSaasGamerecord,
new UpdateWrapper<FaSaasGamerecord>()
.eq("id", faSaasGamerecord.getId())
.set("newPlayer", newPlayerCount)
.set("timeavg", averageTimeCount)
.set("playCount", playRecordCount)
.set("shareCount", playerShareCount)
.set(UPDATE_TIME,TimeUtil.nowToTimeStamp())
);
if(update<1){
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"更新失败");
}
transactionManager.commit(transactionStatus);
}catch (Exception e){
log.error("FaSaasGamerecord更新失败");
transactionManager.rollback(transactionStatus);
}
通过向transactionManager提交事务来完成事务的操作。如果失败了,就将当前的事务回滚。
一般把多条sql和commit写在try 语句中,catch则操作回滚行为。
@Transactional失效的6种场景
1.修饰在非public方法上
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
这是spring源码种的AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法。
如果Modifier.isPublic()是false 那么就return null.
2.配置错误的propagation
注意加粗的部分。
主要是以下的三种:
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
3.rollbackfor指定错误
Spring默认是抛出unchecked异常,他是继承于RuntimeException,或者error才会回滚事务;如果抛出除此之外的其它异常,那就不会给你回滚了。
4.同一个类中的方法调用。
这是最常见的错误。比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方
5.异常被catch吸收
这个都懂