二级缓存机制原理详解
1. 整体架构
MyBatis-Plus二级缓存采用装饰器模式实现,核心组件包括:
- Cache接口:定义缓存基本操作
- PerpetualCache:基础缓存实现(HashMap)
- 装饰器:如LruCache、FifoCache等
- TransactionalCache:事务缓存管理器
2. 工作流程
-
初始化阶段:
- 解析Mapper XML中的<cache>配置
- 创建基础缓存实例
- 根据配置添加装饰器
-
查询流程:
sequenceDiagram participant Client participant SqlSession participant Executor participant Cache participant DB Client->>SqlSession: 执行查询 SqlSession->>Executor: query() Executor->>Cache: 检查缓存 alt 缓存命中 Cache-->>Executor: 返回缓存结果 else 缓存未命中 Executor->>DB: 执行查询 DB-->>Executor: 返回结果 Executor->>Cache: 缓存结果 end Executor-->>SqlSession: 返回结果 SqlSession-->>Client: 返回结果 -
更新流程:
sequenceDiagram participant Client participant SqlSession participant Executor participant Cache participant DB Client->>SqlSession: 执行更新 SqlSession->>Executor: update() Executor->>DB: 执行SQL DB-->>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 {
@Autowired
private 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;
}
@Transactional
public 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");
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
// 自定义序列化逻辑
redisTemplate.opsForValue().set(
generateRedisKey(key),
serialize(value),
30, TimeUnit.MINUTES // 设置TTL
);
}
// 其他方法实现...
}
案例2:多级缓存策略
- 配置多级缓存
@Configuration
public class CacheConfig {
@Bean
public 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;
}
@Override
public 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;
@Override
public 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/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
password:
database: 0
mybatis-plus:
configuration:
cache-enabled: true
@Configuration
public class RedisCacheConfig {
@Bean
public 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;
}
@Bean
public 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 {
@Autowired
private 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 {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public 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等集中式缓存实现
- 注意设置合理的缓存过期时间防止脏数据
169



被折叠的 条评论
为什么被折叠?



