写在前面
不论是工作中还是面试的时候,spring事务失效的问题一直是经常碰到的问题,其实会有很多情况下spring事务会失效,所以我们平时最好反复确认程序中的事务是否真的生效。
事务的本质是Spring AOP通过生成代理类,并重写其中的public并且非final,static方法,并对目标方法做了事务方面的增强来实现的
方法访问权限
@Transactional(rollbackFor = Exception.class)
private Response doTheTransactionStuff(TransactionEntity entity){
//do something
}
这种情况下spring的事务是没法生效的。直接原因是因为spring中规定了事务生效的方法必须是public,否则返回null,即事物不生效
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 不允许非public的方法
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
//其它代码
}
protected boolean allowPublicMethodsOnly() {
return false;
}
spring中源码不允许有非public的方法支持事务。有意思的事,spring为什么要预留一个allowPublicMethodsOnly的方法呢?
根本原因是因为Spring AOP是通过代理来实现事务管理的。代理对象会包裹目标对象并对其方法进行增强,以实现事务的控制。而代理对象只能代理目标对象的public方法,因为代理对象的方法是作为目标对象的替代者,代理对象只能对外暴露目标对象中的public方法。因此,只有public方法才能被代理对象截获并添加事务管理的逻辑。
spring aop----->代理----->代理只能暴露目标对象的public方法
final修饰的方法
@Transactional(rollbackFor = Exception.class)
public final Response doTheTransactionStuff(TransactionEntity entity){
//do something
}
上述方法被final修饰,同样也无法使得事务生效,即使是public方法也不行。
原因是Spring的事务机制无法在final方法上生效是因为final方法是无法被子类重写的,而Spring AOP是通过代理来实现事务管理的。代理对象会继承目标对象的方法,并通过对目标对象方法的增强来实现事务的控制。然而,由于final方法无法被子类重写,代理对象无法对final方法进行增强。
spring aop----->代理----->代理需要继承并重写目标方法来做事务增强----->final方法没法被重写
方法内部调用
@Transactional(rollbackFor = Exception.class)
public Response doTheTransactionStuff(TransactionEntity entity){
//do something
}
public Response entryMethod(TransactionEntity entity){
return doTheTransactionStuff(entity);
}
方法doTheTransactionStuff被entryMethod调用,在doTheTransactionStuff上的事务没法生效,是因为上述的entryMethod方法实际上是直接通过this来调用了,也就是不通过代理类的那个增强方法,而是被代理对象的初始方法。
public Response entryMethod(TransactionEntity entity){
return this.doTheTransactionStuff(entity);
}
基于上面说的原理,我们可以通过注入一个自己的对象来调用方法就行
@Service
public class UserService{
@Resource
private UserService otherInstance;
@Transactional(rollbackFor = Exception.class)
public Response doTheTransactionStuff(TransactionEntity entity){
//do something
}
public Response entryMethod(TransactionEntity entity){
return otherInstance.doTheTransactionStuff(entity);
}
}
只要不通过this.doTheTransactionStuff来调用,这么做看上去有点hack,不建议这么做
这个解法很适合解释为什么spring解决循环依赖需要三级缓存,而不是二级缓存
简单解释一下就是这里注入的otherInstances实际不是UserService的对象,而是otherInstance的代理对象。
还有一种方法更加hack,通过((UserService)AopContext.currentProxy()).doTheTransactionStuff(entity),来显式的拿出来UserService的代理对象,也就是otherInstance的代理对象
未被spring容器管理的对象
包含了prototype类型的,没有被注入到容器的对象,比如没有添加@Component,@Service等注解,不展开了
多线程调用
在spring中每个事务都需要维护自己的数据库连接,不同事务间的数据库连接不一样,事务自然也就没法传递
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
多线程调用代码如下,即使添加了join,明确知道了db操作结束了,但也不再事务管理范围内
@Service
public class UserService{
@Resource
private UserService otherInstance;
@Transactional(rollbackFor = Exception.class)
public Response doTheTransactionStuff(TransactionEntity entity){
//do something
}
public Response entryMethod(TransactionEntity entity){
Thread transactionThread = new Thread(() -> {
otherInstance.doTheTransactionStuff(entity);
});
transactionThread.start();
transactionThread.join();
return new Response();
}
}
表不支持事务或者未开启事务
前者不做解释,后者主要是在传统的spring项目中,那么需要applicationContext中配置事务相关参数。另外需要注意的是,pointcut切不到的位置也没法让事务生效。spring boot的项目在启动类加上@EnableTransactionManagement
<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切点把事务切进去 -->
<aop:config>
<aop:pointcut expression="execution(* property.hghargo.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>