PS 补充下四种隔离级别和传播特性

4种事务隔离级别由低往高:


READ UNCOMMITTED,读取未提交的数据(脏读)


READ COMMITTED,读取已提交的数据,这也是oracle默认的隔离级别(不可重复读)


REPEATABLE READ,可重复读取数据,这也是Mysql默认的隔离级别(幻读)


SERIALIZABLE,可串行化读


7种事务传播机制:[http://Propagation.xxx]


REQUIRED:如果当前方法有事务则加入事务,没有则创建一个事务


NOT_SUPPORTED:不支持事务,如果当前有事务则挂起事务运行


REQUIREDS_NEW:新建一个事务并在这个事务中运行,如果当前存在事务就把当前事务挂起。新建的事务的提交与回滚一挂起事务没有联系,不会影响挂起事务的操作


MANDATORY:强制当前方法使用事务运行,如果当前没有事务则抛出异常


NEVER:当前方法不能存在事务,即非事务状态运行,如果存在事务则抛出异常


SUPPORTS:支持当前事务,如果当前没事务也支持非事务状态运行


NESTED:如果当前存在事务,则在嵌套事务内执行。嵌套事务的提交与回滚与父事务没有任务关系,反之,当父事务提交嵌套事务也一起提交,父事务回滚会也回滚嵌套事务的。


如果当前没有事务,则新建一个事务运行,这时候则与PROPAGATION_REQUIRED场景一致.


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/codeGoogler/springboot-learning


作者:Linyb极客之路

链接:https://www.cnblogs.com/linyb-geek/p/15866577.html

其实这个问题说白了就几点:

1、 数据库引擎不支持事务

2、 没有被 Spring 管理

3、 方法不是 public 的

4、 自身调用问题

事务开启必须在本类的开始(也就是说外部调用事务才会生效,不可以本类的方法调用本类的事务,因为这样的话没有经过spring的代理类)

5、 数据源没有配置事务管理器

6、 四种隔离级别和传播特性不支持事务

7、 异常被吃了


事务内部出现异常进行捕获,事务无法回滚

8、 异常类型错误


方法内部出现异常进行捕获,捕获之后再抛出异常,事务默认回滚的是:RuntimeException,触发其他异常的回滚,需要在注解上进行配置【@Transactional(rollbackFor = Exception.class】,仅限于Throwable异常类及其子类

本站所发布的一切视频课程仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站所有课程来自网络,版权争议与本站无关。如有侵权请联系邮箱:[email protected] 或者QQ 3863518080 ,我们将第一时间处理!