MyBatis Cache Introduction, Issues, and Custom Relation Cache Implementation
This article explains MyBatis's first‑level and second‑level caching mechanisms, demonstrates data inconsistency problems caused by joined‑table queries, and provides a custom annotation‑driven relation cache solution with code examples for configuring and validating the cache behavior.
1. MyBatis Cache Overview
MyBatis supports caching; by default only first‑level cache is enabled, second‑level cache must be configured manually.
First‑level cache is scoped to a single SqlSession (i.e., a transaction). Repeated queries of the same Mapper method within the same session return cached results unless an insert, update, or delete occurs.
Second‑level cache is application‑wide, shared across SqlSessions. When enabled, the first query result is stored in a global cache for the Mapper; subsequent identical queries retrieve data from this cache unless the Mapper performs an update.
2. Second‑level Cache Issues
When a Mapper performs a simple single‑table query, the cache works fine, but joined queries can cause stale data. For example, UserMapper joins the Organization table; updating Organization does not invalidate UserMapper’s cache, leading to inconsistent organization information.
2.1 Data Inconsistency Verification
SQL used:
SELECT u.*, o.name org_name FROM user u LEFT JOIN organization o ON u.org_id = o.id WHERE u.id = #{userId}Mapper method: UserInfo queryUserInfo(@Param("userId") String userId); Service method:
public UserEntity queryUser(String userId) {
UserInfo userInfo = userMapper.queryUserInfo(userId);
return userInfo;
}Initial query returns user with orgName "组织1". After updating organization to "组织2", the cached user still shows "组织1", demonstrating the inconsistency.
2.2 Problem‑Handling Idea
Define related Mapper2 in Mapper1 configuration.
When creating cache instance cache1 for Mapper1, read cache2 information of related Mapper2.
Store a reference to cache2 inside cache1.
When cache1 is cleared, also clear cache2.
3. Implementing Relation Cache Refresh
Enable second‑level cache using MyBatis‑Plus: mybatis-plus.configuration.cache-enabled=true Use custom annotation @CacheRelations and custom cache classes RelativeCache and RelativeCacheContext.
Annotation definition:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRelations {
Class<?>[] from() default {};
Class<?>[] to() default {};
}Key parts of RelativeCache implementation (methods putObject, getObject, clear, addRelation, loadRelations, etc.) are shown.
Cache context stores mappings between Mapper classes and their caches, as well as pending relations.
Usage example on UserMapper:
@Repository
@CacheNamespace(implementation = RelativeCache.class, eviction = RelativeCache.class, flushInterval = 30 * 60 * 1000)
@CacheRelations(from = OrganizationMapper.class)
public interface UserMapper extends BaseMapper<UserEntity> {
UserInfo queryUserInfo(@Param("userId") String userId);
}Corresponding XML must include
<cache-ref namespace="com.mars.system.dao.UserMapper"/>to enable caching.
Similarly configure OrganizationMapper with the same cache implementation.
After configuration, the validation steps show that after updating the organization, the user query returns the updated organization name, confirming the relation cache works as expected.
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.
