Unlock MyBatis Performance: Mastering First and Second Level Caches
This article explains how MyBatis first‑level (session) and second‑level (cross‑session) caches work, the conditions required for them to function, common pitfalls when integrating with SpringBoot, and step‑by‑step instructions to enable and safely use second‑level caching in real projects.
Overview of MyBatis Caching
MyBatis provides a two‑level cache architecture—first‑level cache (session scoped) and second‑level cache (shared across sessions) — to improve query performance.
First‑Level Cache
The first‑level cache is enabled by default and works only within the same SqlSession. It is cleared when the session ends.
Typical use case: when an order table and a member table have a one‑to‑many relationship, repeated queries for the same member_id can be served from the first‑level cache.
Conditions for the first‑level cache to take effect:
Same session
Same mapper (namespace)
Same statement (method)
Identical SQL and parameters
No session.clearCache() call between queries
No insert/update/delete executed between queries
Why First‑Level Cache May Not Work with SpringBoot
When MyBatis is integrated with SpringBoot, each SQL execution creates a new SqlSession by default, so the session‑scoped cache never reuses data.
Solution: annotate the service method with @Transactional so that Spring reuses the same SqlSession within the transaction.
@Transactional
public void someMethod() {
// queries here share the same SqlSession and first‑level cache
}How First‑Level Cache Is Managed Internally
During a query, SqlSessionUtils.getSqlSession tries to obtain a session from the transaction manager; if none exists, it creates a new DefaultSqlSession. The session is stored in a ThreadLocal via TransactionSynchronizationManager.bindResource, allowing subsequent queries in the same thread to reuse it.
Second‑Level Cache
Second‑level cache is disabled by default and must be enabled manually. It is suitable for static data such as dictionary, menu, or permission tables that are read‑heavy and rarely updated.
Enabling steps:
Set mybatis-plus.configuration.cache-enabled: true in application.yml.
Add @CacheNamespace on the mapper interface.
Make the entity class implement Serializable.
mybatis-plus:
configuration:
cache-enabled: true @Mapper
@CacheNamespace
public interface ItemMapper {
// mapper methods
}Conditions for Second‑Level Cache to Work
Cache is populated only after the session is committed or closed.
Same mapper (namespace) and same statement.
Identical SQL and parameters.
If readWrite=true (default), the cached object must implement Serializable.
Cache Eviction Rules
Cache is cleared only after a modifying session (insert/update/delete) is committed.
Any update operation clears the entire namespace cache.
How Second‑Level Cache Is Filled
When a query finishes, SqlSession.commit() triggers TransactionalCache.flushPendingEntries, which writes the result into the second‑level cache managed by TransactionalCacheManager. Subsequent queries retrieve data from PerpetualCache without hitting the database.
Why MyBatis Disables Second‑Level Cache by Default
It is not recommended because the cache is scoped by namespace; an update in one mapper does not invalidate caches of other mappers, leading to stale data when tables are joined across namespaces.
Example: ItemMapper updates an item name, but XxxMapper still returns the old name from its second‑level cache because the namespaces are independent.
@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("new name");
itemMapper.updateById(item);
List<PaymentVO> second = xxxMapper.getPaymentVO(1L); // returns cached old name
}This demonstrates the hidden risk of second‑level caching when tables are related across different mappers.
Conclusion
First‑level cache is simple, session‑scoped, and safe; second‑level cache can improve performance for static data but must be used with caution due to potential data inconsistency across namespaces.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
