有时候我们会遇到这样一种问题:
service本类中方法调用另一个方法事务不生效问题,
但是我们已经在每个方法上都加了事务控制,为什么还是不生效呢?
这里就有一个点比较重要了,service本类方法中调用另外一个方法。
serviceImpl1{
public A(){ //给A加事务
B(); //A调用B
}
public B(){
}
}
serviceImpl2{
public C(){
}
}
问题来了。
为什么A方法调用B方法,B的事务没有生效的原因:
我们知道spring事务管理是通过JDK动态代理的方式进行实现的(另一种是使用CGLib动态代理实现的),也正是因为动态代理的特性造成了上述方法中的事务失效!
那么动态代理的这个特性到底是什么才会造成Spring事务失效呢?
1:动态代理的特性到底是什么?
首先我们看一下一个简单的动态代理实现方式:
//接口
public interface OrderService{
void test1();
void test2();
}
//接口实现类
public class OrderServiceImpl implements OrderService{
@Override
public void test1(){
System.out.println("--执行test1--");
}
@Override
public void test2(){
System.out.println("--执行test2--");
}
}
//代理类
class OrderProxy implements InvocationHandler{
private static final String METHOD_PREFIX = "test";
private Object target;
public OrderProxy(Object target){
this.target=target;
}
@Override
public Object invoke(Object object,Method method,Object[] args)throws Throwable{
//我们使用这个标志来识别是否使用dialing还是使用方法本体
if(method.getName().startsWith(METHOD_PREFIX)){
System.out.println("=====分隔符=====");
}
return method.invoke(target, args);
}
public Object getProxy(){
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
}
//测试方法
public class ProxyDemo{
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
OrderProxy proxy = new OrderProxy(orderService);
orderService = (OrderService)proxy.getProxy();
orderService.test1();
orderService.test2();
}
}
此时我们执行以下测试方法,注意了此时是同时调用了test1()和test2()的,执行结果如下:
可以看出,在OrderServiceImpl 类中由于test1()没有调用test2(),他们方法的执行都是使用了代理的,也就是说test1和test2都是通过代理对象调用的invoke()方法。
我们模拟一下场景在test1()中调用test2(),那么代码修改为如下:
执行结果如下:
这里可以很清楚的看出来test1()走的是代理,而test2()走的是普通的方法,没有经过代理!看到这里你是否已经恍然大明白了呢?
这个应该可以很好的理解为什么是这样子!
这是因为在Java中test1()中调用test2()中的方法,本质上就相当于把test2()的方法体放入到test1()中,也就是内部方法,同样的不管你嵌套了多少层,只有代理对象proxy 直接调用的那一个方法才是真正的走代理的,如下:
测试方法和上边的测试方法一样,执行结果如下:
记住:只有代理对象proxy直接调用的那个方法才是真正的走代理的!
2:怎么解决动态代理导致的这个方法调用之间不走代理的问题?
上文的分析中我们已经了解了为什么在该特定场景下使用Spring事务的时候造成事务无法回滚的问题,下边我们谈一下几种解决的方法:
1、我们可以选择逃避这个问题!我们可以不使用以上这种事务嵌套的方式来解决问题,最简单的方法就是把问题提到Service或者是更靠前的逻辑中去解决,使用service.xxxtransaction是不会出现这种问题的。
2、通过AopProxy上下文获取代理对象:
(1)SpringBoot配置方式:注解开启 exposeProxy = true,暴露代理对象 (否则AopContext.currentProxy()) 会抛出异常。
添加依赖:
添加注解:
修改原有代码的执行方式为:
可见,child方法由于异常已经回滚了,而parent可以正确的提交,这才是我们想要的结果!注意的是在parent调用child的时候是通过try/catch捕获了异常的!
(2)传统Spring XML配置文件只需要添加依赖个设置如下配置即可,使用方式一样:
<aop:aspectj-autoproxy expose-proxy="true"/>
3、通过ApplicationContext上下文进行解决:
因此:
避免在一个Service内部进行事务方法的嵌套调用。
在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务
3:springAOP 同理,对事务的影响。
serviceImpl1{
public A(){ 给A加事务
B();
}
public B(){
}
}
serviceImpl2{
public C(){
}
}
为什么A方法调用B方法,B的事务没有生效的原因:
也就是说我们首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。
简单的一个例子:
public interface AService {
public void a();
public void b();
}
@Service()
public class AServiceImpl1 implements AService{
@Transactional(propagation = Propagation.REQUIRED)
public void a() {
this.b();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
}
}
目标对象内部的自我调用将无法实施切面中的增强。
此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,
因此b方法的事务定义@Transactional(propagation = Propagation.REQUIRES_NEW)将不会实施.
(此处:如果不定义,会使用默认的传播行为:require 需要)
即结果是b和a方法的事务定义是一样的。
解决方案:
此处a方法中调用b方法时,只要通过AOP代理调用b方法即可走事务切面,即可以进行事务增强。
public void a() {
aopProxy.b();//即调用AOP代理对象的b方法即可执行事务切面进行事务增强
}
修改我们的业务实现类
this.b();
-----------修改为--------->
((AService) AopContext.currentProxy()).b();
当然使用AopContext需要开启aop框架暴露该代理对象
springboot开启的方式是@EnableAspectJAutoProxy(,exposeProxy = true)
本文是搜集的网上一些好的说明,整理整合,帮助理解。