redis详解_SpringBoot缓存详解并整合Redis

5d676075beafea72c48579c499e43bb9.png

一.简述

Spring从3.1开始定义了

org.springframework.cache.Cache 和

org.springframework.cache.CacheManager接口来统一不同的缓存技术

自然SpringBoot 也提供了支持

二.环境搭建

1、创建一个SpringBoot 项目,引入下面这些依赖

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-cacheartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.3version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

2、在启动类上加上@EnableCaching注解,表示开启基于注解的缓存

2b31fa45a2e906d44f789c8ea8091d73.png

3、编写配置文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_cache?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: root

#mybatis:
# configuration:
# # 打印sql日志
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 打印sql日志
logging:
  level:
    com.xx.mapper: debug

4、创建测试用的表

CREATE TABLE `tb_user` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(10) DEFAULT NULL,
  `password` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

5、编写Mapper层接口代码,这里不做解释,直接拿去用就好

package com.xx.mapper;

import com.xx.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

/**
 * @author aqi
 * DateTime: 2020/6/30 1:40 下午
 * Description: No Description
 */
@Mapper
public interface UserMapper {

    @Select("SELECT * FROM tb_user WHERE id = #{id}")
    User getUserById(int id);

    @Insert("INSERT INTO tb_user (username, password) VALUES (#{username}, #{password})")
    void addUser(User user);

    @Update("UPDATE tb_user SET username = #{username}, password = #{password} where id = #{id}")
    void modUser(User user);
}

三.缓存相关的注解

1、SpringBoot 提供的有关缓存的注解,这些注解既可以作用在方法上也可以作用在类上

b2f2f54f31c17cb2bd3f1e2f7c3da68c.png

2、几个核心注解的属性

4961266ea75f466eded22e221e2083b0.png

3、核心属性详解

50c2611e3f38ee4192acf9205f06883c.png

自定义缓存key生成器

package com.xx.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author aqi
 * DateTime: 2020/6/29 5:00 下午
 * Description: 自定义缓存key生成器
 */
@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator() {
        return new KeyGenerator(){
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                // 自定义缓存key的样式
                return method.getName() + "[" + Arrays.asList(objects).toString() + "]";
            }
        };
    }
}

SpringBoot默认提供的缓存管理器,如果要使用Redis只需要引入Redis的POM和配置文件,就会默认切换到RedisCacheManager

cd69d5ebabc480c241e4f8a2946d94d9.png

四.编写测试接口

1、测试@Cacheable注解

/**
     * 这里如果不写key,则使用id作为key,也就是id = result
     */
    @Cacheable(value = "user", key = "#id", condition = "#id == 1")
    @GetMapping("/getUser/{id}")
    public User getUser(@PathVariable Integer id) {
        System.out.println("请求的id:" + id);
        return userMapper.getUserById(id);
    }

当请求的id为1时,第一次请求去访问数据库,后续则不再访问数据库

当请求的id为2时,每一次请求都会去访问数据库

25e2bf4db18758586f50329f45f4dff3.png

2、测试@CachePut注解

/**
     * 使用返回结果对象的id值作为key值
     */
    @CachePut(value = "user", key = "#result.id")
    @GetMapping("/modUser")
    public User modUser(User user) {
        userMapper.modUser(user);
        return user;
    }

每次执行更新操作都会访问一次数据库,因为@CachePut每次执行都会访问数据库,并且修改缓存,执行更新操作之后调用查询接口不再访问数据库

780ae82ab4bf212ce1ce7a0e75a8938b.png

3、测试@CacheEvict注解

@CacheEvict(value = "user", key = "#id")
    @GetMapping("/delUser/{id}")
    public void delUser(@PathVariable int id) {
        System.out.println("删除用户缓存");
    }

执行查询操作后,执行删除操作,由于缓存中的数据被清除,所以再次执行查询操作将会访问数据库

9a4404daf9462d642897eaba125589ef.png

五.Spring Cache 总结

1、Spring Boot 缓存的结构图

e86e34207ac8ed154a2d770edd29e4bd.png

4458ccde14ba82d27497d1f05f485d94.png

2、缓存部分源码流程(这里学习了尚硅谷的SpringBoot Cache教程,这里附上链接)

①Spring Cache 的自动配置类是:CacheAutoConfiguration

e8c21e984c295f0310072f3eda5a944c.png

②定义了多个缓存组件的配置类

a6a0a5dad3204c9aaa2271a856b2c6b2.png

③系统如何选择使用哪个配置类

   1、通过类头做的判断来决定使用哪个配置类

d45a2774670b80394b0ef1a5bb8f4222.png

2、在配置文件中加上debug:

true这个配置,在控制台查看SpringBoot自动配置了哪些服务,可以看一下默认情况下,SpringBoot

到底使用了哪个缓存配置类,可以发现 SimpleCacheConfiguration匹配上了

87bb06cd88f49ac2c6521659b4892d18.png

dc698e3c878c450292794f410ff577d9.png

