Understanding the Thread‑Safety Mechanism of MyBatis SqlSessionTemplate in Spring Integration
This article explains why MyBatis' default SqlSession implementation is not thread‑safe, how Spring‑MyBatis integration introduces SqlSessionTemplate and its dynamic proxy, and how transaction‑bound ThreadLocal management ensures safe SqlSession usage across multiple threads.
When analyzing the thread‑unsafe nature of MyBatis' first‑level cache, we discover that the default SqlSession implementation ( DefaultSqlSession ) holds a local cache per session, which can cause data inconsistency and dirty reads when accessed concurrently.
Spring‑MyBatis integration returns a SqlSessionTemplate instance from MapperFactoryBean#getObject . This template wraps a SqlSessionProxy , which is a dynamic proxy for the SqlSession interface.
SqlSessionTemplate Constructor
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 1. Parameter validation
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
// 2. Member assignment
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 3. Create dynamic proxy for SqlSession
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}The constructor performs parameter checks, assigns fields, and builds a dynamic proxy using newProxyInstance with SqlSessionInterceptor as the invocation handler.
SqlSessionProxy Construction
The proxy delegates method calls to a real SqlSession obtained via getSqlSession . The interceptor’s invoke method looks like:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Obtain the actual SqlSession (not thread‑safe itself)
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// Call the target method (select, update, etc.)
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// ... exception handling omitted
}
}The interceptor fetches a thread‑bound SqlSession , invokes the requested method, and commits the session if it is not part of an existing transaction.
Getting a SqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
// 1. Try to obtain a SqlSessionHolder from the current transaction
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 2. No holder -> open a new SqlSession and register it
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}This method first checks the transaction‑synchronization manager for an existing SqlSessionHolder . If none is found, it opens a new session and registers it.
Registering the SqlSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory,
ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
}
// ... other code omitted
}
}The holder is bound to a ThreadLocal<Map<Object, Object>> via TransactionSynchronizationManager , ensuring that each thread works with its own SqlSession and thus achieving thread safety.
Overall Flow
When a method on SqlSessionTemplate is called, it first looks for a SqlSessionHolder in the current thread’s ThreadLocal .
If found, the holder provides the existing SqlSession ; otherwise a new session is opened via the factory.
The newly created session is wrapped in a SqlSessionHolder and bound to the transaction manager, making it available for subsequent calls in the same transaction.
By combining ThreadLocal storage, transaction synchronization, and dynamic proxy interception, SqlSessionTemplate guarantees that MyBatis operations are performed with a thread‑safe SqlSession while still respecting Spring’s transaction lifecycle.
In summary, SqlSessionTemplate leverages Spring’s transaction management and a thread‑local binding mechanism to provide a safe, reusable SqlSession instance, preventing the concurrency issues inherent in MyBatis’ default session implementation.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.