Spring-事务Transactional

Spring-事务Transactional

1 基础概念

可参考

2 @Transactional事务提交和回滚原理

  1. SpringBoot会将调用事务注解方法所在的对象进行Cglib动态代理,可见:
    在这里插入图片描述
    NovelManager#insertNovel会将NovelBO中的Author和Book分别插入数据库:
    在这里插入图片描述

  2. 在调用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);
    
  3. 创建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

  4. 开始执行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的提交、回滚、关闭等操作。
    在这里插入图片描述
    这里还创建了用来执行MappedStatementExecutor,在其父类BaseExecutor中传入了我们创建的Transaction,以便在后续同一个SqlSession中执行SQL时添加到本事务:
    在这里插入图片描述
    最后PreaparedStatement方式开始执行SQL,最后释放了这个事务化的SqlSession。

  5. 这个时候我们查询数据库,是没有这条新数据的
    在这里插入图片描述

  6. 继续执行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插入数据。

    特别注意,这个时候,事务还没有提交,数据库里面依然无数据:
    在这里插入图片描述

  7. 处理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,但此时事务还未提交,数据库依然无法查到插入的数据。

  8. 正常时提交事务
    上接前面说的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进行了提交,完成后释放了连接。此后我们可以在数据库查到插入的数据了:
    在这里插入图片描述

  9. 异常时事务回滚
    我们改下代码,在插入Author后,插入Book之前抛出异常,看看事务回滚的情况:
    在这里插入图片描述
    上接前面说的commitTransactionAfterReturning(txInfo);方法,调用栈如下:
    在这里插入图片描述
    可见这里会因为参与了事务的业务代码抛出了属于rollbackFor指定的异常而且未处理,所以Spring帮我们执行了事务回滚。在这里例子中,第一个Author表插入的数据被回滚了。

    注意,处理完事务回滚后,该Throwable异常会被继续向上抛出!

还可参考:

3 @Transactional失效的场景及原理

3.1 @Transactional修饰的方法为非public方法

3.1.1 场景

如题。

其实idea直接报错了。
在这里插入图片描述

3.1.2 原理

Spring必须使用CgLib动态代理,原理是继承当前类重写方法,这里弄成private的方法当然是不行的。

3.2 方法没有被Spring管理

3.2.1 场景

  • 事务方法所在类没有直接被Spring管理
  • 事务方法所在类直接被Spring管理,但@Transactional方法没有被直接调用

3.2.2 原理

场景2,可以看到由于Controller直接调用的方法并没有直接用@Transactional注解,所以没有前文中提到的用事务相关操作。

Controller直接调用以下Service#nowork2方法
在这里插入图片描述
注意:网上有观点说主要是这种情况使用this调用的关系,其实这和this指向谁无关,而主要取决于从Controller发出调用的Service方法是否有@Transactional注解,这两种情况产生的动态代理方法分支是不同的,如下:

  • 直接调用的方法不带@Transactional注解
    在这里插入图片描述
  • 直接调用的方法带@Transactional注解
    在这里插入图片描述

3.3 数据库引擎不支持事务

3.3.1 场景

如题

3.3.2 原理

比如Mysql MyISAM 引擎是不支持事务操作,而常用的InnoDB支持事务。

3.4 @Transactional注解方法又调用同类的@Transactional注解方法-可以生效

3.4.1 场景

注意和3.2 场景2 区别。Controller直接调用以下Service#nowork3方法。
在这里插入图片描述
网上很多文章说本场景事务也无法生效,实测可以生效。

3.5 异常被catch或不在声明的范围内

3.5.1 场景

Controller直接调用以下Service#nowork4方法。
在这里插入图片描述

3.5.2 原理

没有将错误抛出给Spring,导致Spring无法感知此次异常来进行回滚处理。

3.6 propagation设置不对

3.6.1 场景

这里直接转载了一口气说出 6 种 @Transactional 注解的失效场景
在这里插入图片描述

4 需要注意的生效场景

4.1 直接调用的@Transactional注解方法调用其他Service的普通方法

@Transactional
public void test1(){
	aService.method1();
	bService.method1();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值