3、SimpleCacheConfiguration配置往容器中注入了一个ConcurrentMapCacheManager缓存管理器

6cd309a324ff116321f82265643988d3.png

4、ConcurrentMapCacheManager实现了CacheManager接口,通过名称获取到一个缓存组件,如果没有获取到就自己创建一个ConcurrentMapCache缓存组件,并将数据存储在ConcurrentMap中

1b6ca8f62366b6d4e4817a792075b72a.png

5、最后再完整的梳理一下缓存的执行流程

第一步:在方法执行之前,先进入到ConcurrentMapCacheManager中的getCache这个方法,获取到名称为user的Cache缓存组件,第一次进来的时候没有名称叫user的Cache缓存组件,这时候会走到createConcurrentMapCache这里去创建一个名叫user的Cache缓存组件

1494324e5ae36c7ef2fed2e736706328.png

第二步:去刚才创建的叫user的Cache缓存组件中,查找内容,查找的key值就是在@Cahceable中设置的key值,这里是1,由于是第一次进来所以自然是查不到数据的

ca445a05f857409ca3a1bc706907e495.png

70e4478765fd2dff815aa4f7b9e31a01.png

第三步:没有查到缓存结果,就会执行目标方法,并将结果放进缓存中

0fa6c9223947052f7dc73a00b1724f2d.png

五.整合Redis

1、Cache缓存接口,提供了8种缓存实现,只需要配置对应的缓存组件,Spring在自动装配的时候的时候就会自动匹配,并注入容器

ec2245c3e4072d15ca996e4596905d55.png

2、配置redis

①引入redis依赖

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>

②修改配置文件

aba98ddfb18b31845d1ef690a62d5f89.png

六.Redis简单介绍

1、Redis是一个高性能的key-value数据库,可以存储一下这些数据类型

String(字符串),List(列表),Set(集合),Hash(散列),ZSet(有序集合)

SpringBoot Rerdis 提供了两种模板去操作redis

@Resource
    private RedisTemplate redisTemplate;
  @Resource
    private StringRedisTemplate stringRedisTemplate;

2、一些模板方法,redis提供的命令api中都有,具体可以查看Redis官网

1525fdf57020aeca729eb65f9417f6ab.png

3、安装redis desktop manager,Redis可视化工具

4、编写测试类操作redis

@Test
    void addMsg() {
        redisTemplate.opsForValue().set("msg", "Hello");
    }

    @Test
    void appendMsg() {
        redisTemplate.opsForValue().append("msg", "Java");
    }

5、这里可以看到存进去的数据是一些奇怪的字符和乱码,这是由于我使用的是redisTemplate需要进行序列化配置,如果仅仅使用StringRedisTemplate操作字符串是不会出现这种问题的,但是操作其他数据类型则会报错

b88d471e1e148337867b33ac1cef5eda.png

七.最后再聊一聊Redis序列化

1、什么是序列化和反序列化

序列化:将对象写到IO流中

反序列化:从IO流中恢复对象

序列化的意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。

2、Redis提供了多种序列化的手段,当然也可以使用一些外部的序列化工具

e55bae3e64eb5ca41048e85cf553718d.png

3、只需要配置一下,就可以解决刚才出现的问题,但是这么多序列化的手段如何挑选呢,我比较好奇,所以我又稍微深挖了一下

package com.xx.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author aqi
 * DateTime: 2020/6/30 10:56 上午
 * Description: Redis配置
 */
@Configuration
public class MyRedisConfig {

