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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容