Spring事务的1道面试题

简介: 每次聊起Spring事务,好像很熟悉,又好像很陌生。本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。

每次聊起Spring事务,好像很熟悉,又好像很陌生。本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。

原理

Spring事务的原理是:通过AOP切面的方式实现的,也就是通过代理模式去实现事务增强。

具体过程是:对包含@Transactional注解的方法进行拦截,然后重写,重新在方法里加入异常回滚的逻辑。而且,每个线程都是独立管理自己的事务,相互隔离。

原理简单,使用起来也简单,也就是在方法上打上@Transactional注解,然后事务就正常生效了。也很少有人去验证异常情况下是否能真正的回滚。

Spring事务让我熟悉的地方是哪哪看起来都简单,让我陌生的地方使用时的变种较多,有时候莫名其妙的不生效。

源码

以上原理的相关源码如下:

实践出真知

但是 [半支烟] 偶尔会在编码过程中发现有些场景下的事务是失效的,总有些情况让你想不到,总有一些坑点等你去跳。

[半支烟] 觉得验证事务的最好方式就是:记住基本原则 + 动手实践。记住基本原则可以快速处理常规问题,动手实践可以验证偏门问题或者不确定的问题。

几种事务不生效的用法

如下是常见的几种Spring事务不生效的用法,有空的读者一定要牢记,对日常编码很有帮助,同时面试时也能说几句。

  • private方法

Spring是通过AOP代理的方式实现事务增强的,但是private方法无法被代理,所以在private方法上打@Transactional注解是不生效的。

  • final、static修饰的方法

和private方法类似,final和static修饰的方法也无法被代理,所以@Transactional注解也不生效。

因为,static是属于类方法,final修饰的方法无法被重写,自然也就无法植入事务增强代码。

  • Bean对象没有被Spring托管

某个类一定要被Spring托管,那才能通过@Transactional注解去增强事务。如果只有@Transactional注解,而没有把类交给Spring托管,事务也是不生效的。类似如下情况:

// 此处没有@Service注解,此类不被spring托管,及时有@Transactional也不生效
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public final void createAndUpdateUser() {
        createUser();
        updateUserById();
    }

    public void createUser() {
        User user = new User();
        user.setId(2L);
        user.setName("test2");
        user.setEmail("test2" + "@test.com");
        userMapper.insert(user);
        System.out.println("create user");
    }

    public void updateUserById() {
        User user = userMapper.findById(1L);
        user.setName("admin1");
        userMapper.update(user);
        int i = 1 / 0; // 此处会抛出异常
        System.out.println("update user");
    }
}
  • 异常被吞掉

如果在业务代码里,通过try......catch捕获了异常,同时又没有继续抛出异常时,Spring事务也是不生效的。

因为代理增强的逻辑就是要发现了异常,才能回滚事务。如果异常被方法本身吞掉了,则代理会认为没有异常,从而无法回滚。

  • 非RuntimeException异常

Spring事务默认会回滚RuntimeException 及其子类,以及 Error 类型的异常。如果是其余异常,则不会回滚。源码处可见:

这种非RuntimeException异常场景下,需要做2个动作从而保证事务回滚。

  1. 捕获异常,然后抛出自定义异常。
  2. 自行在@Transactional注解中增加@Transactional(rollbackFor = XxxxxxxException.class)属性。或者直接使用rollbackFor = Exception.class,也就免去了第一步。
  • 异步线程的场景

多个线程的场景下,只需要牢记每个线程只管理自己的事务即可。每个线程都有一个独立的事务上下文,存在ThreadLocal中,所以事务信息在不同线程之间是隔离的。

  • 重灾区:在同一个类中调用本类的方法

这个失效场景,是最容易出错的,而且变种还多。在同一个类中调用本类的方法时,牢记以下2点,即可破局:

  1. 是否会开启事务依赖此类的第一个被外部调用的方法。如果此类的第一个被外部调用的方法有@Transactional注解,那事务生效。
  2. 调用自己内部方法时,采用的是this.xxxMethod()的方式,这种方式是不会走AOP代理的,所以被调用的内部方法的@Transactional注解不生效。

