只有光头才能变强。
文本已收录至我的GitHub仓库,欢迎Star: https://github.com/ZhongFuCheng3y/3y
Spring事务管理我相信大家都用得很多,但可能仅仅局限于一个 @Transactional
注解或者在 XML
中配置事务相关的东西。不管怎么说,日常 可能 足够我们去用了。但作为程序员,无论是为了面试还是说更好把控自己写的代码,还是应该得多多了解一下Spring事务的一些细节。
这里我抛出几个问题,看大家能不能瞬间答得上:
Hibernate/JPA
或者是 Mybatis
,都知道的底层是需要一个 session/connection
对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用 同一个 session/connection
对象,而我们又知道Spring IOC所管理的对象默认都是 单例 的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么? 阅读这篇文章的同学我 默认 大家都对Spring事务相关知识有一定的了解了。(ps:如果不了解点解具体的文章去阅读再回到这里来哦)
我们都知道,Spring事务是Spring AOP的最佳实践之一,所以说 AOP入门基础知识(简单配置,使用) 是需要先知道的。如果想更加全面了解AOP可以看这篇文章: AOP重要知识点(术语介绍、全面使用) 。说到AOP就不能不说 AOP底层原理:动态代理设计模式 。到这里,对AOP已经有一个基础的认识了。于是我们就可以 使用XML/注解方式来配置Spring事务管理 。
在IOC学习中,可以知道的是 Spring中Bean的生命周期(引出BPP对象) 并且 IOC所管理的对象默认都是单例的:单例设计模式 ,单例对象如果有" 状态 "(有成员变量),那么多线程访问这个单例对象,可能就造成线程不安全。那么 何为线程安全? ,解决线程安全有很多方式,但其中有一种: 让每一个线程都拥有自己的一个变量:ThreadLocal
如果对我以上说的知识点不太了解的话,建议点击蓝字进去学习一番。
之前朋友问了我一个例子:
在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; }
我 第一反应 :不会回滚吧。
但朋友经过测试说,可以回滚阿。(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
结论:如果是编译时异常不会自动回滚, 如果是运行时异常,那会自动回滚 !
第二个例子来源于知乎@柳树文章,文末会给出相应的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事务的传播机制 没有关系 ,下面我讲述一下:
@Transactional
,那么会生成一个 代理对象 。 接下来我用图来说明一下:
显然地,我们拿到的是代理(Proxy)对象,调用 addEmployee2Controller()
方法,而 addEmployee2Controller()
方法的逻辑是 target.addEmployee()
,调用回原始对象(target)的 addEmployee()
。所以这次的调用 压根就没有事务存在 ,更谈不上说Spring事务传播机制了。
原有的数据:
测试结果:压根就没有事务的存在
从上面的测试我们可以发现:如果是在本类中没有事务的方法来调用标注注解 @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 AOP,Spring AOP底层用的动态代理,动态代理有两种方式:
基于接口代理(JDK代理)
基于CGLib代理(子类代理)
至于为啥以上的情况不能增强,用你们的脑瓜子想一下就知道了。
值得说明的是:那些不能被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中看一下:
BBP的全称叫做:BeanPostProcessor,一般我们俗称 对象后处理器
Spring管理Bean(或者说Bean的生命周期)也是一个 常考 的知识点,我在秋招也 重新 整理了一下步骤,因为比较重要,所以还是在这里贴一下吧:
配置/实现 配置/实现了 init-method
其中也有关于BPP图片:
Spring AOP编程底层通过的是动态代理技术,在调用的时候肯定用的是 代理对象 。那么Spring是怎么做的呢?
我只需要写一个BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,对对象进行判断,看他需不需要织入切面逻辑,如果需要,那我就根据这个对象,生成一个代理对象,然后返回这个代理对象,那么最终注入容器的,自然就是代理对象了。
Spring提供了BeanPostProcessor,就是让我们可以对有需要的对象进行“ 加工处理 ”啊!
Spring事务可以分为两种:
编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。
在编程式事务中有以下几个重要的了接口:
在声明式事务中,除了TransactionStatus和PlatformTransactionManager接口,还有几个重要的接口:
本文主要讲了Spring事务管理一些比较重要的知识点,当然在学习的过程中还看到其他的知识点,如果想要继续学习的同学不妨通过下面给出的参考资料继续阅读。
参考资料:
那些年,我们一起追的Spring
乐于输出 干货 的Java技术公众号:Java3y。公众号内有200多篇 原创 技术文章、海量视频资源、精美脑图,不妨来 关注 一下!
觉得我的文章写得不错,不妨点一下 赞 !