前言
突然好奇Spring的申明式事务是怎么实现的,肯定是后置处理增强的就不用说了。从我多年写代码的经验来说,肯定是生成一个代理对象,在调用service方法之前就会创建事务,然后整个service方法执行完后再提交或者异常回滚。但是这就有一个问题,一个service调用多个dao,以DAO层框架mybatis为例,调用的时候咱们并没有给他传任何connection,既然要保证事务,那么每个DAO里面传的connection肯定是同一个,那么框架底层是如何实现的呢?
过程
为了探究一下实现原理,咱们先写一个简单的DEMO
@Override
@Transactional
public void test() {
List<GoodsOwnerDTO> dataA = wmsDao.selectGoodsOwnerByWarehId("1");
List<GoodsOwnerDTO> dataB = wmsDao.selectGoodsOwnerByWarehId("2");
}
咱们的目的也很简单,就在第二次调用查询的时候看他从哪取的connection,然后开始DEBUG。
首先是MybatisMapperProxy类里面的
@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 if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
//进入这个方法
return mapperMethod.execute(sqlSession, args);
}
继续跟MybatisMapperMethod
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);
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);
// TODO 这里下面改了
if (IPage.class.isAssignableFrom(method.getReturnType()) && args != null
&& IPage.class.isAssignableFrom(args[0].getClass())) {
result = ((IPage<?>) args[0]).setRecords(executeForIPage(sqlSession, args));
// TODO 这里上面改了
} else {
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;
}
继续跟进到了executeForMany方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
//进入这个方法
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
跟进到SqlSessionTemplate的selectList
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}
紧接着是SqlSessionTemplate的内部类SqlSessionInterceptor的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//注意看这里,这里就是去拿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)) {
// 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);
}
}
}
到了这一步,总算有点苗头了,因为我们看到sqlSession了,继续跟到了SqlSessionUtils的getSqlSession
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) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
很多框架总是喜欢用holder来持有比较重要的对象,咱们看到holder可以稍微关注一下,然后继续跟进入到TransactionSynchronizationManager的getResource方法
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
//就是这个方法
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
终于看到doXXX了,不出意外应该是要见底了,继续进doGetResource方法。
@Nullable
private static Object doGetResource(Object actualKey) {
//关键地方
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
代码不长,甚至很简单,就是从一个map里面通过KEY取一个值返回就行,重点也就在这。这个map里面存的啥,咱们看下断点追踪的图片
一个是DATASOURCE,一个是sqlsessionFactory,好,真相大白,然后在看来一下这个resources是个啥,ThreadLocal。
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
到了这里,相信大家应该都明白了,但是可能某些同学还不知道sqlSession是个啥玩意,那也没关系,咱们再看一下下图
sqlsession里面的executor里面持有一个transcation对象,然后transcation里面又持有一个connection对象,否管其他东西是啥,咱们就能保证一个线程里面获取到的connection都是同一个了。如果还不懂,建议去看一下ThreadLocal的用法或者原理了。
备注
以上仅个人理解,不保证没有疏漏之处。