@Transactional我们在日常开发中经常用到,可能会经常遇到@Transactional失效的情况。下面将从what、where、when三个方面讲解@Transactional。
什么是事务?(what)
事务(transaction)是指我们做的一系列完整的事情,在事务中,任何一步出现了问题,这件事情就未算完成。这里的事务是指对数据库的增删改操作,事务的操作具有以下特性:
A(Atomicity)
原子性:事务是个完整的个体,事务内的操作要么全部成功,要么全部失败。
C(Isolation)
一致性:事务中的操作结果要保证数据达到预期的结果。
I(Consistency)
隔离性:一个事务的操作对另一个事务时不可见的。
D(Durability)
持久性:事务的操作结果要存储在数据库中,具有持久性。
编程式事务
是指在代码中实现事务的开启、提交或回滚等操作,代码的侵入性较强。编程式事务可以在代码中精确的定位事务的边界。对于编程式事务管理,spring推荐使用TransactionTemplate。
try {
声明式事务
//TODO something
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new InvoiceApplyException("异常");
}
声明式事务是建立在AOP之上的。本质就是使用动态代理对方法的前后进行拦截,实现事务的提交或者回滚。声明式事务的最大优点就是不需要通过编程对事务进行管理。这样就不需要在业务代码中参杂复杂的事务管理的代码。我们只需要通过在配置文件中配置需要添加事务的方法(或者使用@Transactional注解)就能进行事务管理。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional注解了。
@Transactional public String user()
{
int insert = userMapper.insert(userInfo);
}
@Transactional用在什么地方?(where)
@Transactional可以用在接口、类和方法上。
类上:使用在类上的时候,该类的所有public方法上都会配置相同属性的事务。、
方法上:当改方法的类上和方法上都使用了@Transactional时,方法上的事务配置会覆盖类上的事务配置。
接口上:不建议在接口上配置事务,因为当在声明该接口的实现类使用cglib代理时,接口上的事务配置会失效。
@Transactional属性
propagation属性
propagation代表事务的传播行为,默认值为Propagation.REQUIRED。
Propagation.REQUIRED:如果当前存在事务,则以当前事务运行。如果当前不存在,则创建一个新的事务运行。
Propagation.SUPPORTS:如果当前存在事务,则以当前事务运行。如果不存在事务,则以非事务的方式运行。
Propagation.MANDATORY:如果当前存在事务,则以当前事务运行。如果不存在,则抛出异常。
Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 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。
TransactionDefinition.ISOLATION_DEFAULT:与底层数据库采用的隔离级别保持一致。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:脏读(读未提交)
TransactionDefinition.ISOLATION_READ_COMMITTED:不可重复读(读已提交)
TransactionDefinition.ISOLATION_REPEATABLE_READ:幻读(范围查询时读入新插入数据或者数据被删除)
TransactionDefinition.ISOLATION_SERIALIZABLE:序列化,不存在事务问题。
timeout属性
事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 属性
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor 属性
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor属性
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
@Transactional什么时候会失效(WHEN)
1.@Transactional 应用在非 public 修饰的方法上
之所以会失效是因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute`方法,获取Transactional 注解的事务配置信息。
@Transactional标签用于将对应包装的bean设置成一个新的代理bean对象供外部使用,就是说外部调用这个proxy bean的公共方法时先会调用开启事务等的切面工作,若设置成私有方法只能类内用this指针调用,这样被调用的bean是其本身,不是proxy对象,因此没有transactional切面的意义
protected TransactionAttribute computeTransactionAttribute(Methodmethod,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() &&!Modifier.isPublic(method.getModifiers())) {
return null;
}
显然非public方法会返回null。
2.数据库引擎不支持事务
数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的。
3.事务的Propagation属性配置错误
配置了Propagation.NOT_SUPPORTED或TransactionDefinition.PROPAGATION_NEVER属性
4.rollbackFor 设置错误,@Transactional 注解失效
Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
5.方法之间的互相调用也会导致@Transactional失效
例如:一个操作用户的UserService类中有两个public方法A和B,A方法调用B方法,A上没有@Transactional,而B上有。当controller层调用A方法时,B方法上的事务将不会生效。
原因是由于spring的aop导致的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
6.异常被你的 catch“吃了”导致@Transactional失效
这种情况是最常见的一种@Transactional注解失效场景:
@Transactional private Integer A() throws Exception { int insert = 0; try { User tom = new User(); tom.setCityName("javaHuang"); tom.setUserId(1); User mic = new User(); mic.setCityName("javaHuang"); mic.setUserId(1); /** * 插入user */ insert += userMapper.insert(tom); insert += userMapper.insert(mic); } catch (Exception e) { e.printStackTrace(); } return insert; }
如果A方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚,而是会报出异常。
解决方法:
第一声明事务的时候加上rollback=‘exception’
第二 cath代码块里面手动回滚
总结
以上就是@Transactional的失效场景。
原文链接:https://blog.csdn.net/nianqrzhanghw/article/details/105239899