    /**
     * redisTemplate配置
     * 序列化的几种方式:
     * OxmSerializer
     * ByteArrayRedisSerializer
     * GenericJackson2JsonRedisSerializer
     * GenericToStringSerializer
     * StringRedisSerializer
     * JdkSerializationRedisSerializer
     * Jackson2JsonRedisSerializer
     * @param redisConnectionFactory redis连接工厂
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 设置key的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化方式
        template.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        return template;
    }
}

382c5202d996d1e3d4b638f5e9540477.png

4、比较几种常见序列化手段的差异

测试代码

package com.xx;

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.xx.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.serializer.*;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
class CacheApplicationTests {

    /**
     * 测试几种序列化手段的效率
     */
    @Testvoid test() {
        User user = new User();
        user.setId(1);
        user.setUsername("张三");
        user.setPassword("123");
        List list = new ArrayList<>();for (int i = 0; i < 2000; i++) {
            list.add(user);
        }// 使用GenericJackson2JsonRedisSerializer做序列化(效率太低,不推荐使用)
        GenericJackson2JsonRedisSerializer g2 = new GenericJackson2JsonRedisSerializer();long g2s = System.currentTimeMillis();byte[] byteG2 = g2.serialize(list);long g2l = System.currentTimeMillis();
        System.out.println("GenericJackson2JsonRedisSerializer序列化消耗的时间:" + (g2l - g2s) + "ms,序列化之后的长度:" + byteG2.length);
        g2.deserialize(byteG2);
        System.out.println("GenericJackson2JsonRedisSerializer反序列化的时间:" + (System.currentTimeMillis() - g2l) + "ms");// 使用GenericToStringSerializer做序列化(和StringRedisSerializer差不多,效率没有StringRedisSerializer高,不推荐使用)
        GenericToStringSerializer g = new GenericToStringSerializer(Object.class);long gs = System.currentTimeMillis();byte[] byteG = g.serialize(list.toString());long gl = System.currentTimeMillis();
        System.out.println("GenericToStringSerializer序列化消耗的时间:" + (gl - gs) + "ms,序列化之后的长度:" + byteG.length);
        g.deserialize(byteG);
        System.out.println("GenericToStringSerializer反序列化的时间:" + (System.currentTimeMillis() - gl) + "ms");// 使用Jackson2JsonRedisSerializer做序列化(效率高,适合value值的序列化)
        Jackson2JsonRedisSerializer j2 = new Jackson2JsonRedisSerializer(Object.class);long j2s = System.currentTimeMillis();byte[] byteJ2 = j2.serialize(list);long j2l = System.currentTimeMillis();
        System.out.println("Jackson2JsonRedisSerializer序列化消耗的时间:" + (j2l - j2s) + "ms,序列化之后的长度:" + byteJ2.length);
        j2.deserialize(byteJ2);
        System.out.println("Jackson2JsonRedisSerializer反序列化的时间:" + (System.currentTimeMillis() - j2l) + "ms");// 使用JdkSerializationRedisSerializer,实体类必须实现序列化接口(不推荐使用)
        JdkSerializationRedisSerializer j = new JdkSerializationRedisSerializer();long js = System.currentTimeMillis();byte[] byteJ = j.serialize(list);long jl = System.currentTimeMillis();
        System.out.println("JdkSerializationRedisSerializer序列化消耗的时间:" + (jl - js) + "ms,序列化之后的长度:" + byteJ.length);
        j.deserialize(byteJ);
        System.out.println("JdkSerializationRedisSerializer反序列化的时间:" + (System.currentTimeMillis() - jl) + "ms");// 使用StringRedisSerializer做序列化(效率非常的高,但是比较占空间,只能对字符串序列化,适合key值的序列化)
        StringRedisSerializer s = new StringRedisSerializer();long ss = System.currentTimeMillis();byte[] byteS = s.serialize(list.toString());long sl = System.currentTimeMillis();
        System.out.println("StringRedisSerializer序列化消耗的时间:" + (sl - ss) + "ms,序列化之后的长度:" + byteS.length);
        s.deserialize(byteS);
        System.out.println("StringRedisSerializer反序列化的时间:" + (System.currentTimeMillis() - sl) + "ms");// 使用FastJson做序列化,这个表现为什么这么差我也不是很明白
        FastJsonRedisSerializer f = new FastJsonRedisSerializer<>(Object.class);long fs = System.currentTimeMillis();byte[] byteF = f.serialize(list);long fl = System.currentTimeMillis();
        System.out.println("FastJsonRedisSerializer序列化消耗的时间:" + (fl - fs) + "ms,序列化之后的长度:" + byteF.length);
        f.deserialize(byteF);
        System.out.println("FastJsonRedisSerializer反序列化的时间:" + (System.currentTimeMillis() - fl) + "ms");// 使用FastJson(效率高,序列化后占空间也很小,推荐使用)
        GenericFastJsonRedisSerializer gf = new GenericFastJsonRedisSerializer();long gfs = System.currentTimeMillis();byte[] byteGf = gf.serialize(list);long gfl = System.currentTimeMillis();
        System.out.println("GenericFastJsonRedisSerializer序列化消耗的时间:" + (gfl - gfs) + "ms,序列化之后的长度:" + byteGf.length);
        gf.deserialize(byteGf);
        System.out.println("GenericFastJsonRedisSerializer反序列化的时间:" + (System.currentTimeMillis() - gfl) + "ms");
    }
}

测试结果

39dcef5951b28b9af6218cd0e1a9714d.png

5、总结

89108ef32ad27ed2e85fdbcbbe94e9fc.png

6、附上Redis序列化配置文件

package com.xx.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author aqi
 * DateTime: 2020/6/30 10:56 上午
 * Description: Redis配置
 */
@Configuration
public class MyRedisConfig {

    /**
     * redisTemplate配置
     * @param redisConnectionFactory redis连接工厂
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 配置key的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // 使用Jackson2JsonRedisSerializer配置value的序列化方式
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        // 使用FastJson配置value的序列化方式
// template.setValueSerializer(new GenericFastJsonRedisSerializer());
        return template;
    }
}

使用Jackson2JsonRedisSerializer序列化的结果

007092bd63c4a444b1cf5b9f35537aad.png

使用FastJson序列化的结果

7e6494390666711ab5af70f76ff1f3ae.png

八.最后

才疏学浅,可能有些地方说的不准确,如果有些的不对的地方感谢各位老哥指正。

————————————————

版权声明:本文为CSDN博主「有人看我吗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:

https://blog.csdn.net/progammer10086/article/details/107040457

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值