前言

  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�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种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等.ASPECTJ)
  注: 如果是在idea上运行,则需做如下配置
  在这里插入图片描述
  4、直接用TransactionTemplate
  示例:
  @Autowired
  private TransactionTemplate transactionTemplate;
  private void process(){
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�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种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等.ASPECTJ)
  注: 如果是在idea上运行,则需做如下配置
  在这里插入图片描述
  4、直接用TransactionTemplate
  示例:
  @Autowired
  private TransactionTemplate transactionTemplate;
  private void process(){
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�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种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等.ASPECTJ)
  注: 如果是在idea上运行,则需做如下配置
  在这里插入图片描述
  4、直接用TransactionTemplate
  示例:
  @Autowired
  private TransactionTemplate transactionTemplate;
  private void process(){