Understanding MyBatis First‑Level and Second‑Level Cache and Their Integration with Spring Boot
The article explains MyBatis first‑level and second‑level caching mechanisms, their activation conditions, integration pitfalls with Spring Boot, and how to enable and use them safely, including configuration steps, code examples, and warnings about hidden risks of second‑level cache.
To improve query performance, MyBatis provides a two‑level cache architecture consisting of first‑level (session) cache and second‑level (cross‑session) cache.
The main difference is that the first‑level cache lives only within a SqlSession, while the second‑level cache can be shared across multiple sessions.
First‑level cache is enabled by default and easy to use; second‑level cache must be manually enabled and requires careful handling to avoid hidden issues.
First‑Level Cache
Use Cases
When an order table has a many‑to‑one relationship with a member table, developers often split queries: first fetch orders, then fetch members using the member_id field, and finally combine the data. If the order table contains duplicate member_id values, many redundant member queries occur.
MyBatis solves this by using first‑level cache: within the same SqlSession, identical statements with identical parameters are served from the cache instead of hitting the database.
Because the cache is limited to the current session, it is also called a session cache.
Effective Conditions
The same session must be used.
The same mapper (namespace) must be used.
The same statement (method inside the mapper) must be used.
The same SQL and parameters must be used.
No call to session.clearCache() occurs between the queries.
No insert/update/delete operation is executed between the queries, regardless of whether the data changes affect the cached result.
Why First‑Level Cache May Not Work with Spring Boot
MyBatis creates a new SqlSession for each SQL execution when integrated with Spring Boot, so the cache cannot be shared across calls.
When a mapper method is invoked, MyBatis eventually calls SqlSessionUtils.getSqlSession, which tries to obtain a session from the transaction manager; if no transaction is active, it creates a new DefaultSqlSession.
Therefore, even consecutive identical queries in the same method use different sessions and bypass the first‑level cache.
Solution: Enable Transaction Management
When a method is annotated with @Transactional, Spring opens a transaction, causing MyBatis to reuse the same SqlSession. The cache then becomes effective.
Adding @Transactional to the service method makes the first‑level cache work, as demonstrated by the logs showing the same SqlSession being retrieved from the transaction manager.
Second‑Level Cache
Use Cases
Static data such as dictionary tables, menu tables, and permission tables are read‑only or rarely changed but are queried frequently. First‑level cache cannot serve these scenarios because it is limited to a single session.
Second‑level cache lives outside the session, allowing multiple sessions to share the same cached data.
How to Enable
1) Set cache-enabled to true in the MyBatis configuration (e.g., mybatis‑plus.configuration.cache-enabled: true).
mybatis-plus:
configuration:
cache-enabled: true2) Add the @CacheNamespace annotation to the mapper interface.
3) Ensure the entity class implements java.io.Serializable.
Effective Conditions
The cache is populated only after the session is committed or closed.
The same mapper (namespace) must be used.
The same statement (method) must be used.
The same SQL and parameters must be used.
If readWrite=true (default), the cached objects must implement Serializable.
Cache Eviction Conditions
Only after a modifying session (insert/update/delete) is committed does the cache get cleared.
Updates defined in XML do not clear the cache annotated with @CacheNamespace.
Any insert, update, or delete operation clears the entire cache for the corresponding namespace.
How the Source Fills the Second‑Level Cache
When a query finishes, MyBatis calls SqlSession.commit. During commit, TransactionalCache.flushPendingEntries is invoked, which writes the result into the second‑level cache.
How Queries Use the Second‑Level Cache
During a query, MybatisCachingExecutor.query checks TransactionalCacheManager for a cached entry using a key composed of mapper, method, SQL, and parameters. If the key is found, the cached result is returned without hitting the database.
The key generation and cache lookup involve several decorator layers, ultimately delegating to PerpetualCache and logging via LoggingCache.
Why MyBatis Does Not Enable Second‑Level Cache by Default
Second‑level cache is not recommended because it can introduce hidden hazards. The cache is scoped by namespace; an insert/update/delete in one namespace clears only that namespace’s cache, leaving other namespaces’ caches stale.
When different mappers share related data, stale caches can cause inconsistent query results, as illustrated by the following example.
@Mapper
@Repository
@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);
}
@Autowired
private XxxMapper xxxMapper;
@Test
void test() {
System.out.println("=== query PaymentVO ===");
List<PaymentVO> voList = xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList.get(0)));
System.out.println("=== update item name ===");
Item item = itemMapper.selectById(1);
item.setName("java并发编程");
itemMapper.updateById(item);
System.out.println("=== query PaymentVO again ===");
List<PaymentVO> voList2 = xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList2.get(0)));
}Because the two mappers belong to different namespaces, the update on ItemMapper does not invalidate the cache of XxxMapper. The second query still returns the old itemName from the cache, demonstrating the risk.
Therefore, second‑level cache should be used with caution, especially when tables are joined across different mappers.
Source Attribution
Original article: blog.csdn.net/xujingyiss/article/details/123481116
Note: The following promotional content (technical community invitation) is not part of the technical explanation.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
