二級緩存機制原理詳解
1. 整體架構
MyBatis-Plus二級緩存采用裝飾器模式實現,核心組件包括:
- ?Cache接口?:定義緩存基本操作
- ?PerpetualCache?:基礎緩存實現(HashMap)
- ?裝飾器?:如LruCache、FifoCache等
- ?TransactionalCache?:事務緩存管理器
2. 工作流程
?初始化階段?:
- 解析Mapper XML中的<cache>配置
- 創建基礎緩存實例
- 根據配置添加裝飾器
?查詢流程?:
sequenceDiagramparticipant Clientparticipant SqlSessionparticipant Executorparticipant Cacheparticipant DBClient->>SqlSession: 執行查詢SqlSession->>Executor: query()Executor->>Cache: 檢查緩存alt 緩存命中Cache-->>Executor: 返回緩存結果else 緩存未命中Executor->>DB: 執行查詢DB-->>Executor: 返回結果Executor->>Cache: 緩存結果endExecutor-->>SqlSession: 返回結果SqlSession-->>Client: 返回結果
?更新流程?:
sequenceDiagramparticipant Clientparticipant SqlSessionparticipant Executorparticipant Cacheparticipant DBClient->>SqlSession: 執行更新SqlSession->>Executor: update()Executor->>DB: 執行SQLDB-->>Executor: 返回影響行數Executor->>Cache: 清除相關緩存Executor-->>SqlSession: 返回結果SqlSession-->>Client: 返回結果
3. 關鍵實現細節
- ?緩存鍵生成?:基于Mapper ID + 方法參數 + SQL + 分頁等生成唯一鍵
- ?事務支持?:通過TransactionalCacheManager管理事務提交/回滾時的緩存操作
- ?序列化?:默認使用JVM序列化,可配置為其他序列化方式
生產案例詳細實現步驟
案例1:電商商品緩存系統
- ?基礎配置?
<!-- mybatis-config.xml -->
<configuration><settings><setting name="cacheEnabled" value="true"/><!-- 配置緩存序列化方式 --><setting name="defaultCacheType" value="com.example.MyCustomCache"/></settings>
</configuration>
- ?Mapper配置?
<!-- ProductMapper.xml -->
<mapper namespace="com.example.mapper.ProductMapper"><cache type="org.mybatis.caches.redis.RedisCache"eviction="LRU"flushInterval="300000"size="1024"readOnly="false"/><select id="selectById" resultType="Product" useCache="true">SELECT * FROM product WHERE id = #{id}</select>
</mapper>
- ?服務層實現?
@Service
public class ProductServiceImpl implements ProductService {@Autowiredprivate ProductMapper productMapper;// 帶緩存穿透保護的查詢public Product getProductWithCache(Long id) {// 1. 先查緩存Product product = productMapper.selectById(id);if (product != null) {return product;}// 2. 緩存不存在,查數據庫product = productMapper.selectFromDb(id);if (product == null) {// 防止緩存穿透:緩存空對象product = new Product();product.setId(id);product.setName("NULL_OBJECT");productMapper.cacheNullObject(product);} else {// 放入緩存productMapper.cacheProduct(product);}return product;}@Transactionalpublic void updateProduct(Product product) {// 1. 更新數據庫productMapper.updateById(product);// 2. 清除緩存productMapper.clearCache(product.getId());// 3. 異步重建緩存CompletableFuture.runAsync(() -> {Product freshProduct = productMapper.selectFromDb(product.getId());productMapper.cacheProduct(freshProduct);});}
}
- ?自定義Redis緩存實現?
public class CustomRedisCache implements Cache {private final String id;private final RedisTemplate<String, Object> redisTemplate;public CustomRedisCache(String id) {this.id = id;this.redisTemplate = SpringContextHolder.getBean("redisTemplate");}@Overridepublic String getId() {return this.id;}@Overridepublic void putObject(Object key, Object value) {// 自定義序列化邏輯redisTemplate.opsForValue().set(generateRedisKey(key), serialize(value),30, TimeUnit.MINUTES // 設置TTL);}// 其他方法實現...
}
案例2:多級緩存策略
- ?配置多級緩存?
@Configuration
public class CacheConfig {@Beanpublic Cache productCache() {// 一級緩存:本地緩存(Caffeine)CaffeineCache localCache = new CaffeineCache("localProductCache",Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(5, TimeUnit.MINUTES).build());// 二級緩存:Redis緩存RedisCache redisCache = new RedisCache("redisProductCache");// 構建多級緩存return new MultiLevelCache(localCache, redisCache);}
}
- ?多級緩存實現?
public class MultiLevelCache implements Cache {private final Cache[] caches;public MultiLevelCache(Cache... caches) {this.caches = caches;}@Overridepublic Object getObject(Object key) {// 按順序查詢緩存for (Cache cache : caches) {Object value = cache.getObject(key);if (value != null) {// 填充上級緩存for (Cache upperCache : getUpperCaches(cache)) {upperCache.putObject(key, value);}return value;}}return null;}// 其他方法實現...
}
生產環境注意事項
?緩存一致性解決方案?:
- 使用消息隊列實現緩存更新
@RabbitListener(queues = "cache.update.queue") public void handleCacheUpdate(CacheUpdateMessage message) {if (message.getType().equals("PRODUCT")) {productMapper.clearCache(message.getId());} }
?監控指標采集?:
public class MonitoredCache implements Cache {private final Cache delegate;private final CacheMetrics metrics;@Overridepublic Object getObject(Object key) {long start = System.currentTimeMillis();try {Object value = delegate.getObject(key);metrics.recordHit(value != null);metrics.recordLatency(System.currentTimeMillis() - start);return value;} catch (Exception e) {metrics.recordError();throw e;}}// 其他方法...
}
- ?緩存預熱策略?:
@PostConstruct
public void preloadHotProducts() {List<Long> hotProductIds = productMapper.selectHotProductIds();hotProductIds.parallelStream().forEach(id -> {Product product = productMapper.selectById(id);// 主動放入緩存productMapper.cacheProduct(product);});
}
通過以上詳細實現,可以構建一個高性能、高可用的二級緩存系統,適用于各種生產環境場景。
下面是springboot用法:
<?xml version="1.0" encoding="UTF-8"?>
<project><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.4</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies>
</project>spring:datasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverredis:host: localhostport: 6379password: database: 0mybatis-plus:configuration:cache-enabled: true@Configuration
public class RedisCacheConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory factory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(30)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));return RedisCacheManager.builder(factory).cacheDefaults(config).transactionAware().build();}
}@CacheNamespace(implementation = RedisCache.class, eviction = RedisCache.class)
public interface UserMapper extends BaseMapper<User> {@Options(useCache = true)@Select("SELECT * FROM user WHERE id = #{id}")User selectUserById(Long id);@CacheEvict@Update("UPDATE user SET name=#{name} WHERE id=#{id}")int updateUserName(@Param("id") Long id, @Param("name") String name);
}@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Cacheable(key = "#id", unless = "#result == null")public User getUserById(Long id) {return userMapper.selectById(id);}@Transactional@CacheEvict(key = "#user.id")public void updateUser(User user) {userMapper.updateById(user);}@CacheEvict(allEntries = true)public void clearAllCache() {// 清空所有緩存}
}@Configuration
@MapperScan("com.example.mapper")
public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> {configuration.setCacheEnabled(true);configuration.setLocalCacheScope(LocalCacheScope.SESSION);};}
}
@CacheNamespace
(MyBatis注解)
- 作用:在Mapper接口級別聲明啟用二級緩存
- 核心屬性:
implementation
:指定自定義緩存實現類(默認PerpetualCache)eviction
:指定緩存淘汰策略(LRU/FIFO等)flushInterval
:緩存刷新間隔(毫秒)size
:緩存最大容量
- 示例:
javaCopy Code
@CacheNamespace(implementation = RedisCache.class, eviction = FifoCache.class, flushInterval = 60000) public interface UserMapper {...}
@Options
(MyBatis注解)
- 作用:為單個SQL語句提供額外配置選項
- 常用屬性:
useCache
:是否使用二級緩存(默認true)flushCache
:執行后是否清空緩存(默認false)timeout
:查詢超時時間(秒)
- 示例:
javaCopy Code
@Options(useCache = true, flushCache = false, timeout = 10) @Select("SELECT * FROM users WHERE id = #{id}") User findById(Long id);
@CacheEvict
(Spring緩存注解)
- 作用:方法執行后清除指定緩存
- 關鍵屬性:
value/cacheNames
:目標緩存名稱key
:要清除的緩存鍵(支持SpEL)allEntries
:是否清空整個緩存區域beforeInvocation
:是否在方法執行前清除
- 典型使用場景:
javaCopy Code
@CacheEvict(value = "userCache", key = "#user.id") public void updateUser(User user) { // 更新操作后會清除userCache中該用戶的緩存 }
三者關系示意圖:
@CacheNamespace
定義Mapper的緩存策略@Options
控制單個SQL語句的緩存行為@CacheEvict
在Service層維護緩存一致性
生產建議:
- 對于讀寫頻繁的數據,建議組合使用
@CacheEvict
和@Cacheable
- 分布式環境建議使用Redis等集中式緩存實現
- 注意設置合理的緩存過期時間防止臟數據