从嵌套事务的日志看MyBatis的sqlSession生命周期

service层业务代码

@Override
public void test(){
    QueryWrapper<StoreRebateCalculateLog> queryWrapper;
    queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("delete_flag", 0);
    //执行查询A,以非事务方式执行
    List<StoreRebateCalculateLog> storeRebateCalculateLogs = storeRebateCalculateLogDao.selectBatchIds(Arrays.asList(90010, 90011, 90012, 90013));
    //更新,开启B事务
    ((StoreRebateCalculateLogServiceImpl) AopContext.currentProxy()).updateTest(storeRebateCalculateLogs);//取当前类的代理对象执行,保证在同一个类中的@Transactional注解能够生效
}

@Transactional(rollbackFor = Exception.class)
public void updateTest(List<StoreRebateCalculateLog> storeRebateCalculateLogs1) {
    for (StoreRebateCalculateLog log: storeRebateCalculateLogs1) {
        log.setNeedRepeatCalFlag("0");
        this.storeRebateCalculateLogDao.updateById(log);
    }
}

这里先执行一个查询,然后更新操作。查询的方法是以非事务的方式执行的,实际上这里就算是在调用updateTest()方法前执行的是更新或者删除操作,同样也是以非事务的方式执行的,因为test()方法的事务传播行为默认是PROPAGATION_SUPPORTS。后面执行更新操作的方法updateTest因为采用了声明式事务,而且@Transactional注解事务的传播行为默认为Propagation.REQUIRED,会重新开启事务,这里记为是事务B。

查看调试模式下的MyBatis执行日志

//创建sqlSession A
Creating a new SqlSession  
//为A注册事务    
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8d867b]
JDBC Connection [HikariProxyConnection@8519751 wrapping com.mysql.cj.jdbc.ConnectionImpl@2c5cdd] will be managed by Spring
==>  Preparing: SELECT calculate_log_id,month_str,day_str,total_count,min_id,max_id,status,need_repeat_cal_flag,delete_flag,creation_date,created_by,version_num,last_update_date,last_updated_by,last_update_login FROM store_rebate_calculate_log WHERE calculate_log_id IN ( ? , ? , ? , ? ) AND delete_flag=0
==> Parameters: 90010(Integer), 90011(Integer), 90012(Integer), 90013(Integer)
<==    Columns: calculate_log_id, month_str, day_str, total_count, min_id, max_id, status, need_repeat_cal_flag, delete_flag, creation_date, created_by, version_num, last_update_date, last_updated_by, last_update_login
<==        Row: 90010, 2024-03, 20240308, 0, null, null, 2, 1, 0, 2024-03-14 22:20:09, 62169, 9, 2024-03-17 08:40:44, 62169, 62169
<==        Row: 90011, 2024-03, 20240309, 0, null, null, 2, 1, 0, 2024-03-14 22:20:09, 62169, 9, 2024-03-17 08:41:45, 62169, 62169
<==        Row: 90012, 2024-03, 20240310, 0, null, null, 2, 1, 0, 2024-03-14 22:20:09, 62169, 9, 2024-03-17 08:41:45, 62169, 62169
<==        Row: 90013, 2024-03, 20240311, 0, null, null, 2, 1, 0, 2024-03-14 22:20:10, 62169, 2, 2024-03-14 23:34:23, 62169, 62169
<==      Total: 4
//释放sqlSession A    
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8d867b]
//挂起sqlSession A
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8d867b]
    
//创建sqlSession B    
Creating a new SqlSession
//为B注册事务      
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754]
JDBC Connection [HikariProxyConnection@25084448 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e088a3] will be managed by Spring
==>  Preparing: UPDATE store_rebate_calculate_log SET month_str=?, day_str=?, total_count=?, status=?, need_repeat_cal_flag=?, creation_date=?, created_by=?, version_num=?, last_update_date=?, last_updated_by=?, last_update_login=? WHERE calculate_log_id=? AND version_num=? AND delete_flag=0
==> Parameters: 2024-03(String), 20240308(String), 0(Integer), 2(String), 0(String), 2024-03-14 22:20:09.0(Timestamp), 62169(Integer), 10(Integer), 2024-03-17 09:56:44.399(Timestamp), 62169(Integer), 62169(Integer), 90010(Long), 9(Integer)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754] from current transaction
==>  Preparing: UPDATE store_rebate_calculate_log SET month_str=?, day_str=?, total_count=?, status=?, need_repeat_cal_flag=?, creation_date=?, created_by=?, version_num=?, last_update_date=?, last_updated_by=?, last_update_login=? WHERE calculate_log_id=? AND version_num=? AND delete_flag=0
==> Parameters: 2024-03(String), 20240309(String), 0(Integer), 2(String), 0(String), 2024-03-14 22:20:09.0(Timestamp), 62169(Integer), 10(Integer), 2024-03-17 09:56:44.415(Timestamp), 62169(Integer), 62169(Integer), 90011(Long), 9(Integer)
<==    Updates: 1
//这里省略了一部分更新操作的日志....
    
