介绍
我们都只mybatis具有二级缓存,何为二级缓存,这里我们简单的介绍一下,在mapper文件中添加打开二级缓存,就是mybatis会把每一次查询结果都存在一个map对象中,如果下次还执行重复的查询的话,那么就直接从缓存中拿,避免了与数据库的交互。
问题:在分布式项目中,不同的服务器使用各自的JVM启动项目,各自有单独的mybatis二级缓存,导致缓存的使用效果不好,这里我们可以选用一款中间件来替换原先缓存存放在map中,这里我们使用redis。
项目配置
server.port=8090
#redis 单机链接 配置完成后SpringData会自动创建redisTemplate和StringRedisTemplate
spring.redis.host=127.0.0.1
spring.redis.port=8379
spring.redis.password=qwer1234
#默认使用0号库
spring.redis.database=0
##Redis的Sentinel配置
##master的名字 查看sentinel.conf里面的配置 使用哨兵监听的那个名称
#spring.redis.sentinel.master=mymaster
##连的不再是一个具体的redis主机,书写的是多个哨兵节点
#spring.redis.sentinel.nodes=127.0.0.1:26379
#redis cluster 操作 书写集群中所有的节点
#spring.redis.cluster.nodes=192.168.2.2:7000,192.168.2.2:8888,192.168.2.2:3564
#配置数据库
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/examine?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.type-aliases-package=com.exit.springbootredis.entity
logging.level.com.exit.springbootredis.dao=debug
复制代码
redis的连接方案根据自己的redis部署架构来选取
自定义mybatis二级缓存实现类
package com.exit.springbootredis.cache;
import com.exit.springbootredis.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.DigestUtils;
/**
* @author Y2M
* @createTime 2021/6/30
* @comment 自定义mybatis二级缓存
*/
public class redisCache implements Cache {
/**
* 就是当前放入缓存的mapper的namespace
*/
private final String id;
public redisCache(String id) {
System.out.println("id:"+id);
this.id = id;
}
/**
*
* @return 返回cache的唯一标识
*/
@Override
public String getId() {
return id;
}
/**
* 用来计算缓存数量
* @return
*/
@Override
public int getSize() {
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().size(id.toString()).intValue();
}
@Override
public void putObject(Object key, Object value) {
System.out.println("******开始往缓存放对象*******");
System.out.println("key:"+key);
System.out.println("value:"+value);
System.out.println("******结束往缓存放对象*******");
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.opsForHash().put(id.toString(),getStringMd5(key.toString()),value);
}
@Override
public Object getObject(Object key) {
System.out.println("******开始从缓存那对象*******");
System.out.println("key:"+key);
System.out.println("******结束从缓存那对象*******");
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().get(id.toString(),getStringMd5(key.toString()));
}
/**
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().delete(id.toString(),getStringMd5(key.toString()));
}
/**
* 只要发生增删改就会调用这个方法
*/
@Override
public void clear() {
System.out.println("清空缓存");
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(id.toString());
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
/**
* 优化key的长度
* @param source
* @return 32位的16进制字符串
*/
public String getStringMd5(String source){
return DigestUtils.md5DigestAsHex(source.getBytes());
}
}
复制代码
package com.exit.springbootredis.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* @author Y2M
* @createTime 2021/6/30
* @comment 用来获取SpringBoot创建好的工厂
*/
@Configuration
public class ApplicationContextUtils implements ApplicationContextAware {
/**
* 保留下来工厂
*/
private static ApplicationContext applicationContext;
/**
* 将创建好的工厂已参数的形式传递给这个类
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextUtils.applicationContext = applicationContext;
}
/**
* 提供在工厂中获取对象的方法
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
}
复制代码
我们一起来看看上面那两个类是用来干什么的,先说说ApplicationContextUtils这个类吧,这个类是用来获取SpringBoot创建好的工厂ApplicationContext,为什么要获取ApplicationContext呢?用于redisCache不在Spring的IOC容器中,因为我们要自定义一个类实现ApplicationContextAware接口,在setApplicationContext方法中获取,继而从ApplicationContext中拿到RedisTemplate来操作redis。
redisCache 这个类可以查看注释,我们来说一下为什么要有getStringMd5这个方法,key是由redis来生成的,特别长,具体可以自己查看使用md5加密用来减少长度,当然使用md5也是因为md5对不同的对象加密结果是不一样的,且长度都是32位的16进制字符串。
开启mybatis的二级缓存
<?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.exit.springbootredis.dao.MzroomDao">
<!--开启mybatis的二级缓存 不指定type的话 默认使用的是PerpetualCache这个 这里我们选择自定义实现的缓存-->
<!--<cache type="com.exit.springbootredis.cache.redisCache"/>-->
<!--关联关系的缓存处理 用来将多个具有关联关系查询查询缓存放到一个去处理-->
<cache-ref namespace="com.exit.springbootredis.dao.VoteRecordDao"/>
<select id="findAll" resultType="com.exit.springbootredis.entity.Mzroom">
select *
from mzroom
</select>
<insert id="insert" parameterType="com.exit.springbootredis.entity.Mzroom">
insert into mzroom (id,room_id,`name`,phone,relation) value (null,#{room_id},#{name},#{phone},#{relation})
</insert>
</mapper>
复制代码
我们可以看到<cache-ref/>
和<cache/>
两个标签,这两个标签都是开启二级缓存的,只不过前者可以用来处理关联关系的缓存,而后者不能,什么是关联关系呢?简单的来说两个Dao对操作的表有重复,ADao.class可以修改学校的学院信息,而BDao.class可以修改学院的部门信息。如果使用<cache-ref/>
标签,ADao.class发送增、改、删的操作不光是清空自己的二级缓存也会清空BDao.class的缓存。使用<cache/>
标签,只清空自己的缓存。
测试类
package com.exit.springbootredis;
import com.exit.springbootredis.entity.Mzroom;
import com.exit.springbootredis.service.MzroomService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* @author Y2M
* @createTime 2021/6/30
* @comment
*/
@SpringBootTest(classes = SpringbootRedisApplication.class)
@RunWith(SpringRunner.class)
public class TestMzroomService {
@Autowired
private MzroomService mzroomService;
/**
* 没有开启缓存 注意看控制台的查询语句
*/
@Test
public void findAllNoCache(){
List<Mzroom> all = mzroomService.findAll();
all.forEach(mzroom -> System.out.println(mzroom));
System.out.println("=================");
//此时没有开启缓存 无论查询多少次 都会从数据库中查询 数据
List<Mzroom> allNew = mzroomService.findAll();
allNew.forEach(mzroom -> System.out.println(mzroom));
}
/**
* 开启缓存 mapper文件中添加 <cache/>
* 注意看控制台的查询语句 他就不会去数据库查询 而是从缓存中拿 这里的缓存是指Jvm的缓存 程序重启后就没了
* 当然对象要实现序列化 否则会报错
*/
@Test
public void findAllJvmCache(){
List<Mzroom> all = mzroomService.findAll();
all.forEach(mzroom -> System.out.println(mzroom));
System.out.println("=================");
//此时没有开启缓存 无论查询多少次 都会从数据库中查询 数据
List<Mzroom> allNew = mzroomService.findAll();
allNew.forEach(mzroom -> System.out.println(mzroom));
}
/**
* 开启缓存 mapper文件中添加 <cache type="com.exit.springbootredis.cache.redisCache"/>>
* 注意看控制台的查询语句 他就不会去数据库查询 而是从缓存中拿 这里的缓存是指Redis的缓存 程序重启后还是会有的
* 当然对象要实现序列化 否则会报错
*
* 问题:当数据发生变化如何更新缓存中的数据呢? 解决方法:重写RedisCache的clear方法,当然你手动该数据库的数据无效
*/
@Test
public void findAllRedisCache(){
List<Mzroom> all = mzroomService.findAll();
all.forEach(mzroom -> System.out.println(mzroom));
System.out.println("=================");
Mzroom addMzroom = new Mzroom();
addMzroom.setName("rose").setPhone("753159").setRelation("村花").setRoom_id("8888");
mzroomService.insert(addMzroom);
List<Mzroom> allNew = mzroomService.findAll();
allNew.forEach(mzroom -> System.out.println(mzroom));
}
}
复制代码