SpringBoot-Redis联合Mybatis实现分布式缓存
相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
配置文件
关于Redis的主从复制、哨兵、集群的配置:Redis搭建主从复制、哨兵集群
spring.redis.host= [你redis服务所在的主机ip]
spring.redis.port=[你redis服务端口 单机一般是6379]
# 单机配置
#spring.redis.database=1
#server.port=8899
#如果redis采用哨兵机制,改成连接哨兵的方式
#sentinel为配置文件中自定的master节点名称
spring.redis.sentinel.master=mymaster
#如果有多个哨兵(集群) 则配置各自的节点地址 ip + 端口
spring.redis.sentinel.nodes=[你redis服务所在的主机ip]:[哨兵端口 单个的话一般是26379]
#数据源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
#用 / 会报错
mybatis.mapper-locations=classpath:com.junsir.mapper/*.xml
mybatis.type-aliases-package=com.junsir.entity
#打印数据库的操作
logging.level.com.junsir.dao=debug
缓存实体类
这是我们要向Redis中缓存的对象
@Data
@Accessors(chain = true)
public class Person implements Serializable {
private String personId;
private String name;
private Integer gender;
private String personAddr;
private Date birthday;
}
Mybatis Dao & Mapper
定义了一个方法 返回所有Person对象,假设我们的缓存生效,将无法看到查询数据库的操作日志,而是显示Cache hit表示击中缓存
public interface PersonDao {
List<Person> findAll();
}
Mapper:
直接启用Mybatis的<-cache>标签,缓存是本机缓存,所以我们需要实现自定义Redis缓存
<mapper namespace="com.junsir.dao.PersonDao">
<!-- 开启二级缓存 需要entity开启序列化 type指定自定义缓存策略 -->
<cache type="com.junsir.cache.RedisCache"/>
<!-- 关联查询时,需要保证各方缓存的数据都是最新的,采用共享缓存方案 -->
<!-- <cache-ref namespace="com.junsir.dao.PersonDao"/>-->
<!-- findAll -->
<select id="findAll" resultType="Person">
select person_id,name,gender from person
</select>
</mapper>
RedisCache.java:
此类需要实现org.apache.ibatis.cache.Cache接口,才可以在Mapper中进行指定
/**
* 本类由mybatis初始化 启动时并无法由spring容器管理(盲猜参数无法传递id),故无法注入拿到RedisTemplate
* 要拿到RedisTemplate 需要动下工厂 在工厂启动时拿到RedisTemplate
*
* 实现细节:
* 多表关联查询下的内容保持最新 - 由于DB增删改只会使Redis清空对应key的缓存 所以目的就是让有关联的缓存数据同时进同时出
*
* 实现完毕之后的优化问题:
* 1.key过长会影响redis性能,采用MD5算法对key进行处理,这里利用的是MD5加密算法的唯一性
* */
public class RedisCache implements Cache {
//自定义Cache必要条件
private final String id;
//根据断点 : id:mapper的namespace
public RedisCache(String id) {
this.id = id;
}
//返回cache的唯一标识
@Override
public String getId() {
return this.id;
}
//重点实现 加入缓存
@Override
public void putObject(Object key, Object value) {
System.out.println("key================" + key + "value ===================" + value);
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.opsForHash().put(id.toString(),keyToMd5(key.toString()),value);
}
//重点实现 查询缓存
@Override
public Object getObject(Object key) {
System.out.println("key================" + key );
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().get(id.toString(),keyToMd5(key.toString()));
}
//根据指定的key删除缓存 目前没有实现
@Override
public Object removeObject(Object o) {
return null;
}
//清空缓存 执行增删改的时候都会进行clear
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
//清空缓存
redisTemplate.delete(id.toString());
}
//计算缓存数量
@Override
public int getSize() {
RedisTemplate redisTemplate = getRedisTemplate();
//获取哈希中的键值对数量
return redisTemplate.opsForHash().size(id.toString()).intValue();
}
//统一加工redisTemplate 并返回
public RedisTemplate getRedisTemplate(){
//获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
//设置序列化策略
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
//优化: 对key做MD5处理
public String keyToMd5(String key){
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}
Test.java
在确保Redis服务正常启动后,运行以下测试程序
@SpringBootTest(classes = RedisApplication.class)
public class TestPersonService {
@Autowired
private PersonService personService;
@Test
public void test(){
//理想状态下 第一次查询 Redis中无相关数据
personService.findAll().forEach(p-> System.out.println("p==" + p));
System.out.println("===============================");
//理想状态下 第一次查询的结果已经存在于Redis 此时查询不会再经过数据库
personService.findAll().forEach(p-> System.out.println("p==" + p));
}
}