【Spring基础系列4】注解@Transactional(二)

简介: 前面已经讲解了IOC的基础知识,以及Spring常用的注解,这篇文章是对上一篇文章《【Spring基础系列3】Spring常用的注解》的补充,由于这个注解需要讲述的内容比较多,一方面该注解非常重要,另一方面非常容易入坑,所以这个注解的内容,就单独放到这篇文章来讲。

事务不生效的几种case


所有的测试Case,测试完毕后,DB数据需要手动更新成原始数据,保证测试Case互不影响。


Case1: 类内部访问

简单来讲就是指非直接访问带注解标记的方法 B,而是通过类普通方法 A,然后由 A 访问 B,下面是一个简单的 case,我们在类UserController中新增一个方法testFail():

public void testFail() throws Exception {
    testSuccess();
    throw new Exception("测试事务回滚不生效");
}


这里我们是通过testFail()调用testSuccess(),再看一下测试用例:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testFail();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的记录:" + user);
    }
}
// 输出:
// 原记录:User[uid=1,uname=张三,usex=女]
// 修改后的记录:User[uid=1,uname=张三,usex=女]

从上面的输出可以看到,事务并没有回滚,主要是因为类内部调用,不会通过代理方式访问。


Case2: 私有方法

在私有方法上,添加@Transactional注解也不会生效,私有方法外部不能访问,所以只能内部访问,上面的 case 不生效,这个当然也不生效了:

@Transactional(rollbackFor = Exception.class)
private void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}

直接使用时,下面这种场景不太容易出现,因为 IDEA 会有提醒,文案为: Methods annotated with '@Transactional' must be overridable


Case3: 异常不匹配

@Transactional注解默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚:

@Transactional
public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}


测试 case 如下:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testSuccess();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的记录:" + user);
    }
}
// 输出:
// 原记录:User[uid=1,uname=张三,usex=女]
// 修改后的记录:User[uid=1,uname=张三-test,usex=女]

输出结果如下,事务并未回滚(如果需要解决这个问题,通过设置@Transactional的 rollbackFor 属性即可)


Case4: 多线程

这个场景可能并不多见,在标记事务的方法内部,另起子线程执行 db 操作,此时事务同样不会生效

下面给出两个不同的姿势,一个是子线程抛异常,主线程 ok;一个是子线程 ok,主线程抛异常。


父线程抛出异常

我们在类UserController中新增一个方法testMultThread(),该方法在主线程会抛出异常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
    throw new Exception("测试事务不生效");
}

上面这种场景不生效很好理解,子线程的异常不会被外部的线程捕获,testMultThread这个方法的调用不抛异常,因此不会触发事务回滚,调用方式和前面一样,就换一个方法:

uc.testMultThread();

这里提醒一下,输出的数据因为都是在主线程中输出,所以输出结果都是“张三”,后来我看了库表,发现DB数据已经更新为“张三-testing”,所以事务回滚没有生效。


子线程抛出异常

我们修改代码如下,让子任务抛出异常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
}

同上,DB数据没有回滚,发现DB数据已经更新为“张三-testing”,所以事务回滚没有生效。


Case5: 传播属性

这个内容,我后面再单独出一篇文章来讲,要不然这篇文章篇幅又太长了。


后记


感觉@Transactional这个注解需要了解的内容稍微有点多,不过这个注解是我们绕不开的,所以需要大家好好学习,特别是事务不生效的坑!其中有一个坑“类内部访问”,我刚开始写JAVA是就踩过,后来还是同事告诉我的,现在回顾当时的状态,感觉真的是菜的不能再菜了。

文章后面遗留了“传播属性”的坑,这个我就先放一下,等后面有时间我再写,然后关于Spring的部分,还剩下AOP,这几天我会先把相关知识看完,然后再通过文章的形式分享出来,敬请期待...

相关文章
|
3月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
570 128
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
401 2
|
3月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
391 1
使用Spring的@Retryable注解进行自动重试
|
3月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
302 12
|
3月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
191 0
探索Spring Boot的@Conditional注解的上下文配置
|
3月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
142 0
Spring中最大化@Lazy注解,实现资源高效利用
|
3月前
|
Java 测试技术 编译器
@GrpcService使用注解在 Spring Boot 中开始使用 gRPC
本文介绍了如何在Spring Boot应用中集成gRPC框架,使用`@GrpcService`注解实现高效、可扩展的服务间通信。内容涵盖gRPC与Protocol Buffers的原理、环境配置、服务定义与实现、测试方法等,帮助开发者快速构建高性能的微服务系统。
613 0
|
Java 数据库 Spring
【spring(四)】Spring事务管理和@Transactional注解
【spring(四)】Spring事务管理和@Transactional注解
227 0
|
数据库连接 数据库 开发者
Spring问题之使用@Transactional注解时需要注意哪些事项
Spring问题之使用@Transactional注解时需要注意哪些事项
163 4
|
XML Java 关系型数据库
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
483 0