一文带你认识Spring事务
前言
只有光头才能变强。
文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y
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打脸)
看了一下文档,原来文档有说明:
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
,那么会生成一个代理对象。
接下来我用图来说明一下:
显然地,我们拿到的是代理(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事务管理的传播级别,内部方法也可以工作在外部方法所启动的事务上下文中。
至于Spring事务传播机制的几个级别,我在这里就不贴出来了。这里只是再次解释“啥情况才是属于Spring事务传播机制的范畴”。
四、多线程问题
我们使用的框架可能是
Hibernate/JPA
或者是Mybatis
,都知道的底层是需要一个session/connection
对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection
对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?
回想一下当年我们学Mybaits的时候,是怎么编写Session工具类?
没错,用的就是ThreadLocal,同样地,Spring也是用的ThreadLocal。
以下内容来源《精通 Spring4.x》
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的“状态性对象”采用ThreadLocal封装,让它们也成为线程安全的“状态性对象”,因此,有状态的Bean就能够以singleton的方式在多线程中工作。
我们可以试着点一下进去TransactionSynchronizationManager中看一下:
五、啥是BPP?
BBP的全称叫做:BeanPostProcessor,一般我们俗称对象后处理器
- 简单来说,通过BeanPostProcessor可以对我们的对象进行“加工处理”。
Spring管理Bean(或者说Bean的生命周期)也是一个常考的知识点,我在秋招也重新整理了一下步骤,因为比较重要,所以还是在这里贴一下吧:
- ResouceLoader加载配置信息
- BeanDefintionReader解析配置信息,生成一个一个的BeanDefintion
- BeanDefintion由BeanDefintionRegistry管理起来
- BeanFactoryPostProcessor对配置信息进行加工(也就是处理配置的信息,一般通过PropertyPlaceholderConfigurer来实现)
- 实例化Bean
- 如果该Bean
配置/实现
了InstantiationAwareBean,则调用对应的方法 - 使用BeanWarpper来完成对象之间的属性配置(依赖)
- 如果该Bean
配置/实现了
Aware接口,则调用对应的方法 - 如果该Bean配置了BeanPostProcessor的before方法,则调用
- 如果该Bean配置了
init-method
或者实现InstantiationBean,则调用对应的方法 - 如果该Bean配置了BeanPostProcessor的after方法,则调用
- 将对象放入到HashMap中
- 最后如果配置了destroy或者DisposableBean的方法,则执行销毁操作
其中也有关于BPP图片:
5.1为什么特意讲BPP?
Spring AOP编程底层通过的是动态代理技术,在调用的时候肯定用的是代理对象。那么Spring是怎么做的呢?
我只需要写一个BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,对对象进行判断,看他需不需要织入切面逻辑,如果需要,那我就根据这个对象,生成一个代理对象,然后返回这个代理对象,那么最终注入容器的,自然就是代理对象了。
Spring提供了BeanPostProcessor,就是让我们可以对有需要的对象进行“加工处理”啊!
六、认识Spring事务几个重要的接口
Spring事务可以分为两种:
- 编程式事务(通过代码的方式来实现事务)
- 声明式事务(通过配置的方式来实现事务)
编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。
在编程式事务中有以下几个重要的了接口:
- TransactionDefinition:定义了Spring兼容的事务属性(比如事务隔离级别、事务传播、事务超时、是否只读状态)
- TransactionStatus:代表了事务的具体运行状态(获取事务运行状态的信息,也可以通过该接口间接回滚事务等操作)
- PlatformTransactionManager:事务管理器接口(定义了一组行为,具体实现交由不同的持久化框架来完成---类比JDBC)
在声明式事务中,除了TransactionStatus和PlatformTransactionManager接口,还有几个重要的接口:
- TransactionProxyFactoryBean:生成代理对象
- TransactionInterceptor:实现对象的拦截
- TransactionAttrubute:事务配置的数据
最后
本文主要讲了Spring事务管理一些比较重要的知识点,当然在学习的过程中还看到其他的知识点,如果想要继续学习的同学不妨通过下面给出的参考资料继续阅读。
参考资料:
- 那些年,我们一起追的Spring
- 《精通Spring 4.x 企业应用开发实战》
- 《Spring技术内幕》
乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!
觉得我的文章写得不错,不妨点一下赞!
一文带你认识Spring事务的更多相关文章
- 一文带你深入浅出Spring 事务原理
Spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获 ...
- 阿里大牛带你深入分析spring事务传播行为
spring框架封装了很多有用的功能和组件,便于在项目开发中快速高效的调用,其中spring的事务使用非常简单,只需要在用到事务的地方加一行注解即可: 1@Transactional 但越是看起来简单 ...
- 一文带你了解Spring核心接口Ordered的实现及应用
前言 最近在看框架的时候,发现了这个接口,在此进行总结,希望能够给大家帮助,同时提升自己. order接口的大体介绍 Spring框架中有这个一个接口,名字叫Ordered,联想我们在数据库中应用的O ...
- 一文带你了解 Spring 5.0 WebFlux 应用场景
一.什么是 Spring WebFlux 下图截自 Spring Boot 官方网站: 结合上图,在了解 Spring WebFlux 之前,我们先来对比说说什么是 Spring MVC,这更有益我们 ...
- 一文带你掌握Spring Web异常处理方式
一.前言 大家好,我是 去哪里吃鱼 ,也叫小张. 最近从单位离职了,离开了五年多来朝朝夕夕皆灯火辉煌的某网,激情也好悲凉也罢,觥筹场上屡屡物是人非,调转过事业部以为能换种情绪,岂料和下了周五的班的前同 ...
- JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?一文带你厘清个中曲直,给你个选择SpringDataJPA的理由!
序言 Spring Data JPA作为Spring Data中对于关系型数据库支持的一种框架技术,属于ORM的一种,通过得当的使用,可以大大简化开发过程中对于数据操作的复杂度. 本文档隶属于< ...
- 【项目实践】一文带你搞定Spring Security + JWT
以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...
- 从源码入手,一文带你读懂Spring AOP面向切面编程
之前<零基础带你看Spring源码--IOC控制反转>详细讲了Spring容器的初始化和加载的原理,后面<你真的完全了解Java动态代理吗?看这篇就够了>介绍了下JDK的动态代 ...
- 一文搞定 Spring事务
Spring 事务 上文 使用SpringJDBC 1.JDBC事务控制 不管你现在使用的是那一种ORM开发框架,只要你的核心是JDBC,那么所有的事务处理都是围绕着JDBC开展的,而JDBC之中 ...
随机推荐
- 关于ajax原理阐述
ajax是什么呢?说白了就是一个请求,一个读取服务器资源以及提交资源到服务器的中间处理机制,那它具体是怎样工作的,又有怎样的原理呢?var ajax=function(url,fnSucceed,fn ...
- [Kali_Metasploit]db_connect创建连接时无法连接的解决方案
问题1复现路径: postgresql selected, no connection 第一步: db_connect postgres:toor@127.0.0.1/msfbook 连接成功不需要进 ...
- PAT1059:Prime Factors
1059. Prime Factors (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 HE, Qinming Given ...
- 最值得收藏的java技术博客(Java篇)
第一个:java_my_life 作者介绍:找不到原作者信息.大概做了翻阅全部是2012年的博客. 博客主要内容:主要内容是关于Java设计模式的一些讲解和学习笔记,在相信对学习设计模式的同学帮助很大 ...
- mongo Shell初体验
mongo shell是一个MongoDB的交互式JavaScript接口.您可以使用mongo shell来查询和更新数据以及执行管理操作. 打开cmd命令行,输入mongo,就可以进入mongo ...
- JFrame图形界面 ----鼠标消息
#开始 不管是什么GUI 按钮的存在都是必不可少的而且还会有很多奇怪的按钮 #代码 package window; import java.awt.Container; import java.awt ...
- @Scheduled不执行的原因
1. 今天用@Schedule做了一个定时任务,希望凌晨1点执行,代码如下 @Service public class ParseJsonService { @Scheduled(cron = &qu ...
- SVN使用教程2017.10.6
http://www.cnblogs.com/mq0036/p/5250198.html
- java 基础之枚举
问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个 解决办法: 1) 在setGrade方法中做判断,不符合格式要求就抛出异常 2) 直接限定用户的选择,通过自定义类模拟枚举的 ...
- [python3.5][PyUserInput]模拟鼠标和键盘模拟
一.PyUserInput安装 python3.5的PyMouse和PyKeyboard模块都集成到了PyUserInput模块中.在python3.5中,直接安装PyUserInput模块即可 Py ...