Spring 随笔 transactional 7-事务实际的开启时机

0. 感觉保尔·柯察金的话有些浪漫在里面

如果一开始从spring的事务源码看起的话,我估计是看不出来——数据库的事务命令真正被写入到数据库服务中其实是在:纳入事务管理的方法中第一条SQL调用的过程中。

遇到事务传播行为的问题时,开了个小差,想到了这个问题,于是开启了debug。


我们预设被纳入事务管理的方法是这么写的(debug)

@Transactional
public void test() {
	// 我们自己方法中的第一条SQL方法(此时事务命令被写入到数据库)
	this.xxxMapper.update();
	...
	this.xxxMapper.insert();
	...
}

1. 我们的SQL方法被代理执行

	// org.apache.ibatis.binding.MapperProxy#invoke
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		try {
		  if (Object.class.equals(method.getDeclaringClass())) {
			return method.invoke(this, args);
		  } else {
			// step into ...
			return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
		  }
		} catch (Throwable t) {
		  throw ExceptionUtil.unwrapThrowable(t);
		}
	}
  
	// org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker#invoke
	@Override
	public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
		// step into ...
		return mapperMethod.execute(sqlSession, args);
	}
	
	public Object execute(SqlSession sqlSession, Object[] args) {
		Object result;
		switch (command.getType()) {
			case INSERT: {
				Object param = method.convertArgsToSqlCommandParam(args);
				result = rowCountResult(sqlSession.insert(command.getName(), param));
				break;
			}
			case UPDATE: {
				Object param = method.convertArgsToSqlCommandParam(args);
				// step into ...
				// 我们这里假设是一个更新的SQL
				result = rowCountResult(sqlSession.update(command.getName(), param));
				break;
			}
			case DELETE: {
				Object param = method.convertArgsToSqlCommandParam(args);
				result = rowCountResult(sqlSession.delete(command.getName(), param));
				break;
			}
			case SELECT:
				if (method.returnsVoid() && method.hasResultHandler()) {
				  executeWithResultHandler(sqlSession, args);
				  result = null;
				} else if (method.returnsMany()) {
				  result = executeForMany(sqlSession, args);
				} else if (method.returnsMap()) {
				  result = executeForMap(sqlSession, args);
				} else if (method.returnsCursor()) {
				  result = executeForCursor(sqlSession, args);
				} else {
				  Object param = method.convertArgsToSqlCommandParam(args);
				  result = sqlSession.selectOne(command.getName(), param);
				  if (method.returnsOptional()
					  && (result == null || !method.getReturnType().equals(result.getClass()))) {
					result = Optional.ofNullable(result);
				  }
				}
				break;
			case FLUSH:
				result = sqlSession.flushStatements();
				break;
			default:
				throw new BindingException("Unknown execution method for: " + command.getName());
		}
		if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
			throw new BindingException("Mapper method '" + command.getName()
			  + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
		}
		return result;
	}
	
	// org.mybatis.spring.SqlSessionTemplate#update(java.lang.String, java.lang.Object)
	@Override
	public int update(String statement, Object parameter) {
		// skip ...
		// 这里委托对象: this.sqlSessionProxy 是一个代理类,我们跳过这里的反射调用
		// 接下来该方法实际之前会被sqlSessionTemplate内部的一个私有的拦截器(SqlSessionInterceptor)所增强
		return this.sqlSessionProxy.update(statement, parameter);
	}

2. SqlSession拦截器中创建SqlSession并打开其事务选项

// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// step into ...
		// 获取当前的sqlsession,没有则先创建一个
		SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
			SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
		try {
			// 调用下一个拦截器(执行方法)
			Object result = method.invoke(sqlSession, args);
			if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
				// 如果当前session并未开启事务,则直接提交
				// force commit even on non-dirty sessions because some databases require
				// a commit/rollback before calling close()
				sqlSession.commit(true);
			}
			return result;
		} catch (Throwable t) {
			Throwable unwrapped = unwrapThrowable(t);
			if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
				// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
				closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
				sqlSession = null;
				Throwable translated = SqlSessionTemplate.this.exceptionTranslator
					.translateExceptionIfPossible((PersistenceException) unwrapped);
				if (translated != null) {
					unwrapped = translated;
				}
			}
			throw unwrapped;
		} finally {
			if (sqlSession != null) {
				closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
			}
		}
	}
}

	// org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
	public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
	  PersistenceExceptionTranslator exceptionTranslator) {

		notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
		notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

		SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

		SqlSession session = sessionHolder(executorType, holder);
		if (session != null) {
			// 如果存在SqlSession,直接返回
			return session;
		}

		LOGGER.debug(() -> "Creating a new SqlSession");
		// step into ...
		// 没有session,那么创建(open)一个
		session = sessionFactory.openSession(executorType);

		// 对应的,注册一个sqlSessionHolder
		registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

		return session;
	}
	
	// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(org.apache.ibatis.session.ExecutorType)
	@Override
	public SqlSession openSession(ExecutorType execType) {
		return openSessionFromDataSource(execType, null, false);
	}
	
	// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
	private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
		Transaction tx = null;
		try {
			final Environment environment = configuration.getEnvironment();
			final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
			// 新建一个sqlSession的事务
			tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
			final Executor executor = configuration.newExecutor(tx, execType);
			return new DefaultSqlSession(configuration, executor, autoCommit);
		} catch (Exception e) {
			closeTransaction(tx); // may have fetched a connection so lets call close()
			throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
		} finally {
			ErrorContext.instance().reset();
		}
		// end ...
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

肯尼思布赖恩埃德蒙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值