Backend Development 11 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding the Thread‑Safety Mechanism of MyBatis SqlSessionTemplate in Spring Integration

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.

JavaSpringMyBatisThreadLocalSqlSessionTemplateTransactionManagement
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.