SpringBoot与Redis

一、安装Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

docker环境下的redis

1、首先去docker的镜像仓库dockerhub寻找一下,然后docker pull 一个redis下来

[root@ecs-s6-medium-2-linux-20190906163518 /]# docker pull redis
Using default tag: latest
Trying to pull repository docker.io/library/redis ... 
latest: Pulling from docker.io/library/redis
b8f262c62ec6: Pull complete 
93789b5343a5: Pull complete 
49cdbb315637: Pull complete 
ea0579387266: Pull complete 
6b8ecda334de: Pull complete 
ac5a9c26d32a: Pull complete 
Digest: sha256:cb379e1a076fcd3d3f09e10d7b47ca631fb98fb33149ab559fa02c1b11436345
Status: Downloaded newer image for docker.io/redis:latest
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker.io/redis     latest              01a52b3b5cd1        12 days ago         98.2 MB
docker.io/mysql     5.7                 383867b75fd2        3 weeks ago         373 MB
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker run -d -p 6379:6379 --name myredis docker.io/redis
1de77d13d36b7f0d08137f039450b57d295afc7359564b2eb9d6e64bb53db646
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
1de77d13d36b        docker.io/redis     "docker-entrypoint..."   12 seconds ago      Up 11 seconds       0.0.0.0:6379->6379/tcp   myredis
1eed21092c63        mysql:5.7           "docker-entrypoint..."   3 weeks ago         Up 3 weeks                                   mysql57

2、利用RedisDesktopManager来验证我们的redis是否已经成功

在这里插入图片描述
进行一个简单的测试
在这里插入图片描述
可以看到Redis已经可以开始使用了,Redis具体的命令可以参考REDIS的官方文档

二、添加Redis的starter

添加pom文件

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

添加yml配置

spring:
  redis:
    host: 119.3.223.115

全部添加完成后可以看到RedisAutoConfiguration 已经可以使用了

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

从注解开始看看:

@Configuration:表明这是一个配置类
@ConditionalOnClass(RedisOperations.class) :含有这个RedisOperations类才开始redis自动配置,RedisOperations是spring-data-redis jar包中的,在引入pring-boot-starter-data-redis时就引入了。
@EnableConfigurationProperties(RedisProperties.class):开启属性的自动注入
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }):导入这两个配置文件,这两个配置文件就是根据你导入的jar包来判断是用Lettuce还是Jedis来作为redis驱动的。

三、使用Redis

通过opsForXXX的形式。只要是redis中支持的命令基本都可以点出来。

Redis的五种基本类型
string(字符串),list(列表),set(集合),hash(哈希),zset(有序集合)
stringRedisTemplate.opsForValue();
stringRedisTemplate.opsForHash();
stringRedisTemplate.opsForList();
stringRedisTemplate.opsForSet();
stringRedisTemplate.opsForZSet();

@Test
    public void test01(){

        //gei redis 中保存数据
        stringRedisTemplate.opsForValue().append("message","hello");

    }

通常情况下我们在保存对象数据的时候。
(1)通常我们把对象转换为json去存储
(2)或者采用RedisTemplate的序列化器JdkSerializationRedisSerializer
我们可以自己写一个Serializer来实现json的转化

@Configuration
public class MyRedisConfig {

    @Bean
    @ConditionalOnMissingBean(
            name = {"queRedisTemplate"}
    )
    public RedisTemplate<Object, List<QuestionnaireDetail>> queRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, List<QuestionnaireDetail>> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<QuestionnaireDetail> ser = new Jackson2JsonRedisSerializer<QuestionnaireDetail>(QuestionnaireDetail.class);
        template.setDefaultSerializer(ser);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(
            name = {"jsonRedisTemplate"}
    )
    public RedisTemplate<Object, Object> jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> ser = new Jackson2JsonRedisSerializer<Object>(Object.class);
        template.setDefaultSerializer(ser);
        return template;
    }
}

(1)queRedisTemplate:为序列化一个单独的对象。
(2)jsonRedisTemplate:为一个泛型的序列化。
都通过@Bean来注入

