redis实现分布式缓存

redis实现分布式缓存

请添加图片描述

🔥在网上看到很多利用redis实现缓存的贴子,但是大多数都是用到AOP实现的,于是就想写一篇关于mybatis二级缓存结合redis实现redis分布式缓存,mybatis二级缓存只能作用于xml中写的sql语句

请添加图片描述

🔥环境搭建

📌目录结构

在这里插入图片描述

一如既往的springboot标准结构,直接通过idea创建即可

📌pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

📌application.properties

#server
server.port=8080
#jdbc
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/redis_test?useSSL=false&useUnicode=true&characterEncoding=utf8
spring.datasource.password=123456
spring.datasource.username=root

#mybatis-plus
mybatis-plus.mapper-locations=classpath:mapper/*.xml
#控制台打印sql
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0

📌SpringContextUtil

@Component
@Lazy(false)
public class SpringContextUtil implements ApplicationContextAware {
	private static ApplicationContext applicationContext; // Spring应用上下文环境

	@Override
	public void setApplicationContext(ApplicationContext arg0) throws BeansException {
		SpringContextUtil.applicationContext = arg0;
	}

	public static ApplicationContext getApplicationContext(String redisTemplate) {
		return applicationContext;
	}

	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name) throws BeansException {
		return (T) applicationContext.getBean(name);
	}
}

📌测试类

@SpringBootTest
class RedisTestApplicationTests {

    @Autowired
    private UserServie userServie;


    @Autowired
    private DeptServie deptServie;

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void findAll() {
        userServie.findAll();
        userServie.findAll();
    }

    @Test
    void addUser(){
        User user = new User();
        user.setUName("xiaoxin");
        user.setAge(19);
        userServie.addUser(user);
    }

    @Test
    void getUserAndDeptById(){
        System.out.println(userServie.getUserAndDeptById(1));
    }

    @Test
    void findDeptAll(){
        deptServie.findDeptAll().forEach((depts)->System.out.println(depts));
        deptServie.findDeptAll().forEach((depts)->System.out.println(depts));
    }

    @Test
    void addDept(){
        Dept dept = new Dept();
        dept.setDid(1);
        dept.setDeptName("开发部");
        deptServie.addDept(dept);
    }

    @Test
    void delDeptById(){
         deptServie.delDeptById(1);
    }
}

🔥mybatis二级缓存

mybatis使用二级缓存非常的简单,只需要在需要添加缓存的xml文件加入如下标签,就能够实现对user表的缓存,但是mybatis二级缓存是与项目在一同一台机器内存中,会对项目的性能有很大影响,也不支持久化等等,不推荐这种方式

在这里插入图片描述

🔥利用mybatis二级缓存&redis实现分布式缓存

这种方式下项目和缓存没有部署在同一台机器上,而且可拓展性也更强,首先我们看一下myabtis实现缓存的Cache接口的结构,其实它就是通过PerpetualCache类去配置实现缓存数据的

在这里插入图片描述
我们通过debug,执行测试类中的findAll()方法可以看到确实进入这个PerpetualCache类,问题就变得很明了!我们只需要自己定义一个类去实现Cache接口,修改默认的缓存配置类,将覆盖PerpetualCache类中存储缓存的方式换成redis方式存储即可
在这里插入图片描述
开启mybatis二级缓存

<!-- 开启mybatis二级缓存-->
    <cache type="com.dasuan.demo.config.RedisCache"/>

实现Cache接口,重写里面的方法。目前没有进行任何配置

public class RedisCache implements Cache {
    @Override
    public String getId() {
        return null;
    }

    @Override
    public void putObject(Object o, Object o1) {

    }

    @Override
    public Object getObject(Object o) {
        return null;
    }

    @Override
    public Object removeObject(Object o) {
        return null;
    }

    @Override
    public void clear() {

    }

    @Override
    public int getSize() {
        return 0;
    }
}

完整的配置,下面我会详细的说下为什么要这样配置!!

public class RedisCache implements Cache {

    private final String id;

    //必须存在的构造方法
    public RedisCache(String id){
        System.out.println("id:============================================>"+id);
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        System.out.println("key:============================================>"+key);
        System.out.println("value:==========================================>"+value);
        //利用redis中Hash类型存储
        getRedisTemplate().opsForHash().put(this.id,key,value);
    }

    @Override
    public Object getObject(Object key) {
        System.out.println("key:============================================>"+key);
        //从redis中获取对应的value
        return getRedisTemplate().opsForHash().get(this.id,key);
    }

    @Override
    public Object removeObject(Object key) {
        System.out.println("removeObject:方法的key========================================>"+key);
        return null;
    }

    //claer()方法在更新操作的时候会执行情况缓存
    @Override
    public void clear() {
        System.out.println("进入了clear方法()=============================>");
        //从redis中删除对应的key
        getRedisTemplate().delete(this.id);
    }
    //用来计算缓存的数据流 可以自己测试一下
    @Override
    public int getSize() {
        return 0;
    }

    public RedisTemplate getRedisTemplate(){
        return SpringContextUtil.getBean("redisTemplate");
    }
}

🔥这是mybatis源码中PerpetualCache类的配置,它默认使用是mybatis中的缓存,我们可以模仿着源码进行配置,将默认缓存改成以redis缓存实现

public class PerpetualCache implements Cache {
    private final String id;
    private final Map<Object, Object> cache = new HashMap();

    public PerpetualCache(String id) {
        this.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);
    }

    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

    public void clear() {
        this.cache.clear();
    }

    public boolean equals(Object o) {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if (this == o) {
            return true;
        } else if (!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }

    public int hashCode() {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}

下面我将要介绍为什么要这样配置!!

请添加图片描述

首先直接自定义实现Cache接口,重写里面的方法。不进行任何配置,然后开启mybatis二级缓存
在这里插入图片描述

我们直接执行测试类中findAll()方法,在报错信息中我们可以看到如下一段描述,报错的大致意思是基本缓存实现必须有一个以 String id 作为参数的构造函数。这个时候我们可能不明白它这个报错到底是啥意思,不要急!我们可以看下mybatis源码中PerpetualCache
在这里插入图片描述

通过报错和对比mybatis源码中PerpetualCache类,大概可以确定我们缺少的是如下两行代码
在这里插入图片描述

自定义的Cache类加上如下代码,并设置getId方法的返回值,执行测试类中findAll方法

在这里插入图片描述

我们可以看到id打印出来的是一个类路径名,先不管,我们继续看下面的putObject(Object key, Object value),这个方法非常重要,通过方法名咱们已经可以看出这是一个放入缓存数据库的方法
在这里插入图片描述
自定义的Cache类加上如下代码,先来看一下key和value到底是个什么?执行测试类中findAll()方法
在这里插入图片描述
key是一段各种信息的组合,可以作为唯一标识,value是一个对象的集合
在这里插入图片描述
这个时候我们一共获得了三个值 id , key , value 并且哪怕数据库数据发生了改变,只有value会跟着变化,其余两个都不会。这时候我马上可以想到redis中Hash类型,hash结构为一个大key ,value又是一个键值对,根据一个大key,和键值对里面的key,唯一确定一个value

在这里插入图片描述

通过redis中Hash类型存储缓存数据,执行测试类中findAll()方法
在这里插入图片描述
可以看到redis中已经存入了这条数据,但是每一次查询仍然是从数据库获取的数据,这是为什么呢?别急!我们继续看下一个重要的方法getObject(Object key)

在这里插入图片描述
在这里插入图片描述

和上面一样继续getObject(Object key)打印key,先来看一下key到底是个什么?执行测试类中findAll()方法

在这里插入图片描述
从以下截图中,我们可以看出两个信息,一个是每次getObject(Object key),是在putObject(Object key, Object value)之前执行的,也就是在我们在执行查询方法的时候,会先查询缓存中是否有数据,如果没有数据就将查询到的数据存入缓存中,另外一个信息是这个两个方法中的key是一模一样的,并且细心的朋友会发现id在每一次执行方法的时候都会打印全类路径名
在这里插入图片描述

通过一个大key,和键值对里面的key,唯一确定一个value

在这里插入图片描述

执行测试类中findAll()方法,为了方便查看效果,我清除了redis中的数据

在这里插入图片描述

执行测试类中findAll()方法,查询到的数据又存储到了redis中

在这里插入图片描述
如何更新缓存数据库呢?很简单!只要进行的更新操作,会进入下面的clear()方法执行清除缓存方法 注意:并不会进入removeObject(),这是一个无效的方法
在这里插入图片描述

执行测试类中delDeptById()方法,可以看到进入了clear()方法

在这里插入图片描述

注意redis中数据库变化,可以看到删除一条记录后,DeptDao中缓存全部被清除了,添加和修改同样也是这样,这里就不做演示了
在这里插入图片描述
在这里插入图片描述

至此为止,通过查询数据,如果缓存里面没有数据,就会从数据库中查询数据,然后放到缓存,通过更新数据库可以清空相应的Dao中的缓存,缓存看起来基本所有都配置好了,但实际还存在一个问题,看下来的例子

执行getUserAndDeptById()方法,这是一个关联查询,查询的是用户信息及其对应的部门信息,可以看到上一步操作已经将1号部门给删了,数据库中也不存在1号部门信息了,而现在依然可以通过缓存查询到了已经删除掉的部门信息

在这里插入图片描述

在这里插入图片描述

为什么出现这样的问题呢,因为每个Dao中的缓存是没有任何关联的getUserAndDeptById()的缓存数据是存储到UserDao中的,虽然数据库中已经删掉部门信息,但是缓存中仍然存在存储在UserDao中getUserAndDeptById()方法的缓存数据

在这里插入图片描述

上述现象肯定是不行的,要么不删,要么把相关联的缓存数据都得删掉才合理,这是需要使用到如下标签来关联缓存

这个标签可以在任意一个Dao中添加,如在DeptDao中添加,关联缓存标签中namesoace值为与其关联的UserDao全类路径名,反之也是如此,关联后DeptDao和UserDao中的所有缓存数据都存储在UserDao中,全部由UserDao管理

在这里插入图片描述

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值