Redis实战之—实现mybatis的分布式缓存

介绍

我们都只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));
    }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享