事务的传播无效,required_new无效,动态代理给spring事务传播留下的坑

事务的传播级别

七种传播级别
propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。(默认)
propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

情况A

有事务的A方法调用没事务的B方法,都能成功插入,
B有异常都回滚。
A使用REQUIRED,B会加入到A的事务当中。

@Transactional(propagation=Propagation.REQUIRED)
    public void A() {
        User user=new User();
        user.setUserName("张三");
        mapper.save(user);

        B();
    }

    public void B() {
        User user=new User();
        user.setUserName("李四");
        mapper.save(user);
    }

情况B

B单独加上事务REQUIRES_NEW:创建一个新的事务,挂起原来的事务。
无异常时都能添加。
B有异常,都不能添加。

问题就来了,为什么都不能添加呢?按道理应该B有异常B回滚,A正常添加才对。这里就涉及了JDK动态代理和spring事务的一个坑点。在Spring事务中嵌套调用其他的事务方法,事务都不能生效。

@Transactional(propagation=Propagation.REQUIRED)
    public void A() {
        User user=new User();
        user.setUserName("张三");
        mapper.save(user);

        B();
    }
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void B() {
        User user=new User();
        user.setUserName("李四");
        mapper.save(user);
        int i=1/0;
    }

动态代理的特性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--");
    }
}

代理类

public class ObjectProxy implements InvocationHandler {
    private static final String METHOD_PREFIX = "test";
    private Object target;
    public ObjectProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 我们使用这个标志来识别是否使用代理还是使用方法本体
        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 Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        ObjectProxy proxy = new ObjectProxy(orderService);
        orderService = (OrderService) proxy.getProxy();
        orderService.test1();
        orderService.test2();
    }
}

获取的代理分别代理执行test1方法和test2方法。

========分隔符========
–执行test1–
========分隔符========
–执行test2–

代理执行了两次


修改一下实现类嵌套执行test2

public class OrderServiceImpl implements OrderService {
    @Override
    public void test1() {
         System.out.println("--执行test1--");
         test2();
    }
    @Override
    public void test2() {
         System.out.println("--执行test2--");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl();
        ObjectProxy proxy = new ObjectProxy(orderService);
        orderService = (OrderService) proxy.getProxy();
        orderService.test1();
//        orderService.test2();
    }
}

结果:

========分隔符========
–执行test1–
–执行test2–

代理只了一次

总结:

只有代理对象proxy直接调用的那个方法才是真正的走代理的,嵌套的方法实际上就是 直接把嵌套的代码移动到代理的方法里面。 所以,嵌套的事务都不能生效。

解决方案:

  • 传统mvc项目spring配置里面增加:
<!-- 开启暴露Aop代理到ThreadLocal支持,解决事务嵌套不生效问题。 -->
<aop:aspectj-autoproxy expose-proxy="true"/><!—注解风格支持-->  
<aop:config expose-proxy="true"><!—xml风格支持-->   

配置约束要加上以下配置, 同时加入aop类的jar。
这里写图片描述
使用代理对象调用B方法。

    @Transactional(propagation=Propagation.REQUIRED)
    public void A() {
        User user=new User();
        user.setUserName("张三");
        mapper.save(user);
        try {
            ((UserService) AopContext.currentProxy()).B();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void B() {
        User user=new User();
        user.setUserName("李四");
        mapper.save(user);
        throw new RuntimeException("child Exception....................");
    }

注意:调用嵌套的方法需要用try-catch处理,不然异常继续向上抛,导致A也异常回滚了。

  • SpringBoot配置方式

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

添加注解:
这里写图片描述
修改原有代码的执行方式为:
这里写图片描述

  • 通过ApplicationContext上下文进行解决

获取到当前类的实例对象直接调用。


    /**
     * Spring应用上下文
     */
    @Autowired
    private ApplicationContext context;
    private UserService proxy;
    @PostConstruct
    public void init() {
        //从Spring上下文中获取AOP代理对象
        proxy = context.getBean(UserService.class);
    }

    @Transactional(propagation=Propagation.REQUIRED)
    public void A() {
        User user=new User();
        user.setUserName("张三");
        mapper.save(user);
        try {
            proxy.B();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void B() {
        User user=new User();
        user.setUserName("李四");
        mapper.save(user);
        throw new RuntimeException("child Exception....................");
    }

无论是 获取代理对象调用内嵌方法,还是从上下文对象获取对象调用。本质就是重新的调用一次内嵌的方法,避免了动态代理 只代理外面方法的坑。重新调用就重新产生事务了。

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring框架中,事务传播性是指在一个方法中调用另一个方法时,事务应该如何进行传播。其中,REQUIREDSpring事务传播性最常用的一种类型。下面是对REQUIRED事务传播性的详细解释: 1. 如果当前方法已经在一个事务中,则调用的方法将在同一个事务中运行。如果调用的方法发生异常并抛出了异常,则整个事务将回滚。 2. 如果当前方法没有在一个事务中,则调用的方法将开启一个新的事务并在其中运行。如果调用的方法发生异常并抛出了异常,则整个事务将回滚。 3. 如果当前方法没有在一个事务中,但调用的方法标有@Transactional注解并且使用REQUIRED传播性,则调用的方法将加入当前方法所在的事务中。 4. 如果当前方法已经在一个事务中,但调用的方法标有@Transactional注解并且使用REQUIRED_NEW传播性,则当前方法的事务将被挂起,调用的方法将在一个新的事务中运行。如果调用的方法发生异常并抛出了异常,则只有调用方法所在的事务会回滚,当前方法所在的事务不会回滚。 总之,使用REQUIRED传播性可以确保方法在一个事务中运行,同时保证整个事务的一致性和完整性。但需要注意的是,如果方法的执行时间过长,可能会导致事务锁定时间过长,从而影响系统性能。因此,在使用Spring事务时,应该根据具体情况选择合适的事务传播性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值