什么是分布式缓存?
在原始的应用中,通常将缓存存储在本地应用程序的 JVM 中,但是随着项目微服务化的趋势,在项目以微服务的方式重构后,缓存应是由单独的服务器进行管理和存储,分布在不同的物理区域进行部署,已达到高可用效果。
Mybatis原始的缓存存储方式
在未使用分布式缓存时,Mybatis 是自带缓存的 mybatis将每次查询到到的结果存放在一个类的 HashMap 属性中,MyBatis 的缓存处理接口为 org.apache.ibatis.cache Cache
操作的缓存的实现类为 org.apache.ibatis.cache.impl PerpetualCache
Mybatis 将查询到的数据缓存到 次实现类的 cashe
属性中。
以下为部分源码
public class PerpetualCache implements Cache {
//缓存id
private final String id;
//最终存储缓存处
private final Map<Object, Object> cache = new HashMap();
public PerpetualCache(String id) {
this.id = id;
}
//查询缓存id
public String getId() {
return this.id;
}
//获取缓存大小
public int getSize() {
return this.cache.size();
}
//将数据加入缓存
public void putObject(Object key, Object value) {
this.cache.put(key, value);
}
//获取缓存
public Object getObject(Object key) {
return this.cache.get(key);
}
//此方法mybatis并未实现调用者 属于保留方法
public Object removeObject(Object key) {
return this.cache.remove(key);
}
//清除缓存
public void clear() {
this.cache.clear();
}
//......未全部展示源码
步骤:
当请求到来时访问应用中的dao层mybatis解析映射文件中是否添加了<cache/>
缓存标签表示开启缓存。在默认缓存实现类 PerpetualCache
中使用 getObject(Object key)
方法尝试获取缓存。如果有将返回缓存中的内容,否则将查询数据库,将得到的结果存入缓存并返回给用户。
分布式缓存的实现原理
此次示例的环境是 SpringBoot + Mybatis + Redis 作为分布式缓存的基本环境。使用Mybatis作为缓存的:存储,获取,删除的操作者,使用 Redis 作为缓存的存储者。
步骤:
当请求到来时访问应用中的dao层mybatis解析映射文件中是否添加了<cache/>
缓存标签表示开启缓存。
若开启类缓存将使用 我们指定的实现类 去redis中获取缓存 如果有将返回缓存 若没有将从数据库查询,再将数据存入redis中并返回给用户。
我们的目的是替换掉mybatis 中的缓存默认处理类,让其使用我们提供的实现类来操作缓存 在Redis中。
实现示例
依赖项:
省略Springboot依赖
数据库三件套
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
使用此依赖操作Redis 数据库
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
步骤
注意将实体类实现序列化接口
- 创建普通的 SSM 项目
- 配置应用。
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 907282050
url: jdbc:mysql://127.0.0.1:3306/db_test?serverTimezone=Asia/Shanghai&useSSL=false
type: com.alibaba.druid.pool.DruidDataSource
# 配置redis连接信息
redis:
host: localhost
port: 6379
database: 0
mybatis:
mapper-locations: classpath:mappers/*.xml
type-handlers-package: com.ccnn.test_01.com.ccnn.test_01.entity
logging:
level:
com.ccnn.test_01.dao: DEBUG
- 获取Springboot 容器中注入好的操作redis的
RedisTemplate
对象
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static Object getRedisTemplate(String name){
return applicationContext.getBean(name);
}
}
- 自定义类实现
org.apache.ibatis.cache.Cache
接口和其中方法
import com.ccnn.test_01.jedis.ApplicationContextUtil;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
public class RedisCache implements Cache {
/* id 的值为此类的全限定类名(com.ccnn.test_01.dao.UserMapper)和mapper.xml映射文件中的namespace
标签中的值相同*/
private final String id;
/* 将 RedisTemplate作为属性 声明方便使用*/
private final RedisTemplate<Object,Object> redisTemplate;
public RedisCache(String id) {
this.id = id;
/* 获取RedisTemplate对象并设置 序列化规则 */
redisTemplate = (RedisTemplate<Object,Object> ) ApplicationContextUtil.getRedisTemplate("redisTemplate");
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setKeySerializer(new StringRedisSerializer());
}
@Override
public String getId() {
return this.id;
}
/* 从缓存中获取数据
如果未在缓存中获取到数据 则会从数据库中查询,
将查询到的数据使用此类的 putObject();向缓存中
存储(在mybatis使用的默认缓存实现类:org.apache.
ibatis.cache.impl.PerpetualCache中 使用了一个HashMap 对象
进行存储)
因此我们在此处将数据从 Redis 中取出数据作为缓存返回。
*/
@Override
public Object getObject(Object o) {
/*此处 参数 o 和 putObject(Object o, Object o1) 中的 o为同一参数*/
System.out.println("获取数据——————————"+o)
return redisTemplate.opsForHash().get(id,o.toString());
}
/* 向缓存中存储数据
在缓存中未查询到结果时 将从数据库中查询,将查询到的结果
存入到缓存中,因此 在此处将数据存储到 Redis 中。
*/
@Override
public void putObject(Object o, Object o1) {
//以下为参数的输出结果
/*2057322197:1276118677:com.ccnn.test_01.dao.UserMapper.
selectUser:0:2147483647:select * from user where name
= ?:小李:SqlSessionFactoryBean---[User{name='小李', age=15}]*/
System.out.println(o.toString()+"---"+o1.toString());
redisTemplate.opsForHash().put(id,o.toString(),o1);
}
/*mybatis 并未实现这个发的调用 无调用者*/
@Override
public Object removeObject(Object o) {
return null;
}
//移除缓存中的数据
@Override
public void clear() {
redisTemplate.delete(id);
}
@Override
public int getSize() {
return redisTemplate.opsForHash().size(id).intValue();
}
//加密方法
private static String getKeyToM5(String k){
return DigestUtils.md5DigestAsHex(k.getBytes());
}
}
在 更新 删除 添加 时都会根据 id 属性的值将redis数据库中的值删除,在下次查询时则将值从mysql中添加到redis中。
- 为Mapper.xml 映射文件添加
<cache type="自定义实现类"/>
<?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.ccnn.test_01.dao.UserMapper">
<!--指定缓存处理类-->
<cache type="com.ccnn.test_01.cache.RedisCache"/>
<select id="selectUser" resultType="com.ccnn.test_01.entity.User" parameterType="String">
select * from user where name = #{name}
</select>
<update id="updateUser" parameterType="String">
update user set name = #{name} where age = 15
</update>
</mapper>
- 适应多表联查。
在多表联查时 请将两个有关联关系的mapper.xml文件中的一方去共享 主要
的一方的缓存 cache 标签修改为:
<?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.ccnn.test_01.dao.EmpMapper">
<cache-ref namespace="被共享的mapper.xml文件的 命名空间的值"/>
<select >
.......
</select>
</mapper>
使双方对一块 redis 中的缓存进行操作,在任意一方对数据进行修改时都会清空此项缓存。
优化
将存入 redis 中的缓存数据进行优化。
在看到打印的缓存数据的 key 时我们发现 key过于长 因此使用 spring 内涵的 MD5 加密工具类(两个内容相同的文件加密后得到的值必定相同)进行加密 生成32位16进制的 字符串 ,作为 key 存入到Redis中。在获取缓存时再进行解密。
上文已添加。