5分钟探究Spring事务失效原因

栏目: Java · 发布时间: 5年前

内容简介:Spring的事务管理,大家在项目中几乎都会使用上,但是我们是否正确使用了吗?原理是否真的知道呢?本文将会结合业务场景快速讲解Spring事务失效的原理如果有这样的业务,A类中的save方法需要调用本类的save2方法,不管save2中的方法执行成功与否,都不能影响save方法的执行,因此,我们会想到把save2的事务传播行为设置成 REQUIRES_NEW,代码如下:由于,save2方法不能影响save方法的执行,所以必须补抓 save2方法。 预期结果应该是 save方法正常插入数据,save2方法插入

Spring的事务管理,大家在项目中几乎都会使用上,但是我们是否正确使用了吗?原理是否真的知道呢?本文将会结合业务场景快速讲解Spring事务失效的原理

1 业务场景

如果有这样的业务,A类中的save方法需要调用本类的save2方法,不管save2中的方法执行成功与否,都不能影响save方法的执行,因此,我们会想到把save2的事务传播行为设置成 REQUIRES_NEW,代码如下:

@Service
@Slf4j
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private UserService2 userService2;

    @Transactional
    public void save() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(5, 'Jack5')");
        try {
            save2();
        } catch (Exception e) {
            System.err.println("出错啦");
        }

    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save2() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(6, 'Jack6')");
        int i = 1 / 0;
    }
}

复制代码

由于,save2方法不能影响save方法的执行,所以必须补抓 save2方法。 预期结果应该是 save方法正常插入数据,save2方法插入数据失败

执行结果:

5分钟探究Spring事务失效原因
5分钟探究Spring事务失效原因

是的,你并没有看错,save2方法竟然插入成功!如果知道原因,可以不用继续看下文了~

2 探究

2.1 Spring的传播行为

再贴一下Spring的传播行为 public enum Propagation {

REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED(4), NEVER(5), NESTED(6); } REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。 NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。 NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。 NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。

可以肯定的是 ,该业务确实是使用 REQUIRES_NEW 。但是为什么失效呢?

2.2 动态代理

在继续探究前,先简单带过一下动态代理。 代理模式主要功能是为了增强一个类中的方法诞生的一种设计模式。 而代理模式分为动态代理和静态代理,动态代理的代理类是在运行时生成的,而静态代理是在编译时生成的。动态代理可以分为基于接口的JDK动态代理和基于类的Cglib动态代理。

下面讲解一下基于JDK的动态代理: 在 java 的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

public interface Person {
    void work();
}

public class Student implements Person {
    @Override
    public void work() {
        System.out.println("读书");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    //增强的目标类
    private Person person;

    public MyInvocationHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("先吃饭-----再看书");
        method.invoke(person, args);
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Student();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(person);
        System.out.println(Arrays.toString(Student.class.getInterfaces()));
        Person proPerson = (Person) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), myInvocationHandler);
        proPerson.work();
    }
}
复制代码

结果为: 先吃饭-----再看书 读书

详细代码可以在github下载, github.com/229319258/s…

2.3 动态代理的坑

至此,我们可以知道,Spring事务是基于动态代理实现的。那么,Spring事务失效的真正原因和动态代理有什么关联呢?

模拟Spring事务失效的问题,把上文的代码稍微修改一下,

public class Student implements Person {
    @Override
    public void work() {
        System.out.println("读书");
        try {
            this.work2();
        } catch (Exception e) {

        }
    }
    public void work2() {
        System.out.println("不想读啊");
        int i = 1 / 0;
    }
}

复制代码

大家,可以把重心放在try的代码块上,我们可以发现,实际上调用work2方法的是Student实例,并不是所谓的work2的增强类。 同理,上文中Spring事务失效的save2方法,调用的实例并不是代理类,而是未增强的普通对象UserService。

因此,没有使用Proxy生成的方法,Spring事务当然会失效~

那么,问题又来了。如果我确实想要让save2的事务生效,应该怎么处理呢? 有两种方法

  • 把save2重新放在另一个类上
  • 使用方法 AopContext.currentProxy() 获取当前代理对象
@Transactional
    public void save() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(5, 'Jack5')");
        try {
            UserService proxy = (UserService) AopContext.currentProxy();
            proxy.save2();
        } catch (Exception e) {
            System.err.println("出错啦");
        }

    }
复制代码

3 结论

1.我们在使用Spring事务的时候,不能直接在一个定义 @Transactional调用同一个类的 @Transactional(propagation = Propagation.REQUIRES_NEW)

2.除了这种情况失效外,我们也不能直接在一个未设置 @Transactional的方法,调用同一个类中调用@Transactional的方法,因为,实际上调用的并不是 proxy类的方法,而是本身的方法。 如:

//    @Transactional
    public void save() {
        save2();
    }

    @Transactional()
    public void save2() {
        jdbcTemplate.execute("INSERT INTO user (id, name) VALUES\n" +
                "(7, 'Jack7')");
        int i = 1 / 0;
    }
复制代码

查看数据库的数据,同样save2的数据并不会回滚,因为并不是调用代理类,而是调用普通的this(UserService)的方法。因此,事务同样失效。

是不是有一种想要马上看一下自己写的代码,有没有以上的问题。-.-


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

产品的视角:从热闹到门道

产品的视角:从热闹到门道

后显慧 / 机械工业出版社 / 2016-1-1 / 69.00

本书在创造性的提出互联网产品定义的基础上,为读者提供了一个从0基础到产品操盘手的产品思维培养方法! 全书以互联网产品定义为基础,提出了产品思维学习的RAC模型,通过认识产品、还原产品和创造产品三个阶段去培养产品思维和产品认知。 通过大量的图片和视觉引导的方法,作者像零基础的用户深入浅出的描绘了一条产品经理的自我修养路径,并且提供了知识地图(knowledge map)和阅读雷达等工具,......一起来看看 《产品的视角:从热闹到门道》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码