//释放sqlSession B   
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754]
//提交sqlSession B       
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754]
//注销sqlSession B       
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754]
//关闭sqlSession B    
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10c1754]
    
//恢复sqlSession A    
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8d867b]
//注销sqlSession A  
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8d867b]
//关闭sqlSession A     
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8d867b]

可以得出下面的流程图

image-20240317191102230

说明

事务A与事务B有可能是共用一个事务,具体要看外层调用者和被调用者的事务传播行为怎么定义的。

例如,如果上面的test()方法也加上@Transactional注解,结果会是怎么样的?

即如下的代码:

@Override
@Transactional(rollbackFor = Exception.class) //这里的区别
public void test(){
    QueryWrapper<StoreRebateCalculateLog> queryWrapper;
    queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("delete_flag", 0);
    //执行查询A,A事务开启
    List<StoreRebateCalculateLog> storeRebateCalculateLogs = storeRebateCalculateLogDao.selectBatchIds(Arrays.asList(90010, 90011, 90012, 90013));
    //更新
    ((StoreRebateCalculateLogServiceImpl) AopContext.currentProxy()).updateTest(storeRebateCalculateLogs);//取当前类的代理对象执行,保证在同一个类中的@Transactional注解能够生效
}

@Transactional(rollbackFor = Exception.class)
public void updateTest(List<StoreRebateCalculateLog> storeRebateCalculateLogs1) {
    for (StoreRebateCalculateLog log: storeRebateCalculateLogs1) {
        log.setNeedRepeatCalFlag("0");
        this.storeRebateCalculateLogDao.updateById(log);
    }
}

跟踪日志发现:

//这里只截取中间部分有差异的日志
==>  Preparing: SELECT calculate_log_id,month_str,day_str,total_count,min_id,max_id,status,need_repeat_cal_flag,delete_flag,creation_date,created_by,version_num,last_update_date,last_updated_by,last_update_login FROM store_rebate_calculate_log WHERE calculate_log_id IN ( ? , ? , ? , ? ) AND delete_flag=0
==> Parameters: 90010(Integer), 90011(Integer), 90012(Integer), 90013(Integer)
<==    Columns: calculate_log_id, month_str, day_str, total_count, min_id, max_id, status, need_repeat_cal_flag, delete_flag, creation_date, created_by, version_num, last_update_date, last_updated_by, last_update_login
<==        Row: 90010, 2024-03, 20240308, 0, null, null, 2, 1, 0, 2024-03-14 22:20:09, 62169, 11, 2024-03-17 10:46:48, 62169, 62169
<==        Row: 90011, 2024-03, 20240309, 0, null, null, 2, 1, 0, 2024-03-14 22:20:09, 62169, 11, 2024-03-17 10:47:55, 62169, 62169
<==        Row: 90012, 2024-03, 20240310, 0, null, null, 2, 1, 0, 2024-03-14 22:20:09, 62169, 11, 2024-03-17 10:48:03, 62169, 62169
<==        Row: 90013, 2024-03, 20240311, 0, null, null, 2, 1, 0, 2024-03-14 22:20:10, 62169, 4, 2024-03-17 10:48:03, 62169, 62169
<==      Total: 4
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1f057a7]

//发现这里更新时是从当前的会话中取的sqlSession,用的是同一个sqlSession,没有创建新的sqlSession
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1f057a7] from current transaction
==>  Preparing: UPDATE store_rebate_calculate_log SET month_str=?, day_str=?, total_count=?, status=?, need_repeat_cal_flag=?, creation_date=?, created_by=?, version_num=?, last_update_date=?, last_updated_by=?, last_update_login=? WHERE calculate_log_id=? AND version_num=? AND delete_flag=0
==> Parameters: 2024-03(String), 20240308(String), 0(Integer), 2(String), 0(String), 2024-03-14 22:20:09.0(Timestamp), 62169(Integer), 12(Integer), 2024-03-17 11:21:30.178(Timestamp), 62169(Integer), 62169(Integer), 90010(Long), 11(Integer)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1f057a7]