@Autowired
    StringRedisTemplate stringRedisTemplate; //操作字符串

    @Autowired
    RedisTemplate<Object,Object> redisTemplate;//操作KV对象

    @Autowired
    RedisTemplate<Object, List<QuestionnaireDetail>> queRedisTemplate;//自定义独立的序列化为json的方法

    @Autowired
    RedisTemplate<Object,Object> jsonRedisTemplate;//自定义泛型的序列化为json的方法

    @Resource
    private QuestionDetailService questionDetailService;

    /**
     * Redis的五种基本类型
     * string(字符串),list(列表),set(集合),hash(哈希),zset(有序集合)
     * stringRedisTemplate.opsForValue();
     * stringRedisTemplate.opsForHash();
     * stringRedisTemplate.opsForList();
     * stringRedisTemplate.opsForSet();
     * stringRedisTemplate.opsForZSet();
     * */
    @Test
    public void test01(){

        List<QuestionnaireDetail> questionnaireDetailList=questionDetailService.getQuestionnaireDetailList((long) 1);

        //gei redis 中保存数据  ("red1",questionnaireDetailList);
        stringRedisTemplate.delete("message");
        stringRedisTemplate.opsForValue().append("message","hello");
        //测试
        queRedisTemplate.delete("okq");
        queRedisTemplate.opsForValue().set("okq",questionnaireDetailList);

        //自带的插入对象通常情况下是乱码
        redisTemplate.opsForValue().set("red1",questionnaireDetailList);

        //自定义泛型的序列化通过Jackson2JsonRedisSerializer来实现序列化为Json
        jsonRedisTemplate.opsForValue().set("redJson",questionnaireDetailList);
    }

编写一个测试类。来实现调用
在这里插入图片描述
查看一下效果,完成了数据存储的json化。

四、自定义RedisCacheManage

首先我们要知道springboot2.0和1.0相比有了很多变化。其中redis的实现从Jedis变成了Lettuce

Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程环境下使用 Jedis,需要使用连接池,
每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。
Lettuce的连接是基于Netty的,连接实例可以在多个线程间共享,
所以,一个多线程的应用可以使用同一个连接实例,而不用担心并发线程的数量。当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。通过异步的方式可以让我们更好的利用系统资源,而不用浪费线程等待网络或磁盘I/O。
只在基于Lettuce的配置中,加入了RedisLockRegistry对应bean的配置,由于在集群的模式下,基于Jedis的配置下,通过RedisLockRegistry
获取分布式锁的时候报错:
EvalSha is not supported in cluster environment
具体的解决方案就是切换至基于Lettuce的配置,请参考
https://stackoverflow.com/questions/47092475/spring-boot-redistemplate-execute-sc

当我们引入了redis的starter时。容器中帮我们匹配了RedisCacheConfiguration
在这里插入图片描述
我们重写一个缓存管理器cacheManagerNew

/**
     * 缓存管理器
     * */
    @Bean
    public CacheManager cacheManagerNew(RedisConnectionFactory redisConnectionFactory) {


        RedisSerializer<String> redisSerializerKey = new StringRedisSerializer();

        //解决查询缓存转换异常的问题
        Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        redisSerializer.setObjectMapper(objectMapper);

        //自定义一个SerializationPair使用Jackson2JsonRedisSerializer
        RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer);
        //RedisCacheConfiguration的一些配置
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .disableCachingNullValues() //禁止存入空值
                .prefixKeysWith("( ͡° ᴥ ͡°)") //给新增一个固定的前缀
                .computePrefixWith(cacheName -> "caching:" + cacheName)//给新增一个自定义的前缀
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializerKey))//设置缓存的Key序列化为string
                .serializeValuesWith(serializationPair) //设置缓存的value序列化为json
                .entryTtl(Duration.ofHours(1)); // 设置缓存有效期一小时

        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

里边有很多的配置,在此处例举一些

