前言
只有光头才能变强。
文本已收录至我的 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
https://zhuanlan.zhihu.com/p/41961670
《精通 Spring 4.x 企业应用开发实战》
《Spring 技术内幕》
乐于输出干货的 Java 技术公众号:Java3y。公众号内有 200 多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!
觉得我的文章写得不错,不妨点一下赞!