美文网首页
@Transactional 注解失效情况及解决办法

@Transactional 注解失效情况及解决办法

作者: 瞎胡扯1 | 来源:发表于2022-10-13 15:37 被阅读0次

    一、@Transactional 注解在了非 public 方法上

    1. 如下所示@Transactional修饰在了非public方法上
    @Service
    public class TestServiceImpl {
    
        @Resource
        private Test1Mapper test1Mapper;
        
        @Transactional
         protected Long save(Long seq){
            Long aLong = Optional.ofNullable(seq).orElse(System.currentTimeMillis());
            Test1Entity entity = new Test1Entity().setSeq(seq);
            test1Mapper.insert(entity);
            long l = entity.getId() / 0;
            return entity.getId();
        }
    }
    
    
    1. 失效原因

      Spring 中是通过动态代理来实现注解了@Transactional的方法的事务处理操作,而在处理 @Transactional 的注解时是只对 public方法才有效。
      Spring 在扫描切点时,是根据注解进行判断是否创建切点。

      AopUtils#canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions)
      
      
      public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
          // .....
          for (Class<?> clazz : classes) {
              Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
              for (Method method : methods) {
                  if (introductionAwareMethodMatcher != null ?
                          introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                          // 此处判断是否匹配生成切面
                          methodMatcher.matches(method, targetClass)) {
                      return true;
                  }
              }
          }
          return false;
      }
      
      
      // TransactionAttributeSourcePointcut# matches(Method method, Class<?> targetClass)
      
      public boolean matches(Method method, Class<?> targetClass) {
          TransactionAttributeSource tas = getTransactionAttributeSource();
          // 获取 注解属性
          return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
      }
      
      
      // AnnotationTransactionAttributeSource#getTransactionAttribute
      // AnnotationTransactionAttributeSource#computeTransactionAttribute
      
      @Nullable
      protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
          // Don't allow no-public methods as required.
          // 判断是否只允许 public 方法
          if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
              return null;
          }
      
          // 其他代码 ...
          return null;
      }
      
      
    2. 解决方法

    • 方法一:把需要支持事务的方法统一定义为 public 修饰的方法
    • 方法二:创建一个全为public修饰的方法的门面类,在此类中添加@Transactional

    二、在类内部调用添加 @Transactional 的方法

    1. 实例
    @Service
    public class TestServiceImpl {
    
       @Resource
       private Test1Mapper test1Mapper;
    
       @Transactional
        public void save(Long seq){
           Long aLong = Optional.ofNullable(seq).orElse(System.currentTimeMillis());
           test1Mapper.insert(new Test1Entity(null, seq));
           long l = aLong / 0;
       }
    
       // 调用内部方法
       public void innerInvoke(){
           this.save(100L);
       }
    }
    
    
    1. 失效原因

    此种情况跟情况一的原因类似,在Spring启动时会扫描所有添加了@Transactional注解的类,创建的切面,当在同一个类中调用是,切面失去了作用。

    1. 解决方法

    既然事务管理是基于动态代理对象的代理逻辑实现的,那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过this对象来调用方法,绕过的代理对象,肯定就是没有代理逻辑了。

    如下所示:

    
    @Service
    public class TestServiceImpl {
    
        @Resource
        private Test1Mapper test1Mapper;
    
        // 第一个一个自己作为属性
        @Resource
        @Lazy
        private TestServiceImpl test1Service;
    
        @Transactional
         public void save(Long seq){
            Long aLong = Optional.ofNullable(seq).orElse(System.currentTimeMillis());
            test1Mapper.insert(new Test1Entity(null, seq));
            long l = aLong / 0;
        }
    
        // 调用内部方法
        public void innerInvoke(){
           //内部调用事务方法  
            test1Service.save(100L);
        }
    }
    
    

    三、捕获异常未抛出

    1. 实例
    
    @Service
    public class TestServiceImpl {
    
        @Resource
        private Test1Mapper test1Mapper;
    
        @Transactional
         public void save(Long seq){
            try {
                Long aLong = Optional.ofNullable(seq).orElse(System.currentTimeMillis());
                test1Mapper.insert(new Test1Entity(null, seq));
                long l = aLong / 0;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    1. 失效原因

    事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。
    这种的话,可能我们比较常见,问题就出在代理逻辑中,我们先看看源码里卖弄动态代理逻辑是如何为我们管理事务的。

    TransactionAspectSupport#invokeWithinTransaction

    
     protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)  
          throws Throwable {  
      
       // If the transaction attribute is null, the method is non-transactional.  
       final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);  
       final PlatformTransactionManager tm = determineTransactionManager(txAttr);  
       final String joinpointIdentification = methodIdentification(method, targetClass);  
      
       if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {  
          // Standard transaction demarcation with getTransaction and commit/rollback calls.  
           //开启事务  
          TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);  
          Object retVal = null;  
          try {  
             // This is an around advice: Invoke the next interceptor in the chain.  
             // This will normally result in a target object being invoked.  
              //反射调用业务方法  
             retVal = invocation.proceedWithInvocation();  
          }  
          catch (Throwable ex) {  
             // target invocation exception  
              //异常时,在catch逻辑中回滚事务  
             completeTransactionAfterThrowing(txInfo, ex);  
             throw ex;  
          }  
          finally {  
             cleanupTransactionInfo(txInfo);  
          }  
           //提交事务  
          commitTransactionAfterReturning(txInfo);  
          return retVal;  
       }  
      
       else {  
         //....................  
       }  
    } 
    
    
    1. 解决方法
    • 方法一:在方法中不对异常进行捕获
    • 方法二:若必须捕获,在捕获处理后在重新抛出异常。

    相关文章

      网友评论

          本文标题:@Transactional 注解失效情况及解决办法

          本文链接:https://www.haomeiwen.com/subject/bhufzrtx.html