在 Spring 中,事务是通过 AOP(面向切面编程)机制实现的。Spring 事务的管理是基于代理对象的,也就是说,Spring 会创建一个代理对象来拦截带有事务注解(如 @Transactional
)的方法调用,并在方法执行前后进行事务的处理。因此,当某些情况下事务失效时,通常与 Spring 的代理机制有关。
具体来说,在同一类中的方法调用会导致 Spring 事务失效 的原因是 Spring 的代理机制无法拦截内部的自我调用(self-invocation),这破坏了事务的正确处理。以下是详细的解释:
1. Spring 事务的工作原理
Spring 使用两种代理机制来管理事务:
- JDK 动态代理:针对实现了接口的类,Spring 会创建一个实现了相同接口的代理类。事务逻辑通过代理类在方法调用时插入。
- CGLIB 代理:针对没有实现接口的类,Spring 会使用 CGLIB 生成子类代理,拦截方法调用并插入事务逻辑。
不论哪种代理方式,Spring 都是在代理类中对事务进行管理。如果调用来自外部的类,代理对象会拦截该调用并正确地管理事务逻辑。
2. 为什么同类中的方法调用导致事务失效
当一个类中的一个方法调用同类中的另一个方法时,如果调用的方法带有 @Transactional
注解,但这个调用是 直接的内部方法调用(不是通过代理对象调用的),Spring 事务将不会生效。这是因为内部方法调用不会通过 Spring 生成的代理类进行调用,而是直接在当前对象中执行,因此 Spring 无法介入处理事务。
例子:
@Service
public class TransactionService {
@Transactional
public void publicMethod() {
// 事务在这里生效
internalMethod();
}
@Transactional
public void internalMethod() {
// 事务在这里不会生效,因为它是通过内部调用触发的
}
}
在这个例子中,当 publicMethod()
被外部调用时,Spring 事务管理器能够生效。然而,publicMethod()
内部直接调用了 internalMethod()
,这属于类内部的自我调用。由于 internalMethod()
没有通过代理类进行调用,Spring 事务管理器无法对这个方法进行拦截和处理,导致事务失效。
3. 事务失效的原因总结
- 内部调用:当类内部的方法调用另一个带有
@Transactional
注解的方法时,这个调用不会通过 Spring 的代理对象进行,而是直接通过this
引用,因此 Spring 无法拦截并应用事务。 - 代理对象失效:Spring AOP 代理机制只能拦截通过代理对象进行的方法调用,而不能拦截类内部的直接方法调用。
4. 如何解决同类中事务失效的问题
为了解决类内部方法调用导致的事务失效问题,可以考虑以下几种解决方案:
方法 1:通过 Spring 容器重新获取代理对象
在类内部通过 Spring 容器获取当前对象的代理实例,然后通过代理对象调用目标方法,从而让事务生效。
@Service
public class TransactionService {
@Autowired
private ApplicationContext context;
@Transactional
public void publicMethod() {
// 从 Spring 容器中获取代理对象
TransactionService proxy = context.getBean(TransactionService.class);
proxy.internalMethod(); // 通过代理对象调用方法,事务生效
}
@Transactional
public void internalMethod() {
// 事务在这里生效
}
}
方法 2:将事务性方法抽取到另一个类中
将需要事务管理的方法放在另一个 Spring 管理的类中,然后通过依赖注入的方式调用,这样每次调用都是通过代理对象进行的,事务管理可以生效。
@Service
public class TransactionService {
@Autowired
private AnotherService anotherService;
public void publicMethod() {
// 调用另一个类中的方法,事务生效
anotherService.internalMethod();
}
}
@Service
public class AnotherService {
@Transactional
public void internalMethod() {
// 事务在这里生效
}
}
方法 3:使用 AOP 编程手动管理事务
如果不想改变方法结构,也可以使用 Spring AOP 手动管理事务,显式地在方法内部控制事务的开始和结束。
@Service
public class TransactionService {
@Autowired
private PlatformTransactionManager transactionManager;
public void publicMethod() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
internalMethod();
transactionManager.commit(status);
} catch (Exception ex) {
transactionManager.rollback(status);
throw ex;
}
}
@Transactional
public void internalMethod() {
// 事务生效
}
}
方法 4:使用 @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
使用 @EnableAspectJAutoProxy
并设置 exposeProxy = true
,可以在内部调用时获取当前的代理对象,并通过代理对象调用方法。
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {}
@Service
public class TransactionService {
@Transactional
public void publicMethod() {
// 获取代理对象
TransactionService proxy = (TransactionService) AopContext.currentProxy();
proxy.internalMethod(); // 通过代理对象调用方法
}
@Transactional
public void internalMethod() {
// 事务生效
}
}
5. 总结
- 原因:Spring 事务通过代理机制实现,当类中的一个方法调用同类中另一个带有
@Transactional
注解的方法时,由于这个调用没有经过代理对象,而是直接调用自身,Spring 无法拦截并应用事务,因此事务失效。 - 解决方案:可以通过获取代理对象、将事务性方法移到其他类中,或者显式地管理事务来解决类内部调用导致的事务失效问题。
正确理解 Spring 的代理机制和事务管理原理,可以避免事务失效的问题并有效管理事务。
标签:事务,调用,Spring,代理,之同,失效,方法,public From: https://www.cnblogs.com/DCFV/p/18440588