zoukankan      html  css  js  c++  java
  • 使用@Transactional应注意的问题

    使用@Transactional应注意的问题

    @Transactional 基本原理概述

    在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。

    你需要注意的事

    1. @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能
    2. Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
    3. 当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,还是在具体的类上使用 @Transactional 注解比较好。
    4. 避免 Spring 的 AOP 的自调用问题:自调用就是方法A内调用本类的另一个加上事务注解的方法B时,方法B中对数据库的操作是不带事务的。

    Spring AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

    失效原因
    image

    方法one方法two都是public的:

    • classA中 ,任意要调用classB的方法,是通过spring代理的方式,那么spring的注解才会生效
    • classA中,方法one 调用同class内的方法two,即this调用,spring注解不会生效(例如@Cachable,@Transaction)

    解决方法

    既然已知原因,那么解决的方法就有了,核心思想就是如何获得动态代理对象,而不是使用this去调用。

    方案一:使用AspectJ代理

    @Service
    public class OrderService {
        private void insert() {
            insertOrder();
        }
    @Transactional
        public void insertOrder() {
            //insert log info
            //insertOrder
            //updateAccount
           }
    }
    

    insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

    上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,可以使用 AspectJ取代 Spring AOP 代理,但现在有更好的解决方法。

    方案二:利用AopContext.currentProxy()方法获得代理

    方法的意思是尝试返回当前AOP代理。这种做法非常简洁,但是在默认情况下是不起作用的!因为AopContext中拿不到currentProxy,会报空指针。需要一些额外的配置,但不能对所有的注解拦截都有效,这是因为这些注解不是用的AspectJ代理,如果是@Transactional事务注解的话, 则是生效的,具体细节要翻源码了,这里不推荐使用。

    方案三:通过ApplicationContext来获得动态代理对象(推荐)

    @Component
    public class AsyncService implements ApplicationContextAware {
    
        private ApplicationContext applicationContext;
    
        public void async1() {
            System.out.println("1:" + Thread.currentThread().getName());
            // 使用AppicationContext来获得动态代理的bean,然后再执行你调用的方法
            this.applicationContext.getBean(AsyncService.class).async2();
        }
    
        @Async
        public void async2() {
            System.out.println("2:" + Thread.currentThread().getName());
        }
    
        // 注入ApplicationContext
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    
  • 相关阅读:
    Java转Exe
    匹配数字和字母的正则表达式
    POI
    Maven– HelloWorld实例
    eclipse+maven+tomcat构建web工程
    Maven
    SSH
    Struct2小结:
    Eclipse中没有javax.servlet和javax.servlet.http包的处理办法
    openSession()与getCurrentSession()的区别
  • 原文地址:https://www.cnblogs.com/keeya/p/11180612.html
Copyright © 2011-2022 走看看