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

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 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种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等


相关文章
|
4月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
5月前
|
Java 关系型数据库 MySQL
Spring 事务失效场景总结
Spring 事务失效场景总结
66 4
|
1月前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
46 1
Spring高手之路24——事务类型及传播行为实战指南
|
25天前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
38 3
|
3月前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
3月前
|
Java 测试技术 数据库
Spring事务传播机制(最全示例)
在使用Spring框架进行开发时,`service`层的方法通常带有事务。本文详细探讨了Spring事务在多个方法间的传播机制,主要包括7种传播类型:`REQUIRED`、`SUPPORTS`、`MANDATORY`、`REQUIRES_NEW`、`NOT_SUPPORTED`、`NEVER` 和 `NESTED`。通过示例代码和数据库插入测试,逐一展示了每种类型的运作方式。例如,`REQUIRED`表示如果当前存在事务则加入该事务,否则创建新事务;`SUPPORTS`表示如果当前存在事务则加入,否则以非事务方式执行;`MANDATORY`表示必须在现有事务中运行,否则抛出异常;
171 4
Spring事务传播机制(最全示例)
|
2月前
|
Java 关系型数据库 MySQL
Spring事务失效,我总结了这7个主要原因
本文详细探讨了Spring事务在日常开发中常见的七个失效原因,包括数据库不支持事务、类不受Spring管理、事务方法非public、异常被捕获、`rollbackFor`属性配置错误、方法内部调用事务方法及事务传播属性使用不当。通过具体示例和源码分析,帮助开发者更好地理解和应用Spring事务机制,避免线上事故。适合所有使用Spring进行业务开发的工程师参考。
38 2
|
2月前
|
Java 程序员 Spring
Spring事务的1道面试题
每次聊起Spring事务,好像很熟悉,又好像很陌生。本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。
Spring事务的1道面试题
|
3月前
|
Java Spring
Spring 事务传播机制是什么?
Spring 事务传播机制是什么?
26 4
|
2月前
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
67 0