MyBatis提供了两种级别的缓存, 分别时一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,只在SqlSession对象内部存储缓存数据,如果SqlSession对象不一样就无法命中缓存,二级缓存是mapper级别的缓存,只要使用的Mapper类一样就能够共享缓存。
在查询数据时,Mybatis会优先查询二级缓存,如果二级缓存没有则查询一级缓存,都没有才会进行数据库查询。
Mybatis的一级缓存默认是开启的,而二级缓存需要在mapper.xml配置文件内或通过@CacheNamespace注解手动开启。
需要注意的是,在于Spring进行整合时,必须开启事务一级缓存会生效,因为不开启缓存的话每次查询都会重新创建一个SqlSession对象,因此无法共享缓存。
通过@CacheNamespace开启某个Mapper的二级缓存。
@Mapper
@CacheNamespace
public interface EmployeeMapper extends BaseMapper<Employee> {
}
开启所有的二级缓存:
mybatis-plus:
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
cache-enabled: true
Mybatis内置的二级缓存在分布式环境下存在分布式问题,无法使用,但是我们可以整合Redis来实现分布式的二级缓存。
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.4.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>3.24.3version>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.8.22version>
dependency>
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfiguration {
private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
private static final GenericJackson2JsonRedisSerializer JACKSON__SERIALIZER = new GenericJackson2JsonRedisSerializer();
@Bean
@Primary
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
//设置缓存过期时间
RedisCacheConfiguration redisCacheCfg = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(STRING_SERIALIZER))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(JACKSON__SERIALIZER));
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheCfg)
.build();
}
@Bean
@Primary
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// key序列化
redisTemplate.setKeySerializer(STRING_SERIALIZER);
// value序列化
redisTemplate.setValueSerializer(JACKSON__SERIALIZER);
// Hash key序列化
redisTemplate.setHashKeySerializer(STRING_SERIALIZER);
// Hash value序列化
redisTemplate.setHashValueSerializer(JACKSON__SERIALIZER);
// 设置支持事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public RedisSerializer<Object> redisSerializer() {
//创建JSON序列化器
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//必须设置,否则无法将JSON转化为对象,会转化成Map类型
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
}
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
@Slf4j
public class MybatisRedisCache implements Cache {
// redisson 读写锁
private final RReadWriteLock redissonReadWriteLock;
// redisTemplate
private final RedisTemplate redisTemplate;
// 缓存Id
private final String id;
//过期时间 10分钟
private final long expirationTime = 1000*60*10;
public MybatisRedisCache(String id) {
this.id = id;
//获取redisTemplate
this.redisTemplate = SpringUtil.getBean(RedisTemplate.class);
//创建读写锁
this.redissonReadWriteLock = SpringUtil.getBean(RedissonClient.class).getReadWriteLock("mybatis-cache-lock:"+this.id);
}
@Override
public void putObject(Object key, Object value) {
//使用redis的Hash类型进行存储
redisTemplate.opsForValue().set(getCacheKey(key),value,expirationTime, TimeUnit.MILLISECONDS);
}
@Override
public Object getObject(Object key) {
try {
//根据key从redis中获取数据
Object cacheData = redisTemplate.opsForValue().get(getCacheKey(key));
log.debug("[Mybatis 二级缓存]查询缓存,cacheKey={},data={}",getCacheKey(key), JSONUtil.toJsonStr(cacheData));
return cacheData;
} catch (Exception e) {
log.error("缓存出错",e);
}
return null;
}
@Override
public Object removeObject(Object key) {
if (key != null) {
log.debug("[Mybatis 二级缓存]删除缓存,cacheKey={}",getCacheKey(key));
redisTemplate.delete(key.toString());
}
return null;
}
@Override
public void clear() {
log.debug("[Mybatis 二级缓存]清空缓存,id={}",getCachePrefix());
Set keys = redisTemplate.keys(getCachePrefix()+":*");
redisTemplate.delete(keys);
}
@Override
public int getSize() {
Long size = (Long) redisTemplate.execute((RedisCallback<Long>) RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.redissonReadWriteLock;
}
@Override
public String getId() {
return this.id;
}
public String getCachePrefix(){
return "mybatis-cache:%s".formatted(this.id);
}
private String getCacheKey(Object key){
return getCachePrefix()+":"+key;
}
}
//开启二级缓存并指定缓存类
@CacheNamespace(implementation = MybatisRedisCache.class,eviction = MybatisRedisCache.class)
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}