可参考SpringBoot事务Transaction 你真的懂了么?
2 原理-
SpringBoot会将调用事务注解方法所在的对象进行Cglib动态代理,可见:
NovelManager#insertNovel会将NovelBO中的Author和Book分别插入数据库:
-
在调用insertNovel#insertNovel这个用@Transactional注解标记方法之前,利用动态代理加入了一段开启事务的代码:
这里面TransactionAspectSupport里最重要的关键方法如下:// 再调用@Transactional注解的方法前开启一个事务 TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); // 调用@Transactional注解的方法,使用事务 retVal = invocation.proceedWithInvocation(); // 处理异常时的retVal if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { // Set rollback-only in case of Vavr failure matching our rollback rules... TransactionStatus status = txInfo.getTransactionStatus(); if (status != null && txAttr != null) { retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); } } // 在用户业务方法返回后提交事务,或是在异常时回滚事务 // 在后面第8步会详细说正常和异常回滚的情况 commitTransactionAfterReturning(txInfo); -
创建Connection和事务
可见以下关键日志输出:[DEBUG][2021-12-25T11:15:11.147+0800][][][http-nio-8081-exec-1][AbstractPlatformTransactionManager.java:370]:Creating new transaction with name [com.mljk.springboot.demo.manager.NovelManager.insertNovel]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,timeout_10000,-java.lang.Exception [DEBUG][2021-12-25T11:15:11.157+0800][][][http-nio-8081-exec-1][HikariConfig.java:1098]:transactionIsolation............default [DEBUG][2021-12-25T11:15:11.329+0800][][][http-nio-8081-exec-1][DataSourceTransactionManager.java:263]:Acquired Connection [HikariProxyConnection@221551358 wrapping com.mysql.cj.jdbc.ConnectionImpl@1f695a36] for JDBC transaction [DEBUG][2021-12-25T11:15:11.331+0800][][][http-nio-8081-exec-1][DataSourceTransactionManager.java:281]:Switching JDBC Connection [HikariProxyConnection@221551358 wrapping com.mysql.cj.jdbc.ConnectionImpl@1f695a36] to manual commit
注意这里创建了数据库 Connection:
这里创建的Connection是HikariProxyConnection@xxx wrapping com.mysql.cj.jdbc.ConnectionImpl@yyy这里Spring还帮我们为NovelManager.insertNovel创建了新的事务,然后改为了手动提交事务。
-
开始执行Author插入方法调用
可看到以下关键日志[INFO ][2021-12-25T11:15:18.199+0800][][][http-nio-8081-exec-1][AuthorService.java:20]:start to insert author:Author(id=null, name=Tom2, nation=CHN) [DEBUG][2021-12-25T11:15:18.207+0800][][][http-nio-8081-exec-1][Logger.java:49]:Creating a new SqlSession [DEBUG][2021-12-25T11:15:18.213+0800][][][http-nio-8081-exec-1][Logger.java:49]:Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272] [DEBUG][2021-12-25T11:15:18.237+0800][][][http-nio-8081-exec-1][Logger.java:49]:JDBC Connection [HikariProxyConnection@221551358 wrapping com.mysql.cj.jdbc.ConnectionImpl@1f695a36] will be managed by Spring [DEBUG][2021-12-25T11:15:18.241+0800][][][http-nio-8081-exec-1][baseJdbcLogger.java:137]:==> Preparing: INSERT INTO author ( name, nation ) VALUES ( ?, ? ) [DEBUG][2021-12-25T11:15:18.271+0800][][][http-nio-8081-exec-1][baseJdbcLogger.java:137]:==> Parameters: Tom2(String), CHN(String) [DEBUG][2021-12-25T11:15:18.274+0800][][][http-nio-8081-exec-1][baseJdbcLogger.java:137]:<== Updates: 1 [DEBUG][2021-12-25T11:15:18.289+0800][][][http-nio-8081-exec-1][Logger.java:49]:Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272] [INFO ][2021-12-25T11:15:18.289+0800][][][http-nio-8081-exec-1][AuthorService.java:22]:finished inserting author:Author(id=1, name=Tom2, nation=CHN)
可知,在调用mybatis插入数据时,帮我们创建了SqlSession并注册同步到了事务,然后PreaparedStatement方式开始执行SQL,最后释放了这个事务化的SqlSession。
-
这个时候我们查询数据库,是没有这条新数据的
-
继续执行Book插入方法调用
日志如下:[INFO ][2021-12-25T11:15:18.289+0800][][][http-nio-8081-exec-1][BookService.java:20]:start to insert book:Book(id=null, name=ThreeBody, price=128.0, authorId=1, publishDate=2012-12-12 10:18:00.0) [DEBUG][2021-12-25T11:15:18.290+0800][][][http-nio-8081-exec-1][Logger.java:49]:Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272] from current transaction [DEBUG][2021-12-25T11:15:18.291+0800][][][http-nio-8081-exec-1][baseJdbcLogger.java:137]:==> Preparing: INSERT INTO book ( name, price, author_id, publish_date ) VALUES ( ?, ?, ?, ? ) [DEBUG][2021-12-25T11:15:18.297+0800][][][http-nio-8081-exec-1][baseJdbcLogger.java:137]:==> Parameters: ThreeBody(String), 128.0(Double), 1(Long), 2012-12-12 10:18:00.0(Timestamp) [DEBUG][2021-12-25T11:15:18.298+0800][][][http-nio-8081-exec-1][baseJdbcLogger.java:137]:<== Updates: 1 [DEBUG][2021-12-25T11:15:18.298+0800][][][http-nio-8081-exec-1][Logger.java:49]:Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272] [INFO ][2021-12-25T11:15:18.298+0800][][][http-nio-8081-exec-1][BookService.java:22]:finished inserting book:Book(id=1, name=ThreeBody, price=128.0, authorId=1, publishDate=2012-12-12 10:18:00.0)
可见这里从当前同一个事务中获取了DefaultSqlSession@551e31e0,然后执行了SQL插入数据。
特别注意,这个时候,事务还没有提交,数据库里面依然无数据:
-
处理SqlSession
可见关键日志:[DEBUG][2021-12-25T11:17:32.866+0800][][][http-nio-8081-exec-1][Logger.java:49]:Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272] [DEBUG][2021-12-25T11:17:42.551+0800][][][http-nio-8081-exec-1][Logger.java:49]:Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272] [DEBUG][2021-12-25T11:17:44.379+0800][][][http-nio-8081-exec-1][Logger.java:49]:Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@555c2272]
可见事务同步已经提交、反注册最终关闭了该SqlSession,但此时事务还未提交,数据库依然无法查到插入的数据。
-
正常时提交事务
上接前面说的commitTransactionAfterReturning(txInfo);方法,调用栈如下:
关键逻辑可见AbstractPlatformTransactionManager#processCommit,其内定义了一些事务执行前后触发的事件。比如事务提交后重置连接、释放连接等。这一步关键日志如下:
[DEBUG][2021-12-25T11:17:54.501+0800][][][http-nio-8081-exec-1][AbstractPlatformTransactionManager.java:740]:Initiating transaction commit [DEBUG][2021-12-25T11:18:10.992+0800][][][http-nio-8081-exec-1][DataSourceTransactionManager.java:326]:Committing JDBC transaction on Connection [HikariProxyConnection@221551358 wrapping com.mysql.cj.jdbc.ConnectionImpl@1f695a36] [DEBUG][2021-12-25T11:42:54.542+0800][][][http-nio-8081-exec-1][DataSourceTransactionManager.java:385]:Releasing JDBC Connection [HikariProxyConnection@221551358 wrapping com.mysql.cj.jdbc.ConnectionImpl@1f695a36] after transaction
可见事务通过此前拥有的JDBC Connection进行了提交,完成后释放了连接。此后我们可以在数据库查到插入的数据了:
-
异常时事务回滚
前面讲到的
还可参考:
- Spring中的@Transactional注解为什么要加rollbackFor = Exception.class之源码解析
- SpringBoot中的Transaction研究(一)TransactionManager



