实测版本
<!-- tk.mybatis , 其中mybatis版本 3.5.3 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
mybatis二级缓存相关注解
@org.apache.ibatis.annotations.CacheNamespace
- 作用于mapper接口上, 标记当前mapper开启二级缓存
- 对应mapper.xml文件中的
<cache/>
标签
@CacheNamespace
public interface DeviceInfoMapper {
...
}
这种方式标记的,对mapper.xml中的sql无效, 但是对于使用
@Select
注解中的sql有效
@org.apache.ibatis.annotations.CacheNamespaceRef.CacheNamespace
标记当前mapper接口引用了指定class的缓存
- 对应mapper.xml文件中的
<cache-ref/>
标签
@CacheNamespaceRef(DeviceInfoMapper.class) // 标记当前mapper使用与 DeviceInfoMapper 类相同的namespace
public interface DeviceNameDefinitionMapper {
...
}
如果在这个接口中,有增删改相关操作, 将会清空指定
namespace
下的缓存,特别是有关联查询的几个mapper,需要在同一个namespace下,防止缓存中的脏数据产生
@org.apache.ibatis.annotations.Property
- 标记在接口方法上,用于传入相关配置属性
- 对应mapper.xml文件中的
<property></property>
标签
<cache>
<property name="cacheSec" value="600"/>
</cache>
开启二级缓存
简单讲就是加注解,加配置
- springboot项目,如果没有指定的mybatis.xml配置文件,建议在 yaml或 properties 配置文件中增加配置项:
mybatis:
configuration:
cache-enabled: true
如果有 mybatis.xml的配置文件, 在文件中增加配置
<settings>
<setting name = "cacheEnabled" value = "true" />
</settings>
- 对应mapper.xml里增加
<!-- type属性中,指定的缓存的实现类,这里用到自定义实现,将缓存放到redis里,以确保分布式服务缓存一致 -->
<cache type="com.kartist.demo.common.config.MybatisRedisCache"/>
- 如果在其他mapper中,有关联查询的, 在相关的mapper里增加
<cache-ref/>
标签
<!-- namespace为引用的mapper路径,一个原则 有关联查询的相关表,在同一个namespace下,具体是哪个不重要 -->
<cache-ref namespace="com.kartist.demo.worker.dao.BusinessUnitInfoMapper"/>
例如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kartist.demo.device.dao.DeviceNameDefinitionMapper">
<cache type="com.kartist.demo.common.config.MybatisRedisCache"/>
<cache-ref namespace="com.kartist.demo.device.dao.DeviceInfoMapper"/>
...
</mapper>
- 在接口上增加注解
@CacheNamespaceRef(DeviceInfoMapper.class)
如果接口上不增加这个注解,在调用
tk.mybatis
或mybatis-plus
提供的原生接口时有可能导致缓存中脏数据的产生
整合redis
简单讲就是自定义缓存方式,在用的地方指定自定义的缓存方式
- 需要实现
org.apache.ibatis.cache.Cache
接口 - 在开启二级缓存的地方,指定使用实现类
- MybatisRedisCache
import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class MybatisRedisCache implements Cache {
final static String NAME_SPACE ="mybatis-cache:";
private static RedisTemplate<String, Object> redisTemplate;
private static int cacheSec;
private final String id;
/**
* The {@code ReadWriteLock}.
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
log.warn("MybatisRedisCache:id=" + id);
this.id = id;
}
public static void setCacheSec(int cacheSec) {
MybatisRedisCache.cacheSec = cacheSec;
}
public static void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
MybatisRedisCache.redisTemplate = redisTemplate;
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
try {
log.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject: key=" + key + ",value=" + value);
if (null != value) {
if (cacheSec > 0) {
// 这里简单起见, 先固定好了缓存的时长
// 也可以尝试 结合<property name="cacheSec" value="600"/> 在不同的mapper中指定特殊的缓存时长
// 也可以根据实际业务情况,制定缓存策略
redisTemplate.opsForValue().set(NAME_SPACE + key.toString(), value, cacheSec, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(NAME_SPACE + key.toString(), value);
}
}
} catch (Exception e) {
e.printStackTrace();
log.error("redis保存数据异常!");
}
}
@Override
public Object getObject(Object key) {
try {
log.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject: key=" + key);
int size = this.getSize();
if (null != key) {
// 这里很坑, 如果选用的redis序列化反序列化的方式不合适,在返回结果后可能会报类转换异常
return redisTemplate.opsForValue().get(NAME_SPACE + key.toString());
}
} catch (Exception e) {
e.printStackTrace();
log.error("redis获取数据异常!");
}
return null;
}
@Override
public Object removeObject(Object key) {
try {
if (null != key)
return redisTemplate.delete(NAME_SPACE + key.toString());
} catch (Exception e) {
e.printStackTrace();
log.error("redis获取数据异常!");
}
return null;
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys(NAME_SPACE + "*");
if (CollectionUtil.isNotEmpty(keys)) {
redisTemplate.delete(keys);
}
log.debug(">>>>>>>>>>>>>>>>>>>>>>>>clear");
}
@Override
public int getSize() {
Set<String> keys = redisTemplate.keys(NAME_SPACE + "*");
if (CollectionUtil.isNotEmpty(keys)) {
return keys.size();
}
return 0;
}
}
由于需要用到redis,因此还需要写一个配置,用于注入redisTemplate
@Configuration
public class MybatisRedisCacheConfiguration {
@Value("${mybatis.cache-sec:600}")
private int cacheSec;
@Autowired
public void config(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 序列化 这里也可根据实际情况,使用其他的序列化实现类,
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
MybatisRedisCache.setRedisTemplate(template);
MybatisRedisCache.setCacheSec(cacheSec);
}
}