0

0

Spring事务的深入解析(附示例)

不言

不言

发布时间:2019-01-31 10:35:39

|

3099人浏览过

|

来源于博客园

转载

本篇文章给大家带来的内容是关于spring事务的深入解析(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

Spring事务管理我相信大家都用得很多,但可能仅仅局限于一个@Transactional注解或者在XML中配置事务相关的东西。不管怎么说,日常可能足够我们去用了。但作为程序员,无论是为了面试还是说更好把控自己写的代码,还是应该得多多了解一下Spring事务的一些细节。

这里我抛出几个问题,看大家能不能瞬间答得上:

  • 如果嵌套调用含有事务的方法,在Spring事务管理中,这属于哪个知识点?

  • 我们使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底层是需要一个session/connection对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?

  • 人家所说的BPP又是啥东西?

  • Spring事务管理重要接口有哪几个?

一、阅读本文需要的基础知识

阅读这篇文章的同学我默认大家都对Spring事务相关知识有一定的了解了。(ps:如果不了解点解具体的文章去阅读再回到这里来哦)

我们都知道,Spring事务是Spring AOP的最佳实践之一,所以说AOP入门基础知识(简单配置,使用)是需要先知道的。如果想更加全面了解AOP可以看这篇文章:AOP重要知识点(术语介绍、全面使用)。说到AOP就不能不说AOP底层原理:动态代理设计模式。到这里,对AOP已经有一个基础的认识了。于是我们就可以使用XML/注解方式来配置Spring事务管理。

在IOC学习中,可以知道的是Spring中Bean的生命周期(引出BPP对象)并且IOC所管理的对象默认都是单例的:单例设计模式,单例对象如果有"状态"(有成员变量),那么多线程访问这个单例对象,可能就造成线程不安全。那么何为线程安全?,解决线程安全有很多方式,但其中有一种:让每一个线程都拥有自己的一个变量:ThreadLocal

如果对我以上说的知识点不太了解的话,建议点击蓝字进去学习一番。

二、两个不靠谱直觉的例子

2.1第一个例子

之前朋友问了我一个例子:

在Service层抛出Exception,在Controller层捕获,那如果在Service中有异常,那会事务回滚吗?

// Service方法
    
@Transactional
public Employee addEmployee() throws Exception {

    Employee employee = new Employee("3y", 23);
    employeeRepository.save(employee);
    // 假设这里出了Exception
    int i = 1 / 0;

    return employee;
}

// Controller调用
@RequestMapping("/add")
public Employee addEmployee() {
    Employee employee = null;
    try {
        employee = employeeService.addEmployee();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return employee;

}

第一反应:不会回滚吧。

  • 我当时是这样想的:因为Service层已经抛出了异常,由Controller捕获。那是否回滚应该由Controller的catch代码块中逻辑来决定,如果catch代码块没有回滚,那应该是不会回滚。

但朋友经过测试说,可以回滚阿。(pappapa打脸)

发生了运行时Exception,Spring事务管理自动回滚

看了一下文档,原来文档有说明:

By default checked exceptions do not result in the transactional interceptor marking the transaction for rollback and instances of RuntimeException and its subclasses do

结论:如果是编译时异常不会自动回滚,如果是运行时异常,那会自动回滚

2.2第二个例子

第二个例子来源于知乎@柳树文章,文末会给出相应的URL

我们都知道,带有@Transactional注解所包围的方法就能被Spring事务管理起来,那如果我在当前类下使用一个没有事务的方法去调用一个有事务的方法,那我们这次调用会怎么样?是否会有事务呢?

用代码来描述一下:

// 没有事务的方法去调用有事务的方法
public Employee addEmployee2Controller() throws Exception {

    return this.addEmployee();
}

@Transactional
public Employee addEmployee() throws Exception {

    employeeRepository.deleteAll();
    Employee employee = new Employee("3y", 23);

    // 模拟异常
    int i = 1 / 0;

    return employee;
}

我第一直觉是:这跟Spring事务的传播机制有关吧。

其实这跟Spring事务的传播机制没有关系,下面我讲述一下:

  • Spring事务管理用的是AOP,AOP底层用的是动态代理。所以如果我们在类或者方法上标注注解@Transactional,那么会生成一个代理对象

接下来我用图来说明一下:

Spring会自动生成代理对象

显然地,我们拿到的是代理(Proxy)对象,调用addEmployee2Controller()方法,而addEmployee2Controller()方法的逻辑是target.addEmployee(),调用回原始对象(target)的addEmployee()。所以这次的调用压根就没有事务存在,更谈不上说Spring事务传播机制了。

原有的数据:

原有的数据

测试结果:压根就没有事务的存在

没有事务的存在

2.2.1再延伸一下

从上面的测试我们可以发现:如果是在本类中没有事务的方法来调用标注注解@Transactional方法,最后的结论是没有事务的。那如果我将这个标注注解的方法移到别的Service对象上,有没有事务?

@Service
public class TestService {

    @Autowired
    private EmployeeRepository employeeRepository;
    
    @Transactional
    public Employee addEmployee() throws Exception {

        employeeRepository.deleteAll();

        Employee employee = new Employee("3y", 23);

        // 模拟异常
        int i = 1 / 0;

        return employee;
    }

}


@Service
public class EmployeeService {

    @Autowired
    private TestService testService;
    // 没有事务的方法去调用别的类有事务的方法
    public Employee addEmployee2Controller() throws Exception {
        return testService.addEmployee();
    }
}

测试结果:

抛出了运行时异常,但我们的数据还是存在的!

因为我们用的是代理对象(Proxy)去调用addEmployee()方法,那就当然有事务了。

看完这两个例子,有没有觉得3y的直觉是真的水

三、Spring事务传播机制

如果嵌套调用含有事务的方法,在Spring事务管理中,这属于哪个知识点?

在当前含有事务方法内部调用其他的方法(无论该方法是否含有事务),这就属于Spring事务传播机制的知识点范畴了。

Spring事务基于Spring AOP,Spring AOP底层用的动态代理,动态代理有两种方式:

  • 基于接口代理(JDK代理)

    • 基于接口代理,凡是类的方法非public修饰,或者用了static关键字修饰,那这些方法都不能被Spring AOP增强

  • 基于CGLib代理(子类代理)

    • 基于子类代理,凡是类的方法使用了private、static、final修饰,那这些方法都不能被Spring AOP增强

至于为啥以上的情况不能增强,用你们的脑瓜子想一下就知道了。

值得说明的是:那些不能被Spring AOP增强的方法并不是不能在事务环境下工作了。只要它们被外层的事务方法调用了,由于Spring事务管理的传播级别,内部方法也可以工作在外部方法所启动的事务上下文中

PHP5 和 MySQL 圣经
PHP5 和 MySQL 圣经

本书是全面讲述PHP与MySQL的经典之作,书中不但全面介绍了两种技术的核心特性,还讲解了如何高效地结合这两种技术构建健壮的数据驱动的应用程序。本书涵盖了两种技术新版本中出现的最新特性,书中大量实际的示例和深入的分析均来自于作者在这方面多年的专业经验,可用于解决开发者在实际中所面临的各种挑战。

下载

至于Spring事务传播机制的几个级别,我在这里就不贴出来了。这里只是再次解释“啥情况才是属于Spring事务传播机制的范畴”。

四、多线程问题

我们使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底层是需要一个session/connection对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?

回想一下当年我们学Mybaits的时候,是怎么编写Session工具类?

Mybatis工具类部分代码截图

没错,用的就是ThreadLocal,同样地,Spring也是用的ThreadLocal。

以下内容来源《精通 Spring4.x》

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的“状态性对象”采用ThreadLocal封装,让它们也成为线程安全的“状态性对象”,因此,有状态的Bean就能够以singleton的方式在多线程中工作。

我们可以试着点一下进去TransactionSynchronizationManager中看一下:

全都是ThreadLocal

五、啥是BPP?

BBP的全称叫做:BeanPostProcessor,一般我们俗称对象后处理器

  • 简单来说,通过BeanPostProcessor可以对我们的对象进行“加工处理”。

Spring管理Bean(或者说Bean的生命周期)也是一个常考的知识点,我在秋招也重新整理了一下步骤,因为比较重要,所以还是在这里贴一下吧:

  1. ResouceLoader加载配置信息

  2. BeanDefintionReader解析配置信息,生成一个一个的BeanDefintion

  3. BeanDefintion由BeanDefintionRegistry管理起来

  4. BeanFactoryPostProcessor对配置信息进行加工(也就是处理配置的信息,一般通过PropertyPlaceholderConfigurer来实现)

  5. 实例化Bean

  6. 如果该Bean配置/实现了InstantiationAwareBean,则调用对应的方法

  7. 使用BeanWarpper来完成对象之间的属性配置(依赖)

  8. 如果该Bean配置/实现了Aware接口,则调用对应的方法

  9. 如果该Bean配置了BeanPostProcessor的before方法,则调用

  10. 如果该Bean配置了init-method或者实现InstantiationBean,则调用对应的方法

  11. 如果该Bean配置了BeanPostProcessor的after方法,则调用

  12. 将对象放入到HashMap中

  13. 最后如果配置了destroy或者DisposableBean的方法,则执行销毁操作

Application中Bean的声明周期

其中也有关于BPP图片:

BBP所在的位置

5.1为什么特意讲BPP?

Spring AOP编程底层通过的是动态代理技术,在调用的时候肯定用的是代理对象。那么Spring是怎么做的呢?

我只需要写一个BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,对对象进行判断,看他需不需要织入切面逻辑,如果需要,那我就根据这个对象,生成一个代理对象,然后返回这个代理对象,那么最终注入容器的,自然就是代理对象了。

Spring提供了BeanPostProcessor,就是让我们可以对有需要的对象进行“加工处理”啊!

六、认识Spring事务几个重要的接口

Spring事务可以分为两种:

  • 编程式事务(通过代码的方式来实现事务)

  • 声明式事务(通过配置的方式来实现事务)

编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。

在编程式事务中有以下几个重要的了接口:

  • TransactionDefinition:定义了Spring兼容的事务属性(比如事务隔离级别、事务传播、事务超时、是否只读状态)

  • TransactionStatus:代表了事务的具体运行状态(获取事务运行状态的信息,也可以通过该接口间接回滚事务等操作)

  • PlatformTransactionManager:事务管理器接口(定义了一组行为,具体实现交由不同的持久化框架来完成---类比JDBC)

PlatformTransactionManager解析

在声明式事务中,除了TransactionStatus和PlatformTransactionManager接口,还有几个重要的接口:

  • TransactionProxyFactoryBean:生成代理对象

  • TransactionInterceptor:实现对象的拦截

  • TransactionAttrubute:事务配置的数据

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

98

2025.08.06

hibernate和mybatis有哪些区别
hibernate和mybatis有哪些区别

hibernate和mybatis的区别:1、实现方式;2、性能;3、对象管理的对比;4、缓存机制。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

137

2024.02.23

Hibernate框架介绍
Hibernate框架介绍

本专题整合了hibernate框架相关内容,阅读专题下面的文章了解更多详细内容。

76

2025.08.06

Java Hibernate框架
Java Hibernate框架

本专题聚焦 Java 主流 ORM 框架 Hibernate 的学习与应用,系统讲解对象关系映射、实体类与表映射、HQL 查询、事务管理、缓存机制与性能优化。通过电商平台、企业管理系统和博客项目等实战案例,帮助学员掌握 Hibernate 在持久层开发中的核心技能。

32

2025.09.02

Hibernate框架搭建
Hibernate框架搭建

本专题整合了Hibernate框架用法,阅读专题下面的文章了解更多详细内容。

64

2025.10.14

mybatis一级缓存和二级缓存
mybatis一级缓存和二级缓存

在MyBatis中,一级缓存和二级缓存是两种不同级别的缓存机制,它们都可以用来提高性能。本专题提供mybatis一级缓存和二级缓存相关文章,大家可以免费阅读。

296

2023.08.21

ibatis和mybatis有什么区别
ibatis和mybatis有什么区别

ibatis和mybatis的区别:1、基本信息不同;2、开发时间不同;3、功能与易用性;4、配置文件;5、入参类型与出参类型;6、返回结果集接受方式;7、语法差异;8、数据库方言支持;9、插件支持;10、社区活跃度;11、全球化支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

78

2024.02.23

mybatis如何配置数据库连接
mybatis如何配置数据库连接

mybatis配置数据库连接的方法:1、指定数据源;2、配置事务管理器;3、配置类型处理器和映射器;4、使用环境元素;5、配置别名。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

100

2024.02.23

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号