springboot整合redis

Posted FreeFly辉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot整合redis相关的知识,希望对你有一定的参考价值。

此文介绍的是redis整合springboot作为缓存的使用

如果你未接触redis,或想独立使用原生java api操作redis,可以到菜鸟教程先熟悉一下

建议耐心读完一下内容(至少学习第三方软件流程读完)
学习第三方软件流程
  • 先要了解该第三方软件作用(例如你学mysql肯定先从概念学习)
  • 使用第三方提供的原生指令进行操作联系(例如你学mysql,肯定是先学习基本的sql语句,然后控制台(或工具)进行sql敲写练习,熟悉基本操作)
  • 寻找对应软件的java驱动包,然后利用原生javaapi进行操作(例如mysql对应的原生驱动)
  • 最后是否有第三方框架,或工具包(例如连接池)等优化工具
以上这些过程菜鸟教程都可以给你教学(当然这只是我知道的网址,肯定有很多类似教程),以上所有流程你可以在2个小时内搞定(不用所有教程指令都敲一遍)
菜鸟教程redis网址

当你做完以上工作,就可以尝试整合在springboot中使用了

引入jar包(你肯定会快速搭建启动一个springboot项目的)
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

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

		<!-- lettuce连接客户端,基于netty的单线程并发连接,同功能的还有jedis,spring官网比较看着lettuce支持的功能多些-->
		<!--lettuce和jedis就像是驱动框架,有各自的实现,spring redis像是jpa一样封装了统一api,不在意你用的哪种连接框架,spring提供的只是redis操作模板-->
		<dependency>
			<groupId>io.lettuce</groupId>
			<artifactId>lettuce-core</artifactId>
			<version>6.1.4.RELEASE</version>
		</dependency>
注入bean(以下代码全部加了注释)
@Configuration
@EnableCaching
public class RedisPoolNoProperties {
		/**
		*以下配置项我直接写在代码里方便观看,正常的配置应该写在properties或yml文件中
		*然后再引入进来,可以用 spring的@Value注解,例如
		* @Value("${spring.redis.database}")
    	* private int database;
		*/
        private int database = 0; //默认0
        private String host = "127.0.0.1";//redis启动的机器IP地址,这里在同一台电脑启动的
        private String password; //redis默认没有密码
        private int port = 6379; //redis默认端口,启动时可以看到
        private long timeout = 5000;//连接配置,可以发现无论什么网络连接都逃不出超时时间设置
        private long shutDownTimeout = 5000;//断开连接超时时间
    /**以下是连接池配置,可参考学习的mysql连接池配置,
     * 可以看出所有的连接池 都会有 最大连接数,最小连接数,核心数等设置
     * 更多配置项可自行查找文档
     * */

        private int maxIdle=50; //最大连接空闲数
        private int minIdle=5;
        private int maxActive=50;
        private long maxWait=5000;

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        @Bean //注入连接工厂bean,这里用的lettuce,同样的工具还有jedis
        public LettuceConnectionFactory lettuceConnectionFactory() {
            GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
            genericObjectPoolConfig.setMaxIdle(maxIdle);
            genericObjectPoolConfig.setMinIdle(minIdle);
            genericObjectPoolConfig.setMaxTotal(maxActive);
            genericObjectPoolConfig.setMaxWaitMillis(maxWait);
            genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(100);
            RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
            redisStandaloneConfiguration.setDatabase(database);
            redisStandaloneConfiguration.setHostName(host);
            redisStandaloneConfiguration.setPort(port);
            redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
            LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                    .commandTimeout(Duration.ofMillis(timeout))
                    .shutdownTimeout(Duration.ofMillis(shutDownTimeout))
                    .poolConfig(genericObjectPoolConfig)
                    .build();
            LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
            return factory;
        }

        @Bean//通过连接工厂bean注入 readisTemplate,都是一些配置项
        public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(lettuceConnectionFactory);
            //使用Jackson2JsonRedisSerializer替换默认的JdkSerializationRedisSerializer来序列化和反序列化redis的value值
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                    ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            jackson2JsonRedisSerializer.setObjectMapper(mapper);
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            //key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // hash的key也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // value序列化方式采用jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value序列化方式采用jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    }
注入完以上bean后,你可以注入redisTemplate使用了,redisTemplate包含了所有操作
例如:
@RestController
@RequestMapping("string")
public class TestController {

    @Autowired
    RedisTemplate redisTemplate;
	/**
     * 通过redisTemplate的ops方法可以获得对应数据类型的操作,例如
     * ListOperations listOperations = redisTemplate.opsForList(); 对应redis中list的操作
     * HashOperations hashOperations = redisTemplate.opsForHash(); 对应redis中的hash操作
     * redisTemplate.opsForValue() 对应redis中string的操作
     * 这里应该了解为什么要先单独熟悉redis的特点了,毕竟方法名都对应了操作
     */

    @GetMapping("add/{attr}/{value}")
    public void addName(@PathVariable String attr,@PathVariable String value){
        redisTemplate.opsForValue().set(attr,value);
        System.out.println(attr+" has been add to redis value :"+ value);
    }

    @GetMapping("get/{attr}")
    public String getName(@PathVariable String attr){
        String o = (String)redisTemplate.opsForValue().get(attr);
        System.out.println("you are to getting "+attr+" and value is "+o);
        return o;
    }

