赞
踩
可参考
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步会详细说正常情况,第9步为异常回滚的情况 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
可见这里Spring还帮我们为NovelManager.insertNovel
创建了新的事务,然后改为了手动提交事务(正式开启了这次事务)。
这里创建的数据库 Connection
如下:
这里创建的Connection是HikariProxyConnection@xxx wrapping com.mysql.cj.jdbc.ConnectionImpl@yyy
开始执行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
并注册同步到了事务。这里的创建的实现了org.apache.ibatis.transaction.Transaction
接口的是SpringManagedTransaction
其内部含有数据源连接信息,可用来控制JDBCConnection的提交、回滚、关闭等操作。
这里还创建了用来执行MappedStatement
的Executor
,在其父类BaseExecutor
中传入了我们创建的Transaction,以便在后续同一个SqlSession中执行SQL时添加到本事务:
最后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@555c2272
,然后执行了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进行了提交,完成后释放了连接。此后我们可以在数据库查到插入的数据了:
异常时事务回滚
我们改下代码,在插入Author后,插入Book之前抛出异常,看看事务回滚的情况:
上接前面说的commitTransactionAfterReturning(txInfo);
方法,调用栈如下:
可见这里会因为参与了事务的业务代码抛出了属于rollbackFor
指定的异常而且未处理,所以Spring帮我们执行了事务回滚。在这里例子中,第一个Author表插入的数据被回滚了。
注意,处理完事务回滚后,该Throwable
异常会被继续向上抛出!
还可参考:
如题。
其实idea直接报错了。
Spring必须使用CgLib动态代理,原理是继承当前类重写方法,这里弄成private的方法当然是不行的。
场景2,可以看到由于Controller直接调用的方法并没有直接用@Transactional
注解,所以没有前文中提到的用事务相关操作。
Controller直接调用以下Service#nowork2
方法
注意:网上有观点说主要是这种情况使用this
调用的关系,其实这和this
指向谁无关,而主要取决于从Controller发出调用的Service方法是否有@Transactional注解,这两种情况产生的动态代理方法分支是不同的,如下:
如题
比如Mysql MyISAM 引擎是不支持事务操作,而常用的InnoDB支持事务。
注意和3.2 场景2 区别。Controller直接调用以下Service#nowork3
方法。
网上很多文章说本场景事务也无法生效,实测可以生效。
Controller直接调用以下Service#nowork4
方法。
没有将错误抛出给Spring,导致Spring无法感知此次异常来进行回滚处理。
这里直接转载了一口气说出 6 种 @Transactional 注解的失效场景
@Transactional
public void test1(){
aService.method1();
bService.method1();
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。