乐趣区

关于spring:spring事务失效的几种场景以及原因

前言

spring 事务生效场景可能大家在很多文章都看过了,所以明天就水一篇,看大家能不能播种一些不一样的货色。间接进入主题

spring 事务生效场景以及起因

1、场景一:service 没有托管给 spring

public class TranInvalidCaseWithoutInjectSpring {

    private UserService userService;

    public TranInvalidCaseWithoutInjectSpring(UserService userService) {this.userService = userService;}

    @Transactional
    public boolean add(User user){boolean isSuccess = userService.save(user);
        int i = 1 % 0;
        return isSuccess;
    }
}
    @Test
    public void testServiceWithoutInjectSpring(){boolean randomBoolean = new Random().nextBoolean();
        TranInvalidCaseWithoutInjectSpring tranInvalidCaseWithoutInjectSpring;
        if(randomBoolean){tranInvalidCaseWithoutInjectSpring = applicationContext.getBean(TranInvalidCaseWithoutInjectSpring.class);
            System.out.println("service 曾经被 spring 托管");
        }else{tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService);
            System.out.println("service 没被 spring 托管");
        }

        boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user);
        Assert.assertTrue(isSuccess);

    }

生效起因: spring 事务失效的前提是,service 必须是一个 bean 对象
解决方案: 将 service 注入 spring

2、场景二:抛出受检异样

@Service
public class TranInvalidCaseByThrowCheckException {

    @Autowired
    private UserService userService;


    @Transactional
    public boolean add(User user) throws FileNotFoundException {boolean isSuccess = userService.save(user);
        new FileInputStream("1.txt");
        return isSuccess;
    }
    }
 @Test
    public void testThrowCheckException() throws Exception{boolean randomBoolean = new Random().nextBoolean();
        boolean isSuccess = false;
        TranInvalidCaseByThrowCheckException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseByThrowCheckException.class);
        if(randomBoolean){System.out.println("配置 @Transactional(rollbackFor = Exception.class)");
            isSuccess = tranInvalidCaseByThrowCheckException.save(user);
        }else{System.out.println("配置 @Transactional");
            tranInvalidCaseByThrowCheckException.add(user);
        }

        Assert.assertTrue(isSuccess);

    }

生效起因: spring 默认只会回滚非查看异样和 error 异样
解决方案: 配置 rollbackFor

3、场景三:业务本人捕捉了异样

 @Transactional
    public boolean add(User user) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) { }
        return isSuccess;
    }
  @Test
    public void testCatchExecption() throws Exception{boolean randomBoolean = new Random().nextBoolean();
        boolean isSuccess = false;
        TranInvalidCaseWithCatchException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseWithCatchException.class);
        if(randomBoolean){randomBoolean = new Random().nextBoolean();
            if(randomBoolean){System.out.println("将异样原样抛出");
                tranInvalidCaseByThrowCheckException.save(user);
            }else{System.out.println("设置 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();");
                tranInvalidCaseByThrowCheckException.addWithRollBack(user);
            }
        }else{System.out.println("业务本人捕捉了异样");
            tranInvalidCaseByThrowCheckException.add(user);
        }

        Assert.assertTrue(isSuccess);

    }

生效起因: spring 事务只有捕捉到了业务抛出去的异样,能力进行后续的解决,如果业务本人捕捉了异样,则事务无奈感知
解决方案:
1、将异样原样抛出;
2、设置 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

4、场景四:切面程序导致

@Service
public class TranInvalidCaseWithAopSort {

    @Autowired
    private UserService userService;

    @Transactional
    public boolean save(User user) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
        }
        return isSuccess;
    }



}
@Aspect
@Component
@Slf4j
public class AopAspect {@Around(value = "execution (* com.github.lybgeek.transcase.aopsort..*.*(..))")
    public Object around(ProceedingJoinPoint pjp){

        try {System.out.println("这是一个切面");
           return pjp.proceed();} catch (Throwable throwable) {log.error("{}",throwable);
        }

        return null;
    }
}

生效起因: spring 事务切面的优先级程序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异样,则会同业务本人捕捉异样的那种场景一样
解决方案:
1、在切面中将异样原样抛出;
2、在切面中设置 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

5、场景五:非 public 办法

@Service
public class TranInvalidCaseWithAccessPerm {

        @Autowired
        private UserService userService;

