先分析一下spring-mybatis整合之后,mapper的注入过程:

基于上图的分析,spring-mybatis 环境已完成初始化,现在开始分析 mapper 的具体执行情况。
代码执行到mapper的具体操作时
public User getUser(Integer id) {
userMapper.getUser(1);
personMapper.getUser(1);
return null;
}
方法的调用链如下:
MapperProxy.invoke() ->
MapperMethod.execute() ->
SqlSessionTemplate.selectOne()
// Session接口的selectOne方法
// org.apache.ibatis.session.SqlSession#selectOne(java.lang.String)
@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
显然具体的执行逻辑交给了 sqlSessionTemplate 的 sqlSessionProxy 去完成。
// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
// 这个内部类就是sqlSessionProxy的invocationHandler
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 下面关注下这个getSqlSession()方法
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);
}
}
}
}
看一下这个SqlSessionUtils的getSqlSession方法
// 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);
// session由sqlSessionHolder持有,下面关注下怎么拿到的holder
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;
}
// org.springframework.transaction.support.TransactionSynchronizationManager#getResource
@Nullable
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;
}
// org.springframework.transaction.support.TransactionSynchronizationManager#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;
}
关注一下这个resources
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
第一次进来,这个resources是没有sqlSessionHolder的。 这时候就要openSession并registerSessionHolder了。
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
看一下Utils的registerSessionHolder()方法。
// org.mybatis.spring.SqlSessionUtils#registerSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// 这个方法的返回值意味着是否开启了事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because DataSource is not transactional");
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
/**
* Return if transaction synchronization is active for the current thread.
* Can be called before register to avoid unnecessary instance creation.
* @see #registerSynchronization
*/
public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
}
如果开启了事务,就会去注册SessionHolder
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
// org.springframework.transaction.support.TransactionSynchronizationManager#bindResource
// Bind the given resource for the given key to the current thread.
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
// 这里将<DefaultSqlSessionFactory,SqlSessionHolder>添加了进了resource里面
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
//org.springframework.transaction.support.TransactionSynchronizationManager#registerSynchronization
// Register a new transaction synchronization for the current thread.
public static void registerSynchronization(TransactionSynchronization synchronization)
throws IllegalStateException {
Assert.notNull(synchronization, "TransactionSynchronization must not be null");
// static final ThreadLocal<Set<TransactionSynchronization>> synchronizations
Set<TransactionSynchronization> synchs = synchronizations.get();
if (synchs == null) {
throw new IllegalStateException("Transaction synchronization is not active");
}
synchs.add(synchronization);
}
// org.springframework.transaction.support.ResourceHolderSupport#released
// Decrease the reference count by one because the holder has been released
public void requested() {
this.referenceCount++;
}
如果没有开启事务,就不去registerSqlSessionHolder。
最后将sqlSession返回。回到invoke方法:
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;
}
这里判断一下是否开启了事务并且是同一个事务(猜的)。
// org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional
// Returns if the {@code SqlSession} passed as an argument is being managed by Spring
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
return (holder != null) && (holder.getSqlSession() == session);
}
不是就直接commmit,否则就不commit。然后返回查询到的result结果集。 返回之前还要执行finally语句块:
finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
spring与mybatis整合之后,sqlSession的开闭都是由spring负责了。 看下closeSqlSession:
// org.mybatis.spring.SqlSessionUtils#closeSqlSession
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else {
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
如果当前sessionFactory对应的sessionHolder注册过了,则调用holder.release();否则直接调用session.close()进行关闭。
public void released() {
this.referenceCount--;
}
看到这里的close思路就很清晰了。mybatis的一级缓存是基于sqlSession的,与spirng整合之后,sqlSession的开闭都交给了spring控制。这里sqlSession的开闭方式受到了@Transactional注解的影响。
当添加了@Transactional注解之后:
- 执行到SqlSessionUtils的getSqlSession方法的时候,会先尝试从TransactionSynchronizationManager的resources里拿到此时的sqlSessionFactory对应的sqlSesisonHolder。
- 拿不到则创建新的SqlSession和holder注册进这个resources。 返回sqlSession之后(返回到SqlSessiongTemplate内部类的invoke逻辑),会执行被代理的select方法(session接口被代理的不同方法)。
- 拿到查询结果之后,因为添加了@Transactional注解而不会执行sqlSession的commit方法(此方法会将mybatis一级缓存内容放进二级缓存并清空一级缓存),而且在返回之前关闭sqlSession时也不会去直接调用sqlSession的close()方法,而是去调用holder的releaseed()方法去将holder的被引用数减一(this.referenceCount--)。
当没有添加@Transactional注解时:
- 执行到SqlSessionUtils的getSqlSession方法的时候,也会先尝试从TransactionSynchronizationManager的resources里拿到此时的sqlSessionFactory对应的sqlSesisonHolder(不过这时是肯定拿不到的),直接创建新的SqlSession并返回。
- 然后就是回到SqlSessionTemplate去执行被代理的select方法(session接口被代理的不同方法),并且直接执行sqlSession的commit()方法和close方法。
综上,在没有添加@Transactional注解时,sqlSession每次都是创建的新的而且用完就commit()并且close(),所以此时的一级缓存是失效的;当添加了@Transactional注解之后,创建的sqlSession都会包装成sqlSessionHolder注册到一个static ThreadLocal的resources里面,并且不去commit和close,事务内拿到的都是同一个sqlSession,所以此时的以及缓存时生效的。