Unlock MyBatis Performance: Master First‑Level and Second‑Level Caches in SpringBoot
This article explains how MyBatis implements first‑level (session) and second‑level (namespace) caches, details their activation conditions, common pitfalls—especially when integrated with SpringBoot—and provides step‑by‑step configuration, code examples, and best‑practice recommendations to ensure cache effectiveness while avoiding hidden hazards.
MyBatis Caching Overview
To improve query performance, MyBatis provides a two‑level caching architecture: a first‑level (session) cache and a second‑level (namespace) cache.
First‑Level Cache
Application scenario : When a query returns duplicate foreign‑key values (e.g., many orders share the same member_id), the same SQL executed within a single SqlSession can be served from the first‑level cache, avoiding repeated database hits.
Effective conditions :
Same SqlSession (session scope)
Same mapper namespace
Same statement (method) within the mapper
Identical SQL and parameters
No call to session.clearCache() between queries
No insert/update/delete executed between the queries
Why it may not work with SpringBoot : MyBatis creates a new SqlSession for each SQL execution by default. Consequently, the first‑level cache never sees the same session and appears ineffective.
When a transaction is active, SpringBoot reuses the same SqlSession, allowing the cache to work. Adding @Transactional to the service method enables the cache:
@Transactional
public void someServiceMethod() {
// queries here share the same SqlSession and benefit from first‑level cache
}During the first query, MyBatis obtains a SqlSession from SqlSessionUtils. If a transaction is present, the same session is stored in TransactionSynchronizationManager (ThreadLocal). Subsequent queries in the same transaction retrieve the cached session, making the first‑level cache effective.
Second‑Level Cache
Application scenario : Static reference data such as dictionaries, menus, or permission tables are read frequently but change rarely. A second‑level cache, shared across sessions, can serve these hot‑spot queries.
Enabling steps :
Set cache-enabled: true in the MyBatis configuration (e.g., mybatis-plus.configuration in application.yml).
Add @CacheNamespace to the mapper interface.
Make the entity class implement java.io.Serializable (required when readWrite=true).
Effective conditions :
Cache is populated only after the session is committed or closed.
Same mapper namespace and same statement.
Identical SQL and parameters.
If readWrite=true, the cached objects must be Serializable.
Cache eviction conditions :
Any insert, update, or delete in the same namespace clears the entire namespace cache.
Only after a modifying transaction commits does the eviction occur.
When a query finishes, MyBatis calls SqlSession.commit(). This triggers TransactionalCache.flushPendingEntries(), which writes the result into the second‑level cache:
During a subsequent query, MybatisCachingExecutor.query() looks up the cache via TransactionalCacheManager. If a matching key (built from mapper, method, SQL, and parameters) is found, the result is returned without hitting the database:
Why MyBatis disables second‑level cache by default : It is generally discouraged because the cache is scoped to a mapper namespace. Updates in a different mapper do not invalidate the cached data, which can lead to stale results.
Hidden hazard example : Two mappers, ItemMapper and XxxMapper, share the same namespace cache. Updating an Item via ItemMapper does not clear the cache used by XxxMapper, so a subsequent query through XxxMapper may return outdated itemName values.
@Mapper
@CacheNamespace
public interface XxxMapper {
@Select("select i.id itemId, i.name itemName, p.amount, p.unit_price unitPrice " +
"from item i JOIN payment p on i.id = p.item_id where i.id = #{id}")
List<PaymentVO> getPaymentVO(Long id);
}
@Test
void test() {
List<PaymentVO> first = xxxMapper.getPaymentVO(1L);
Item item = itemMapper.selectById(1L);
item.setName("java并发编程");
itemMapper.updateById(item);
List<PaymentVO> second = xxxMapper.getPaymentVO(1L);
// second still returns the old itemName because XxxMapper's cache was not cleared
}Therefore, while second‑level cache can improve performance for static data, its namespace‑level granularity and automatic eviction rules make it prone to inconsistencies; use it cautiously.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