如果确实需要调用内部方法,并且要事务生效的话,那只能将被调用的内部方法独立到新的类中,同时交给Spring管理。

一道面试题

以上关于事务不生效的用法都比较好记,只有在同一个类中调用本类的方法场景下存在多种变种。具体请看这道面试题。请问以下createAndUpdateUser方法的事务生效吗?

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public final void createAndUpdateUser() { //注意这里有final修饰
        createUser();
        updateUserById();
    }

    @Transactional
    public void createUser() {
        User user = new User();
        user.setId(2L);
        user.setName("test2");
        user.setEmail("test2" + "@test.com");
        userMapper.insert(user);
        System.out.println("create user");
    }


    @Transactional(rollbackFor = Exception.class)
    public void updateUserById() {
        User user = userMapper.findById(1L);
        user.setName("admin1");
        userMapper.update(user);
        int i = 1 / 0; // 此处会抛出异常
        System.out.println("update user");
    }
}

如果按照重灾区:在同一个类中调用本类的方法里提到的2个原则,则事务全部生效。

如果按照final、static修饰的方法里提到的原则,则事务全部不生效。

那结果如何呢?结果是以上方法的事务全部生效。

为什么呢?这里在补充一个原则:final修饰的方法如果带上@Transactional注解,事务情况按照被调用的方法自身的事务托管情况而定。

因为以上代码中的createUser方法和updateUserById方法,都有@Transactional注解,所以都生效。

这种特殊情况也实在是让人瞠目,不过只需要牢记以上几种不生效的用法即可,谁没事儿写这种@Transactional + final的代码呢?除了面试会问......

总结

本篇主要聊了几种事务不生效的用户,有兴趣的读者可以记一下。同时,还出了一道特殊场景的面试题,供读者自行实践。希望对你有帮助!

本篇完结!欢迎 关注、交流、全网可搜(程序员半支烟)

原文链接:https://mp.weixin.qq.com/s/V5KpVk0kDhc9vWctOy7X9A

相关文章
|
2月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
14天前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
17天前
|
Java 测试技术 数据库
Spring事务传播机制(最全示例)
在使用Spring框架进行开发时,`service`层的方法通常带有事务。本文详细探讨了Spring事务在多个方法间的传播机制,主要包括7种传播类型:`REQUIRED`、`SUPPORTS`、`MANDATORY`、`REQUIRES_NEW`、`NOT_SUPPORTED`、`NEVER` 和 `NESTED`。通过示例代码和数据库插入测试,逐一展示了每种类型的运作方式。例如,`REQUIRED`表示如果当前存在事务则加入该事务,否则创建新事务;`SUPPORTS`表示如果当前存在事务则加入,否则以非事务方式执行;`MANDATORY`表示必须在现有事务中运行,否则抛出异常;
57 4
Spring事务传播机制(最全示例)
|
2天前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
13 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
12天前
|
Java Spring
Spring 事务传播机制是什么?
Spring 事务传播机制是什么?
17 4
|
15天前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
57 3
|
3天前
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
12 0
|
2月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
2月前
|
前端开发 Java 数据库连接
一天十道Java面试题----第五天(spring的事务传播机制------>mybatis的优缺点)
这篇文章总结了Java面试中的十个问题,包括Spring事务传播机制、Spring事务失效条件、Bean自动装配方式、Spring、Spring MVC和Spring Boot的区别、Spring MVC的工作流程和主要组件、Spring Boot的自动配置原理和Starter概念、嵌入式服务器的使用原因,以及MyBatis的优缺点。
|
2月前
|
前端开发 Java 开发者
Spring常见面试总结(上)
Spring框架是为Java应用提供全面支持的平台,帮助开发者处理基础任务,专注于业务逻辑。它具备IOC(控制反转)和AOP(面向切面编程)等功能,支持MVC架构、事务管理和JDBC异常处理。Spring的IOC容器负责对象的创建、配置及生命周期管理。依赖注入包括构造函数、setter和接口注入等方式。`@Component`、`@Controller`、`@Repository`和`@Service`等注解用于组件识别和装配。`@Autowired`用于精确控制依赖注入。
20 0