    @GetMapping("del/{attr}")
    public String delName(@PathVariable String attr){
        Boolean delete = redisTemplate.delete(attr);
        System.out.println("you are to remove "+attr);
        return String.valueOf(delete);
    }
}
编写完以上controller,就可以在浏览器get请求访问测试了,截图如下:

启动springboot程序,浏览器访问


控制台打印:

redis客户端通过keys * 指令查看是否加入了key

可以看到访问url,数据加入到了redis
现在可以想一下如何做个redis缓存,可以利用 spring的aop在想要缓存的方法上进行拦截,查之前先去redis中查,查到直接返回,查之后更新redis
如果方法名不是很规范固定,那就通过自定义注解,通过aop拦截自定的注解进行缓存,这样可以很好的指定哪些需要缓存

上面是我自己的思路,事实上很容易实现,spring也已经给我们提供了实现(有时候对这些实现没什么太大的好感,感觉总是提高学习成本)

spring提供了 @Cacheable注解用来缓存(同样的还有更新,删除缓存等注解,可自行搜索学习,直接搜 @Cacheable就有了)

例子:

新增一个service方法,用来充当数据库:
@Service
public class CatchService {
    public User getName(String name){
        System.out.println("来到了生产的 : "+name);
        return new User(name,20);
    }
}

接下来在之前的TestController中增加一个方法,如下:

	@Autowired
    private CatchService catchService;

	@GetMapping("catch/{name}")
    @Cacheable(value = "userCatch", key = "#name") 
    //key代表唯一标识的数据项,这里我以User中的name属性作为标识,value可自定义你想表达的标识
    public User catchName(@PathVariable String name){
        return catchService.getName(name);
    }

接下来浏览器访问(此方法我是追加在前面的TestController中的,注意url)

可以看出首次访问,进入了catchservice,并打印添加了 name 为 developer的User,并且key值为自定义 + User的name属性值
接下来第二次访问试试:


第二次访问时看出,浏览器返回了数据,但控制台未打印任何数据.
因为第一次访问已经加入了redis,第二次直接取出返回

这里注意下,redis缓存有超时时间的,所以自己实验时,俩次访问间隔短一些,接下来是redis缓存的一些配置:

 		@Bean("redisCacheManager")
        @Primary //如果有多个实现类,使用@AutoWired 注解时优先赋值有此注解的类
        public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
            RedisSerializer<String> redisSerializer = new StringRedisSerializer();

            //jackson序列化的配置,熟悉spring的应该知道redis解析httpJson默认的就是jackson
            ObjectMapper mapper = new ObjectMapper();
            mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                    ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            jackson2JsonRedisSerializer.setObjectMapper(mapper);

            RedisCacheConfiguration config1 = RedisCacheConfiguration.defaultCacheConfig()
                    .prefixCacheNameWith("purpose header") //所有缓存请求默认都会带的缓存头
                    //缓存失效时间
                    .entryTtl(Duration.ofSeconds(30))
                    //key序列化方式
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                    //value序列化方式
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                    //不允许缓存null值
                    .disableCachingNullValues();

            //设置一个初始化的缓存空间set集合
            Set<String> cacheNames = new HashSet<>();
            cacheNames.add("my-redis-cache1");

            //对每个缓存空间应用不同的配置
            Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(2);
            configurationMap.put("my-redis-cache1", config1);

            return RedisCacheManager.builder(lettuceConnectionFactory)
                    //默认缓存配置
                    .cacheDefaults(config1)
                    //初始化缓存空间
                    .initialCacheNames(cacheNames)
                    //初始化缓存配置
                    .withInitialCacheConfigurations(configurationMap).build();
        }

上面的配置可直接追加在 之前的配置 bean后面,当然随便放在哪个类,标注了@Configuration就行
上面包含了redisCatch缓存时间,序列化方式,公共头等一些配置(其实一些好多我也没搞懂啥意思,只是从网上粘贴过来的,大致知道怎么用,如果小伙伴有搜到jackson,redis缓存配置详解的学习文档,欢迎留言给我连接,我也学习以下)

另外使用了

  • // @Cacheable:触发缓存写入。
  • // @CacheEvict:触发缓存清除。
  • // @CachePut:更新缓存(不会影响到方法的运行)。
  • // @Caching:重新组合要应用于方法的多个缓存操作。
  • // @CacheConfig:设置类级别上共享的一些常见缓存设置。

注解的缓存操作,在redis连接不上时会直接抛出异常,但当redis只是作为缓存时,我们不希望缓存失效就报错,而是缓存失效走正常流程

此时只需要重写 CachingConfigurerSupport 中的异常处理方法,编写自己想要处理的逻辑(如打印日志,发送邮件通知开发着,根据反射判断哪些方法就是想抛出异常等),最后用@Bean注入即可

@Bean
    public CachingConfigurerSupport cachingConfigurerSupport(){
        return new CachingConfigurerSupport(){
            @Override
            public CacheErrorHandler errorHandler() {
                return new CacheErrorHandler(){
                    @Override
                    public void handleCacheGetError(RuntimeException e,Cache cache,Object o) {
                        System.out.println("发生了 获取 异常");
                    }

                    @Override
                    public void handleCachePutError(RuntimeException e,Cache cache,Object o,Object o1) {
                        System.out.println("发生了 添加 异常");
                    }

                    @Override
                    public void handleCacheEvictError(RuntimeException e,Cache cache,Object o) {
                        System.out.println("发生了 删除 异常");
                    }

                    @Override
                    public void handleCacheClearError(RuntimeException e,Cache cache) {
                        System.out.println("发生了 清空 异常");
                    }
                };
            }
        };
    }

以上是关于springboot整合redis的主要内容,如果未能解决你的问题,请参考以下文章

springboot整合mybatis,redis,代码

springboot整合mybatis,redis,代码

Shiro整合Springboot缓存之Redis实现

3分钟搞定springboot整合redis

springboot整合mybatis,redis,代码

完美取代hashmap,SpringBoot整合Redis缓存DB数据