.entryTtl(Duration.ofDays(1)) //Duration.ZERO表示永不过期(此值一般建议必须设置)
.disableKeyPrefix() // 禁用key的前缀
.disableCachingNullValues() //禁止缓存null值
前缀我个人觉得是非常重要的,建议约定:注解缓存一个统一前缀、RedisTemplate直接操作的> 缓存一个统一前缀
.prefixKeysWith(“baidu:”) // 底层其实调用的还是computePrefixWith() 方法,只是它的前缀是固定的(默认前缀是cacheName,此方法是把它固定住,一般不建议使用固定的)
.computePrefixWith(CacheKeyPrefix.simple()); // 使用内置的实现
.computePrefixWith(cacheName -> “caching:” + cacheName) // 自己实现,建议这么使用(cacheName也保留下来了)
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
.disableCreateOnMissingCache() // 关闭动态创建Cache
.initialCacheNames() // 初始化时候就放进去的cacheNames(若关闭了动态创建,这个就是必须的)
.cacheDefaults(configuration) // 默认配置(强烈建议配置上)。 比如动态创建出来的都会走此默认配置
.withInitialCacheConfigurations() // 个性化配置 可以提供一个Map,针对每个Cache都进行个性化的配置(否则是默认配置)
.transactionAware() // 支持事务

在需要缓存的地方

@Cacheable(cacheNames = "userQ",cacheManager = "cacheManagerNew")

运行后可以看到,效果已经实现。通常情况下:
.computePrefixWith(cacheName -> “caching:” + cacheName)//给新增一个自定义的前缀
.prefixKeysWith("( ͡° ᴥ ͡°)") //给新增一个固定的前缀
都存在的时候,后面的会替代前面的设置。
在这里插入图片描述

五、回顾

@EnableCaching // 使用了CacheManager

首先别忘了开启它 否则无效

@Cacheable 触发缓存填充

 /**
     *  @Cacheable:将方法的运行结果进行保存,以后再要相同的数据,直接从缓存中获取,不用调用方法
     * CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件有自己唯一的名字
     * 属性:(缓存数据时使用key-value的形式)
     *      cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
     *      key:缓存数据使用的key;可以用她来指定。默认是使用方法参数的值 键值对:1-方法的返回值
     *          参数id的值:#id相当于#root.args[0]
     *      keyGenerator:key的生成器;可以自己指定key的生成器组件id
     *          ——key和keyGenerator不可同时使用
     *      cacheManager:指定缓存管理器(从哪个缓存管理器中取);cacheResolver:指定缓存解析器
     *      condition:指定符合条件的情况下才缓存;condition="#id>0":id大于0的时候才缓存
     *      unless:否定缓存;当unless指定的true,方法的返回值就不会被缓存;可以获取到结果进行判断
     *          unless="#result == null":结果为空时不缓存
     *      sync:缓存是否使用异步模式
     * /

@CacheEvict 触发缓存驱逐

/**
     *@CacheEvict缓存清除
     * key:指定要清楚的数据
     * allEntries=true:指定清除这个缓存中的所有数据
     * beforeInvocation = false;缓存的清除是否在方法之前执行;false:在方法执行之后清除
     */

@CachePut 更新缓存而不会干扰方法执行

 /**
     * @CachePut:既调用方法,又更新缓存数据;
     * 修改了数据库的某个数据,同时更新缓存
     * 运行过程:
     *  1、先调用目标方法
     *  2、将目标方法的结果缓存起来
     * 更新后重新查询出的数据是更新前的数据:
     *  ——(key默认使用方法参数的值)查询是的缓存是@Cacheable缓存的值,key是1;@CachePut更新后缓存的值:key是传入的employee对象
     *  ——所以要统一key:key="#employee.id";key="#result.id";
     */

@Caching 重新组合要在方法上应用的多个缓存操作

用于处理复杂的缓存情况。比如用户既要根据id缓存一份,也要根据电话缓存一份,还要根据电子邮箱缓存一份,就可以使用它

@CacheConfig 在类级别共享一些常见的缓存相关设置

可以在类级别上标注一些共用的缓存属性

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值