从日志可以发现后面更新时是从当前的会话中取的sqlSession,用的是同一个sqlSession,没有创建新的sqlSession,这是什么原因呢?

其实上面的注解相当于

 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)

由于调用的方法中开启了事务A,然后在调用updateTest()方法的时候,由于使用了Propagation.REQUIRED传播特性,就会使用当前事务A。

SpringBoot源码分析

挂起当前事务,另起事务的地方:

image-20240317140801334

在AbstractPlatformTransactionManager类的getTransaction()方法中:

public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
		Object transaction = doGetTransaction();

		// Cache debug flag to avoid repeated checks.
		boolean debugEnabled = logger.isDebugEnabled();

		if (definition == null) {
			// Use defaults if no transaction definition given.
			definition = new DefaultTransactionDefinition();
		}

        //当前存在该类型的事务,直接获取并返回
		if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			return handleExistingTransaction(definition, transaction, debugEnabled);
		}

		// Check definition settings for new transaction.
		if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
			throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
		}

		// No existing transaction found -> check propagation behavior to find out how to proceed.
		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
			throw new IllegalTransactionStateException(
					"No existing transaction found for transaction marked with propagation 'mandatory'");
		}
		else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            //如果当前的事务传播行为为:PROPAGATION_REQUIRED 或者 PROPAGATION_REQUIRES_NEW 或者 PROPAGATION_NESTED,就会挂起前               面的事务
			SuspendedResourcesHolder suspendedResources = suspend(null);
			if (debugEnabled) {
				logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
			}
			try {
                //除非事务的传播行为为PROPAGATION_NEVER,否则就会新建事务
				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
				DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
			catch (RuntimeException ex) {
				resume(null, suspendedResources);
				throw ex;
			}
			catch (Error err) {
				resume(null, suspendedResources);
				throw err;
			}
		}
		else {
			// Create "empty" transaction: no actual transaction, but potentially synchronization.
			if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
				logger.warn("Custom isolation level specified but no actual transaction initiated; " +
						"isolation level will effectively be ignored: " + definition);
			}
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
		}
	}
  1. 调用的方法test()方法没有加@Transactional注解的情况

    在调用的方法test()方法没有加@Transactional注解时,事务的传播行为和事务隔离级别分别为默认的:

    PROPAGATION_SUPPORTS,ISOLATION_DEFAULT

    image-20240317141358451

    被调用的方法updateTest()加了@Transactional注解,在默认传播行为的配置下,事务的传播行为和事务隔离级别分别为:

    PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-Exception

    image-20240317141840546

    因为在调用updateTest()方法前事务传播行为不是PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED三种中的某一种,所以不会认为这个是个活跃的事务,事实上PROPAGATION_SUPPORTS是以非事务的方式执行的,就算存在update或者delete等语句操作,也不会有commit信息的。然后从下面这里取不到当前事务:

     //当前存在该类型的事务,直接获取并返回
    if (isExistingTransaction(transaction)) {
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(definition, transaction, debugEnabled);
    }
    

    然后逻辑会一直往下走到这个位置:

     //如果当前的事务传播行为为:PROPAGATION_REQUIRED 或者 PROPAGATION_REQUIRES_NEW 或者 PROPAGATION_NESTED,就会挂起前面的事务
     SuspendedResourcesHolder suspendedResources = suspend(null);
    
  2. 调用的方法test()方法加了@Transactional注解的情况

    由于当前调用的方法已经加上了@Transactional注解,因为在调用的方法test()加上@Transactional注解后,其实省略了propagation属性,而propagation的默认值就是 Propagation.REQUIRED,这个时候就会标记为当前sessionHandler是一个活跃的事务。只有活跃的事务,才能从上面的这个地方直接返回。不用走下面的逻辑了。在上面的AbstractPlatformTransactionManager类的getTransaction()方法中的这个位置:

     //当前存在该类型的事务,直接获取并返回
    if (isExistingTransaction(transaction)) {
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(definition, transaction, debugEnabled);
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小灰灰-58

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值