        @Transactional
        protected boolean save(User user){boolean isSuccess = userService.save(user);
            try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
            }
            return isSuccess;
        }

}
public class TranInvalidCaseWithAccessPermTest {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(Application.class);
        TranInvalidCaseWithAccessPerm tranInvalidCaseWithAccessPerm = context.getBean(TranInvalidCaseWithAccessPerm.class);
        boolean isSuccess = tranInvalidCaseWithAccessPerm.save(UserUtils.getUser());

        System.out.println(isSuccess);

    }
}

生效起因: spring 事务默认失效的办法权限都必须为 public

解决方案:
1、将办法改为 public;
2、批改 TansactionAttributeSource, 将 publicMethodsOnly 改为 false【这个从源码跟踪得出结论】
3、开启 AspectJ 代理模式【从 spring 文档得出结论】

文档如下

Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

具体步骤:

1、在 pom 引入 aspectjrt 坐标以及相应插件

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.9</version>
    <configuration>
        <showWeaveInfo>true</showWeaveInfo>
        <aspectLibraries>
            <aspectLibrary>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
             <goals>
              <goal>compile</goal>       <!-- use this goal to weave all your main classes -->
              <goal>test-compile</goal>  <!-- use this goal to weave all your test classes -->
            </goals>
        </execution>
    </executions>
</plugin> 

2、在启动类上加上如下配置

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

注: 如果是在 idea 上运行,则需做如下配置

4、间接用 TransactionTemplate

示例:

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {processInTransaction();
            }
        });

    }

6、场景六:父子容器

生效起因: 子容器扫描范畴过大,将未加事务配置的 serivce 扫描进来

解决方案:
1、父子容器个扫个的范畴;
2、不必父子容器,所有 bean 都交给同一容器治理

注: 因为示例是应用 springboot,而 springboot 启动默认没有父子容器, 只有一个容器,因而就该场景就演示示例了

7、场景七:办法用 final 润饰

    @Transactional
    public final boolean add(User user, UserService userService) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
        }
        return isSuccess;
    }

生效起因: 因为 spring 事务是用动静代理实现,因而如果办法应用了 final 润饰,则代理类无奈对指标办法进行重写,植入事务性能

解决方案:
1、办法不要用 final 润饰

8、场景八:办法用 static 润饰

  @Transactional
    public static boolean save(User user, UserService userService) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
        }
        return isSuccess;
    }

生效起因: 起因和 final 一样

解决方案:
1、办法不要用 static 润饰

9、场景九:调用本类办法

   public boolean save(User user) {return this.saveUser(user);
    }

    @Transactional
    public boolean saveUser(User user) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
        }
        return isSuccess;
    }

生效起因: 本类办法不通过代理,无奈进行加强

解决方案:
1、注入本人来调用;
2、应用 @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()

10、场景十:多线程调用

 @Transactional(rollbackFor = Exception.class)
    public boolean save(User user) throws ExecutionException, InterruptedException {Future<Boolean> future = executorService.submit(() -> {boolean isSuccess = userService.save(user);
            try {int i = 1 % 0;} catch (Exception e) {throw new Exception();
            }
            return isSuccess;
        });
        return future.get();}

生效起因: 因为 spring 的事务是通过数据库连贯来实现,而数据库连贯 spring 是放在 threadLocal 外面。同一个事务,只能用同一个数据库连贯。而多线程场景下,拿到的数据库连贯是不一样的,即是属于不同事务

11、场景十一:谬误的流传行为

 @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public boolean save(User user) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
        }
        return isSuccess;
    }

生效起因: 应用的流传个性不反对事务

12、场景十二:应用了不反对事务的存储引擎

生效起因: 应用了不反对事务的存储引擎。比方 mysql 中的 MyISAM

13、场景十三:数据源没有配置事务管理器

注: 因为 springboot,他默认曾经开启事务管理器。org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration。因而示例略过

14、场景十四:被代理的类过早实例化

@Service
public class TranInvalidCaseInstantiatedTooEarly implements BeanPostProcessor , Ordered {

    @Autowired
    private UserService userService;


    @Transactional
    public boolean save(User user) {boolean isSuccess = userService.save(user);
        try {int i = 1 % 0;} catch (Exception e) {throw new RuntimeException();
        }
        return isSuccess;
    }

    @Override
    public int getOrder() {return 1;}
}

生效起因: 当代理类的实例化早于 AbstractAutoProxyCreator 后置处理器,就无奈被 AbstractAutoProxyCreator 后置处理器加强

总结

本文列举了 14 种 spring 事务生效的场景,其实这 14 种外面有很多都是归根结底都是属于同一类问题引起,比方因为动静代理起因、办法限定符起因、异样类型起因等

demo 链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case

退出移动版