小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
文章涉及的源码均已上传到了码云,参考【README.md】文件部署运行即可
码云项目:spring-boot源码注释版 git@gitee.com:tangjingshan/spring-boot.git
码云路径:{@link org.springframework.study.dataflow.study_transactional.a_findStrartTraClass#main}
前言
本文主要总结下spring声明式事务的底层核心源码是如何实现的 涉及jar包:
compile project(":spring-jdbc")
compile project(":spring-tx")
implementation group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.7.0'
implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.23'
涉及表结构:
DROP TABLE IF EXISTS `user_test`;
CREATE TABLE `user_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) DEFAULT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
INSERT INTO `user_test` VALUES (1, 'a', 10);
INSERT INTO `user_test` VALUES (2, 'b', 20);
相关知识点
1. MethodInterceptor实现aop
使用@Aspect这种方式去实现声明式aop,想必大家都用过了。spring除了注解实现aop,还可以通过实现 MethodInterceptor接口去实现aop,demo如下
public class MethodInterceptorTest {
public static void main(String[] args) {
ProxyFactory proxyFactory=new ProxyFactory();
proxyFactory.setTarget(new MethodInterceptorTest());
proxyFactory.addAdvice(new TransactionInterceptor());
Object proxy = proxyFactory.getProxy();
MethodInterceptorTest methodInterceptor = (MethodInterceptorTest) proxy;
methodInterceptor.doSomething();
}
public void doSomething(){
System.out.println("开始操作数据库。。。。");
}
static class TransactionInterceptor implements MethodInterceptor {
@Nullable
@Override
public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
System.out.println("方法执行前...");
Object res = invocation.proceed();
System.out.println("方法执行后...");
return res;
}
}
}
2. ThreadLocal
ThreadLocal当前线程的局部变量,当前线程在java中也不过是一个对象,而ThreadLoacl则就是当前线程对象的一个成员属性,所以不同线程->不同线程对象->ThreadLocal当前线程对象私有
其可以很便利的解决跨方法传参的问题。比如以下代码需求
son方法接收一个troNo形参,然后调用了father方法,father方法也要用到troNo形参
简单的做法如下
static class Test1 {
public void son(String troNo) {
System.out.println("son: " + troNo);
this.father(troNo);
}
public void father(String troNo) {
System.out.println("father: " + troNo);
}
}
此种做法,如果father方法内部又调用了很多其他方法呢,这些方法都有可能用到troNo形参,总不能每一个方法都加上形参troNo吧
这个时候就可以用ThreadLocal来解决
static ThreadLocal<String> localVar = new ThreadLocal<>();
static class Test2 {
public void son(String troNo) {
System.out.println("son2: " + troNo);
localVar.set(troNo);// 绑定当前线程
this.father();
}
public void father() {
String troNo = localVar.get();// 从当前线程获取值
System.out.println("father2: " + troNo);
}
}
但是使用ThreadLoacl必须当前线程本次用完后必须要显示的释放资源,如下
try {
new Test1().son("aaa");
new Test2().son("bbb");
}finally {
// 显式清除资源
localVar.remove();
//localVar.set(null);
}
原因: 因为执行当前方法的当前线程A,是服务器(tomact等)的线程池中的某一个,当前方法执行完后,当前线程A并不会被销毁,而是回到线程池中。所以Thread.ThreadLocal依然存在于线程A,ThreadLocal的值B也不会被GC回收,如果该值B很大,多条线程都持有不可回收的值B,最终就会导致内存泄漏
我们都试过,一个service方法A,调用了另一个service方法B,此时如果方法B失败了,方法A、B都会回滚,但是方法B的形参上却没有显示的接收一个A创建的Connection
ThreadLocal在spring事务中的具体用途,主要就是用来处理Connection的传播,也就是事务传播级别的实现。后面的代码会具体讲到
一. 如何使用
1. 原生命令行版事务
- 方法1
START TRANSACTION;// 开启事务
INSERT INTO `tjs_mydemo`.`user_test`(`user_name`, `balance`) VALUES ( 'c', 30);
ROLLBACK;// 回滚事务
SELECT * FROM user_test ut;
- 方法2
SET autocommit= 0; //0-关闭自动提交 1/默认-开启自动提交
INSERT INTO `tjs_mydemo`.`user_test`(`user_name`, `balance`) VALUES ( 'd', 40);
ROLLBACK;
SELECT * FROM user_test ut;
spring是采用的方法2
2. 原生jdbc版事务
- 方法1-java版
private static void transactionV1() throws Exception {
JDBCUtils.printAll("transactionV1之前");
try (Connection conn = JDBCUtils.getConnection()) {
try (Statement stmt = conn.createStatement()) {
String sql = "START TRANSACTION;";
stmt.execute(sql);
}
try (Statement stmt = conn.createStatement()) {
String sql = "INSERT INTO `tjs_mydemo`.`user_test`(`user_name`, `balance`) VALUES ( 'c3', 110);";
stmt.execute(sql);
}
conn.rollback();
//throw new RuntimeException("测试回滚。。。");
} catch (Exception ex) {
ex.printStackTrace();
}
JDBCUtils.printAll("transactionV1之后");
}
private static void transactionV2() throws Exception {
JDBCUtils.printAll("transactionV2之前");
try (Connection conn = JDBCUtils.getConnection()) {
conn.setAutoCommit(false);// 这个方法内部也是执行的"SET autocommit= 0;"
// try (Statement stmt = conn.createStatement()) {
// String sql = "SET autocommit= 0;";
// stmt.execute(sql);
// }
try (Statement stmt = conn.createStatement()) {
String sql = "INSERT INTO `tjs_mydemo`.`user_test`(`user_name`, `balance`) VALUES ( 'c2', 120);";
stmt.execute(sql);
}
conn.rollback();
//throw new RuntimeException("测试回滚。。。");
} catch (Exception ex) {
ex.printStackTrace();
}
JDBCUtils.printAll("transactionV2之后");
}
- 完整代码见码云项目
路径:org.springframework.study.dataflow.study_transactional.JDBCTransaction
3. spring声明式事务(测试程序)
springboot声明式事务只是对配置进行了一次封装,最后再说这一块。
先从spring的声明式事务说起。其配置如下
- 测试类
@Transactional
@Service
public class TransactionalService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
TransactionalService transactionalService;
//@Transactional(propagation = Propagation.NEVER)//非事物方式运行
public void updateSonOfBalance(boolean isThrowException) {
jdbcTemplate.execute("UPDATE user_test ut SET ut.balance = ut.balance + 1 WHERE ut.id = 1;");
// 注入的bean,才能走代理
transactionalService.updateFatherOfUserName(isThrowException);
// 直接调用updateFatherOfUserName,updateFatherOfUserName是不会走代理的
// 但是如果此前,已经绑定了已关闭自动提交的Connection到ThreadLocal,则即使没有走代理,也会是在同一事物下
// this.updateFatherOfUserName(isThrowException);
}
//@Transactional(propagation = Propagation.REQUIRES_NEW)//创建一个新事务,如果当前存在事务,将这个事务挂起
public void updateFatherOfUserName(boolean isThrowException) {
try {
jdbcTemplate.execute("UPDATE user_test ut SET ut.user_name = CONCAT(ut.user_name, "_", ut.balance+1) WHERE ut.id = 1;");
if (isThrowException) {
throw new RuntimeException("test rollback.....");
}
System.out.println("add success...");
} catch (Exception ex) {
System.out.println("add fail, star rollback...");
throw ex;
}
}
public void find(String pre) {
RowMapper<UserTest> userInfoRowMapper = new RowMapper<UserTest>() {
@Override
public UserTest mapRow(ResultSet rs, int rowNum) throws SQLException {
UserTest userTest = new UserTest();
userTest.setId(rs.getInt("id"));
userTest.setUserName(rs.getString("user_name"));
userTest.setBalance(rs.getInt("balance"));
return userTest;
}
};
List<UserTest> userTests = jdbcTemplate.query("select * from user_test ut where ut.id = 1;", userInfoRowMapper);
System.out.println(pre + ":" + JSON.toJSONString(userTests));
}
}
public class UserTest implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String userName;
private Integer balance;
}
- 配置类
@Configuration
@ComponentScan
@EnableTransactionManagement//开启事物
public class a_findStrartTraClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(a_findStrartTraClass.class);
testUpdate(context);
}
private static void testUpdate(AnnotationConfigApplicationContext context) {
TransactionalService transactionalService = context.getBean(TransactionalService.class);
//transactionalService.find("before add");
try {
//transactionalService.updateSonOfBalance(true);
transactionalService.updateFatherOfUserName(true);
}catch (Exception ex){
ex.printStackTrace();
System.out.println("add fail, end rollback...");
}
//transactionalService.find("after add");
}
@Bean("jdbcTemplate")
public JdbcTemplate initJdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.initBasicDataSource());
return jdbcTemplate;
}
@Bean
public TransactionManager initTransactionManager() {
org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.initBasicDataSource());
return transactionManager;
}
@Bean("dataSource")
public BasicDataSource initBasicDataSource() {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setUrl("jdbc:mysql://localhost:3306/tjs_mydemo");
basicDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
basicDataSource.setUsername("root");
basicDataSource.setPassword("qx123456");
return basicDataSource;
}
}
二. 制定阅读计划
- 切入点1 spring声明式事务的相关代码很多,如果你要从@Transactional的代理切面去入手,从上往下去看源码的话,代码的调用链,调用分支将会异常复杂
- 切入点2
所以,换个切入点。不管怎么样,spring最终还是使用jdk定义的jdbc接口,去调用mysql或其他厂商的具体jdbc接口实现类去操作数据库的
那么,是否可以在jdbc的关键节点的接口打上断点,从下往上,逆推,去研究每一个节点调用链;然后再从上往下,串联所有关键节点,是否会更快速?
仔细观察jdbc事务的java版本代码,可以知道其主要分为下面几个步骤
- 创建Connection
{@link java.sql.Driver#connect}
- 开启事务
{@link java.sql.Connection#setAutoCommit}
- 创建Statement
{@link java.sql.Connection#createStatement()}
- 执行目标sql
{@link java.sql.Statement#execute(java.lang.String)}
- 回滚事务
{@link java.sql.Connection#rollback()}
- 提交事务
{@link java.sql.Connection#commit}
- 关闭相关资源
这一步先不管,但是要注意,setAutoCommit(false)时,Connection#close方法,会执行Connection#rollback方法
if (!getAutoCommit() && issueRollback) {
rollback();
}
那么接下来就去寻找这些步骤在spring何处触发
- 切入点3
切入点2,理论上是可行的,但是还有会有点绕,因为实际操作后会发现,除了你自己的业务sql,spring启动时还会执行一些其他初始化时会调用jdbc相关的api,导致不好调试
所以,再更进一步,spring操作数据库,最终是使用JdbcTemplate去操作的,具体demo见spring声明式事务,我们点进org.springframework.jdbc.core.JdbcTemplate#execute(org.springframework.jdbc.core.StatementCallback<T>, boolean)
方法看看里面具体怎么实现的
@Autowired
JdbcTemplate jdbcTemplate;
//@Transactional(propagation = Propagation.NEVER)
public void updateSonOfBalance(boolean isThrowException) {
jdbcTemplate.execute("UPDATE user_test ut SET ut.balance = ut.balance + 1 WHERE ut.id = 1;");
如果有兴趣,可以点进去以上标红的地方看看,可以发现其最终还是调用的切入点2中说的,mysql对jdk定义的jdbc接口的实现类 所以我们就在标红的地方打上断点,并结合切入点2,开始追踪
PS: 本次研究只研究到截止于jdbc提供的api,这些api具体的源码实现就不在本次研究范围内了
三. 开始阅读
在切入点3
所标红的三处打上断点,重启测试程序a_findStrartTraClass
1. 创建Connection
1.1 Connection存在哪
调用链A:
obtainDataSource()方法只不过返回了数据库连接池的配置实例DataSource,无非就是哪里注入的问题,暂不追究。
分析调用链A可以发现,Connection是调用ConnectionHolder#getConnection()
方法返回的
所以接下来就是看ConnectionHolder如何获取的,这次不需要重启应用程序,直接使用debug的回退功能
,回到调用当前方法前的那行代码,再重新步入即可
调用链A1:
分析调用链A1可以发现,ConnectionHolder是存在静态常量org.springframework.transaction.support.TransactionSynchronizationManager#resources
中的,其为ThreadLoacl
1.2 该ThreadLoacl何时存入
ThreadLoacl是如何存入值的?大概率是通过ThreadLoacl.set(xxx)赋值的,那么我们全局搜索TransactionSynchronizationManager文件,关键字为resources.set
,可以发现只有一处org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
调用了,我们再此处打上断点,重启测试程序
调用链B:
分析调用链B可以发现,存入threadLocal的值来自于txObject.getConnectionHolder()
,既然有getXXX,那么势必就会有setConnectionHolder方法
所以我们就在org.springframework.jdbc.datasource.JdbcTransactionObjectSupport#setConnectionHolder
方法打上断点,重启测试程序
调用链B1:
PS: 这个断点会进两次,第一次为null直接跳过,只分析第二次不为null的那条
分析调用链B1可以发现,setConnectionHolder方法是在org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
处被调用。而且Connection是由数据库连接池javax.sql.DataSource#getConnection()
创建的
那么数据库连接池的这个getConnection方法是否会调用前面【切入点2】分析时的{@link java.sql.Driver#connect}
这个接口的实现类呢?
我们先在程序doBegin方法里的Connection newCon = obtainDataSource().getConnection();
这一行代码打上断点,然后等程序运行到这一行的时候,再在java.sql.Driver#connect
打上断点
调用链B2:
分析调用链B2可以发现,果然创建Connection最终还是调用了jdk定义的java.sql.Driver#connect
接口的,mysql实现类com.mysql.cj.jdbc.NonRegisteringDriver#connect
1.3 doBegin方法何时调用
继续分析调用链B2,得到调用链B3:
分析调用链B3可以发现,doBegin方法是org.springframework.transaction.interceptor.TransactionInterceptor#invoke
处调用的,而TransactionInterceptor实现了MethodInterceptor
,为transaction的切面类。
再往上分析,就是spring-aop模块的知识了,与本文研究的主要流程无关,暂时不管,这里就只贴下transaction代理的切点位置
* aop配置类
* {@link org.springframework.transaction.config.TransactionManagementConfigUtils#TRANSACTION_ADVISOR_BEAN_NAME}
* aop切点
* {@link org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut.TransactionAttributeSourceClassFilter#matches(java.lang.Class)}
* {@link org.springframework.transaction.annotation.SpringTransactionAnnotationParser#isCandidateClass(java.lang.Class)}
* (AnnotationUtils.isCandidateClass(targetClass, Transactional.class);// 是否持有@Transactional注解)
1.4 结论
经过前面三大步的分析,逆着总结下,所有数据流向就都串起来了
- 判断当前bean是否持有@Transactional注解
{@link org.springframework.transaction.annotation.SpringTransactionAnnotationParser#isCandidateClass(java.lang.Class)}
- 使用切面类对目标方法增强
{@link org.springframework.transaction.interceptor.TransactionInterceptor#invoke}
- 使用数据库连接池创建对应数据源的连接Connection
{@link org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin}
(Connection newCon = obtainDataSource().getConnection();)
-
下一节补充
-
存入ThreadLoacl
{@link org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin}
{@link org.springframework.transaction.support.TransactionSynchronizationManager#bindResource}
(TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());)
- 需要获取连接的时候,从ThreadLoacl拿
{@link org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource}
2. 开启事务
直接在java.sql.Connection#setAutoCommit
这个接口打上断点,重启测试程序即可
调用链C:
分析调用链C可以发现,开启事务的setAutoCommit
方法也是在DataSourceTransactionManager#doBegin
时,调用org.apache.commons.dbcp2.DelegatingConnection#setAutoCommit
方法赋值的。
再整体看下doBegin方法,如下
2.1 结论
整体分析doBegin方法可以发现,setAutoCommit
是在绑定ThreadLocal之前赋值的,所以【1.4】的结论第四点如下
setAutoCommit
开启事务
{@link org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin}
{@link org.apache.commons.dbcp2.DelegatingConnection#setAutoCommit}
(con.setAutoCommit(false);)
3. 创建Statement
回到【切入点3】的org.springframework.jdbc.core.JdbcTemplate#execute(org.springframework.jdbc.core.StatementCallback<T>, boolean)
方法
重启测试程序
调用链D:
3.1 结论
分析调用链D可以发现,创建Statement是调用了jdk定义的java.sql.Connection#createStatement()
接口的,dbcp2实现类org.apache.commons.dbcp2.DelegatingConnection#createStatement()
去创建的(PS:不同的连接池不同的实现)
4. 执行目标sql
继续回到【切入点3】,重启应用程序
调用链E:
4.1 结论
分析调用链E可以发现,执行目标sql是调用了jdk定义的java.sql.Statement#execute(java.lang.String)
接口的,dbcp2实现类org.apache.commons.dbcp2.DelegatingStatement#execute(java.lang.String)
去执行的
5. 回滚事务
在java.sql.Connection#rollback()
和抛出异常处打上断点,重启测试程序
调用链F:
分析调用链F可以发现,执行回滚是调用了jdk定义的java.sql.Connection#rollback()
接口的,dbcp2实现类org.apache.commons.dbcp2.DelegatingConnection#rollback()
去执行的
那DelegatingConnection#rollback()
又是在哪里被调用的呢
继续分析调用链F,得到调用链F1:
5.1 结论
分析调用链F1可以发现,在transaction的切面类,捕捉了调用目标方法时产生的异常,然后调用了DelegatingConnection#rollback()
执行的回滚
6. 提交事务
在java.sql.Connection#commit()
打上断点,并将测试程序改为不抛出异常,重启测试程序
调用链G:
6.1 结论
分析调用链G可以发现,提交事务是调用了jdk定义的java.sql.Connection#commit()
接口的,dbcp2实现类org.apache.commons.dbcp2.DelegatingConnection#commit
去执行的.其也是在transaction的切面类,执行完目标方法后执行的。
最后再回过来,总结下切面类,缩减后的核心代码如下
7. 小结
这里先做下小结,不然后面的阅读将会很吃力
前面6节,已经把单个方法的事务整体流程都走了一遍了,现在逆推总结下,具体总结见5.1 有代码数据流
四. 其他知识点
1. 事务传播级别的源码分析
spring的事务传播级别有很多种,本节以常用的默认级别展开分析
PROPAGATION_REQUIRED:如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行
事务传播级别主要是为了解决方法嵌套时事物的处理。何为方法嵌套?比如以下场景
updateSonOfBalance方法内部调用了updateFatherOfUserName方法,此时默认事务传播级别下,这两个方法内对数据库的所有操作都是在一个事务下的
那么接下来请思考以下几个问题
1.1 如何获取Connection
根据前面的1.4 结论的第6点可知,获取Connection直接从ThreadLocal中拿就行了
org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource
那问题来了,为什么调用updateFatherOfUserName方法没有再次创建一个新的Connection呢?是否有一个地方判断了是否为首次调用,首次调用才创建Connection?
根据前面的1.4 结论的第5点可知,Connection是在org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
创建的,我们仔细分析下这段代码
可以发现,Connection是保存在对象txObject
的成员属性connectionHolder
中的,而且只有该成员属性不为空才会创建一个新的Connection。
既然都围绕着对象txObject
,那么我们就追溯下这个对象在哪创建的?在DataSourceTransactionManager#doBegin
打上断点,并且把测试程序改成调用updateSonOfBalance方法,重启应用程序
调用链H:
分析调用链H可以发现,txObject
是在调用org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
的Object transaction = doGetTransaction();
时创建的,那么我们在这里打上断点,继续重启应用程序
调用链H1:
分析调用链H1可以发现,doGetTransaction
主要做以下几件事
-
实例化一个新的事物包装类
DataSourceTransactionObject
-
尝试从
ThreadLocal
中获取当前Connection,但是获取的为null) -
调用
setConnectionHolder
赋值为null继续调用链H1,得到调用链H2:
分析调用链H2可以发现,doGetTransaction
后面的代码主要做以下几件事
- 判断
txObject.connectionHolder
是否为null - 如果为null,就继续判断事物传播级别
- 如果需要创建事物,就继续调用
DataSourceTransactionManager#doBegin
方法 - 创建Connection
- 绑定Connection到ThreadLoacel
- 返回
TransactionStatus
实例
上面演示的是首次调用时的doGetTransaction
,下面再来看看非首次
调用链H3:
分析调用链H3可以发现,doGetTransaction
主要做以下几件事
- 实例化一个新的事物包装类
DataSourceTransactionObject
- 尝试从
ThreadLocal
中获取当前Connection.A,获取到的值为上一步创建的Connection - 调用
setConnectionHolder
赋值为Connection.A - 调用
org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction
方法,返回TransactionStatus
实例
1.1.1 结论
至此,Connection的首次和非首次的获取都解析完毕,至于获取后具体在哪里用请看下文
org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
的核心逻辑如下图
- 首次
- 从
ThreadLocal
中获取当前Connection,获取的值为null - 于是新创建了一个Connection,并绑定到当前线程
- 非首次
- 从
ThreadLocal
中获取当前Connection,获取的值为上一步创建的Connection
1.2 何时触发回滚事务
根据前面的5.1 回滚事务的结论可知,事务回滚是在DelegatingConnection#rollback()
执行的,那么我们在这里打上断点,并且把测试程序改成调用updateSonOfBalance方法,重启应用程序
调用链I:
分析调用链I可以得到以下类图,此类图很关键,需要背下来
其中TransactionInfo
以及其内部成员属性,每一次调用下一个目标方法,都会在进入切面类后,调用目标方法前,实例化一个新的TransactionInfo
对象
调用链I,主要是做了以下几件事
- 进入切面类
- 构造TransactionInfo实例
- 执行目标方法,抛出异常
- 判断
DefaultTransactionStatus.newTransaction
是否true,为true就执行回滚 - 获取Connection,具体如何setConnection见调用链B1
- 调用
Connection#rollback
回滚
以上除了第4点外,其他的前面都已经梳理了。现在来看看newTransaction何时赋值。
我们在DefaultTransactionStatus文件全局搜索newTransaction =
,可以发现只有一处构造函数调用,我们在这里打上断点,重启测试程序
调用链I1:
分析调用链I1可以发现,newTransaction = true
是在调用
AbstractPlatformTransactionManager#getTransaction
->AbstractPlatformTransactionManager#startTransaction
方法,实例化DefaultTransactionStatus
时赋值的,该方法后面的代码还会赋值DefaultTransactionStatus.transaction.connectionHolder
属性
根据前面的类图分析可知DefaultTransactionStatus应该会在每次调用目标方法前都会实例化一个新的对象,所以应该在调用updateSonOfBalance,内部调用updateFatherOfUserName方法时还会再次进入DefaultTransactionStatus的构造函数,重启应用程序继续调试
调用链I2:
分析调用链I2可以发现,内部调用updateFatherOfUserName方法时,newTransaction = true
是在调用是在调用
AbstractPlatformTransactionManager#getTransaction
->AbstractPlatformTransactionManager#handleExistingTransaction
方法赋值的
1.1.2 结论
进入切面类后,在调用每一个目标方法前,会调用TransactionAspectSupport#createTransactionIfNecessary
方法构造TransactionInfo
实例,只有当TransactionInfo.transactionStatus.newTransaction
为true时的那个目标方法执行完,才会执行回滚。
紧接1.1.1 结论,继续总结
- 首次
- 从
ThreadLocal
中获取当前Connection,获取的值为null - 实例化
DefaultTransactionStatus
对象,并将其newTransaction
属性置为true - 新创建了一个Connection,并绑定到当前线程
- 非首次
- 从
ThreadLocal
中获取当前Connection,获取的值为上一步创建的Connection - 实例化
DefaultTransactionStatus
对象,并将其newTransaction
属性置为false
1.3 何时触发提交事务
根据前面的6.1 提交事务的结论可知,提交事务是在DelegatingConnection#commit()
执行的,那么我们在这里打上断点,并且把测试程序改成调用updateSonOfBalance方法且不报错,重启应用程序
调用链J:
分析调用链J可以发现,提交事务事务和回滚事务都是一样的,只有在newTransaction
属性置为true才执行提交,所以这里就不细化了
1.1.3 结论
1.4 结论
经过上面的分析可知,事务主要是通过包装类TransactionInfo控制的,其具体类图见上文。只有当其newTransaction
属性为true才执行提交或者回滚
那么调用下一个目标方法,生成TransactionInfo时控制好其newTransaction
属下就可以组合成不同的事务传播级别了。
比如PROPAGATION_REQUIRES_NEW
,其定义为
每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行
切面类在调用updateFatherOfUserName
前,生成TransactionInfo时,将其newTransaction
置为true,并从连接池中重新获取一个新的Connection连接,就可以实现该传播方式了。
其他的传播方式大体类似,嵌套事务可能会还会特殊处理一下回滚点(save point),与本文研究的主要流程无关,暂时不管
五. 总结
5.1 有代码数据流
下面这段代码使用@link
注释,复制到你自己的项目中去,ctrl+鼠标左键,即可快速跳转到目标方法,十分方便日后万一遗忘了,快速温习一遍
/**
**aop配置类
* {@link org.springframework.transaction.config.TransactionManagementConfigUtils#TRANSACTION_ADVISOR_BEAN_NAME}
* aop切点
* {@link org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut.TransactionAttributeSourceClassFilter#matches(java.lang.Class)}
* {@link org.springframework.transaction.annotation.SpringTransactionAnnotationParser#isCandidateClass(java.lang.Class)}
* (AnnotationUtils.isCandidateClass(targetClass, Transactional.class);// 是否持有@Transactional注解)
*
**aop切面:
* {@link TransactionInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)}
* {@link TransactionAspectSupport#invokeWithinTransaction(java.lang.reflect.Method, java.lang.Class, org.springframework.transaction.interceptor.TransactionAspectSupport.InvocationCallback)}
*
**一.构造TransactionInfo
* TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
* {@link TransactionAspectSupport#createTransactionIfNecessary(org.springframework.transaction.PlatformTransactionManager, org.springframework.transaction.interceptor.TransactionAttribute, java.lang.String)}
* (TransactionStatus status = tm.getTransaction(txAttr);)
* {@link AbstractPlatformTransactionManager#getTransaction(org.springframework.transaction.TransactionDefinition)}
* // fixme 1. 尝试从ThreadLocal中获取事物Connection
* Object transaction = doGetTransaction();//非顶级事务才能再这里获取到连接
* // fixme 2. 当前线程,是否已经有事物了
* if (isExistingTransaction(transaction)) {
* // Existing transaction found -> check propagation behavior to find out how to behave.
* // fixme 2.1 如果已经有事务了,new TransactionStatus.newTransactionStatus=false
* return handleExistingTransaction(def, transaction, debugEnabled);
* }
* // fixme 2.1 如果没有有事务,则判断事务传播级别
* if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||...){
* // fixme 2.2 调用doBegin方法,开启一个新事物Connection(con.setAutoCommit(false);),并绑定到ThreadLocal
* // new TransactionStatus.newTransactionStatus=true
* return startTransaction(def, transaction, debugEnabled, suspendedResources);
* }
*
**二.调用目标方法
* retVal = invocation.proceedWithInvocation();
*
**三.执行提交
* completeTransactionAfterThrowing(txInfo, ex);
* {@link TransactionAspectSupport#completeTransactionAfterThrowing(org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo, java.lang.Throwable)}
* {@link AbstractPlatformTransactionManager#processRollback(org.springframework.transaction.support.DefaultTransactionStatus, boolean)}
* if (status.isNewTransaction()) {
* //fixme newTransaction为true才回滚
* doRollback(status);
* }
*
**三.执行回滚
* commitTransactionAfterReturning(txInfo);
* {@link TransactionAspectSupport#commitTransactionAfterReturning(org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo)}
* {@link AbstractPlatformTransactionManager#processCommit(org.springframework.transaction.support.DefaultTransactionStatus)}
* if (status.isNewTransaction()) {
* //fixme newTransaction为true才提交
* doCommit(status);
* }
*
** 事务的载体类
* 顶级类TransactionInfo:{@link TransactionAspectSupport.TransactionInfo}
* 事务状态类TransactionStatus:{@link DefaultTransactionStatus}
* 事务包装类transaction:{@link DataSourceTransactionManager.DataSourceTransactionObject}
* 连接包装类管理类ConnectionHolder:{@link ConnectionHolder}
* 连接包装类SimpleConnectionHandle:{@link SimpleConnectionHandle}
*
** Connection存储的ThreadLocal
* 位置:{@link TransactionSynchronizationManager#resources}
* 获取:{@link TransactionSynchronizationManager#doGetResource(java.lang.Object)}
* 赋值:{@link TransactionSynchronizationManager#bindResource(java.lang.Object, java.lang.Object)}
*
*/
5.2 无代码流程图
PS: 此图仅仅为默认事务传播级别下的流程图,如果为Propagation.REQUIRES_NEW
或其他,还需要处理下在方法调用结束后,将ThreadLocal中的Connection还原为上一个方法创建的Connection