• -------------------------------------------------------------
  • ====================================

Springboot 2.X中Spring-cache与redis整合

技能 dewbay 5年前 (2019-04-12) 2796次浏览 已收录 0个评论 扫描二维码

一、本次案例
        Springboot 中Spring-cacheredis 整合。这也是一个不错的框架,与 spring 的事务使用类似,只要添加一些注解方法,就可以动态的去操作缓存了,减少代码的操作。如果这些注解不满足项目的需求,我们也可以参考 spring-cache 的实现思想,使用 AOP 代理+缓存操作来管理缓存的使用。 

        在这个例子中我使用的是 redis,当然,因为 spring-cache 的存在,我们可以整合多样的缓存技术,例如 Ecache、Mamercache 等。 

二、创建 springBoot 项目  king-cache

在 pom.xml 中添加以下依赖
org.springframework.boot spring-boot-starter-cache
org.springframework.boot spring-boot-starter-data-redis

三、redis 客户端配置
无论使用 spring-boot 的哪个版本,我们都需要先配置 redis 连接,两个版本的 redis 客户端连接池使用有所不同。

spring-boot 版本 默认客户端类型
1.5.x jedis
2.x lettuce
在 1.5.x 中,我们配置 jedis 连接池只需要配置 spring.redis.pool.* 开始的配置即可,如下配置

spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.min-idle=0
spring.redis.pool.max-idle=8
但在 2.x 版本中由于引入了不同的客户端,需要指定配置哪种连接池,如下配置

在 application.properties 中添加如下代码:

jedis 客户端

server.port=8080
spring.cache.type=redis
spring.redis.host=192.168.140.130 # server host
spring.redis.password= 123456
spring.redis.port= 6379
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=8

lettuce 客户端

spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.shutdown-timeout=100ms
除此之外还可以看到时间配置上还需要带上对应的单位。

四、JavaConfig 方式配置
通用配置方式只能满足整个程序所有缓存都采用相同公共配置的方式,如果需要特殊处理,如我们的案列,则需要自己采用代码的方式来配置。
采用代码的方式,只要需要配置的是 CacheMananger,采用 Redis 时具体实现我们需要使用其子类 RedisCacheMananger 来做配置

创建 redis 的配置类 RedisConfig.java
注意一定要在类上加上以下两个注解:
    @Configuration  可理解为用 spring 的时候 xml 里面的标签
    @EnableCaching 注解是 spring framework 中的注解驱动的缓存管理功能。
package com.answer.config;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

/**

  • redis 缓存配置
  • Created by zhezhiyong@163.com on 2017/9/21.
    */
    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport { @Bean
    public KeyGenerator KeyGenerator() {
    return new KeyGenerator() {
    @Override
    public Object generate(Object target, Method method, Object… params) {
    StringBuilder sb = new StringBuilder();
    sb.append(target.getClass().getName());
    sb.append(method.getName());
    for (Object obj : params) {
    sb.append(obj.toString());
    }
    return sb.toString();
    }
    };
    } /* @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
    RedisCacheManager manager = new RedisCacheManager(redisTemplate);
    manager.setUsePrefix(true);
    RedisCachePrefix cachePrefix = new RedisPrefix(“prefix”);
    manager.setCachePrefix(cachePrefix);
    // 整体缓存过期时间
    manager.setDefaultExpiration(3600L);
    // 设置缓存过期时间。key 和缓存过期时间,单位秒
    Map expiresMap = new HashMap<>();
    expiresMap.put(“user”, 1000L);
    manager.setExpires(expiresMap);
    return manager;
    }*/ @Bean
    CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    //初始化一个 RedisCacheWriter
    RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
    //设置 CacheManager 的值序列化方式为 JdkSerializationRedisSerializer,但其实 RedisCacheConfiguration 默认就是使用 StringRedisSerializer 序列化 key,JdkSerializationRedisSerializer 序列化 value,所以以下注释代码为默认实现
    //ClassLoader loader = this.getClass().getClassLoader();
    //JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(loader);
    //RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(jdkSerializer);
    //RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
    RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
    //设置默认超过期时间是 30 秒
    defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
    //初始化 RedisCacheManager
    RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    return cacheManager;
    }
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
    StringRedisTemplate template = new StringRedisTemplate(factory);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();
    return template;
    }
    }
    以上代码中 RedisCacheConfiguration 类为 2.x 新增的配置类,增加了几个配置项。这里比较奇怪的是调用它的配置方法每一次都会重新生成一个配置对象,而不是在原对象上直接修改参数值,这一点本人没搞懂作者为何要选择这种方式。 到这里基本的配置就已经完成了 五、具体使用
    如果我们想在其他项目中添加该微服务想进行缓存的操作,需依赖 king-cache 模块: com.answer king-cache 0.0.1-SNAPSHOT 六、测试 Demo——新建项目:king-cache-1 添加以下依赖: com.answer king-cache 0.0.1-SNAPSHOT com.google.guava guava 19.0 org.projectlombok lombok compile
    依上图所示分别创建以下实例: Info.java package com.answer.entity; import java.io.Serializable; import lombok.AllArgsConstructor;
    import lombok.Data; /**
    • Created by zhezhiyong@163.com on 2017/9/22. / @Data @AllArgsConstructor public class Info implements Serializable{ /*
      • @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
        */
        private static final long serialVersionUID = 5457507957150215600L;
        public Info(String string, String string2) {
        // TODO Auto-generated constructor stub
        }
        public String getPhone() {
        return phone;
        }
        public void setPhone(String phone) {
        this.phone = phone;
        }
        public String getAddress() {
        return address;
        }
        public void setAddress(String address) {
        this.address = address;
        }
        private String phone;
        private String address;
    }
    User.java package com.answer.entity; import java.io.Serializable; import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor; /**
    • Created by zhezhiyong@163.com on 2017/9/21.
      */
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class User implements Serializable{ /**
      • @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
        */
        private static final long serialVersionUID = 1L;
        public Long getId() {
        return id;
        }
        public void setId(Long id) {
        this.id = id;
        }
        public String getName() {
        return name;
        }
        public void setName(String name) {
        this.name = name;
        }
        public String getPassword() {
        return password;
        }
        public void setPassword(String password) {
        this.password = password;
        }
        private Long id;
        private String name;
        private String password;
    }
    IndexController.java package com.answer.web; import java.util.List;
    import java.util.Map; import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController; import com.answer.entity.User;
    import com.answer.service.UserService;
    import com.google.common.collect.ImmutableMap; /**
    • Created by zhezhiyong@163.com on 2017/9/21.
      */
      @RestController
      public class IndexController { @Autowired
      private UserService userService; @GetMapping(“/users”)
      @ResponseBody
      public List users() {
      return userService.list();
      } @GetMapping(“/user/{id}”)
      @ResponseBody
      public User findUserById(@PathVariable(“id”) Long id) {
      return userService.findUserById(id);
      } @GetMapping(“/upuser/{id}”)
      @ResponseBody
      public User upuser(@PathVariable(“id”) Long id) {
      return userService.upuser(id);
      } @GetMapping(“/info/{id}”)
      @ResponseBody
      public User findInfoById(@PathVariable(“id”) Long id) {
      return userService.findInfoById(id);
      } @GetMapping(“/user/{id}/{name}”)
      @ResponseBody
      public Map update(@PathVariable(“id”) Long id, @PathVariable(“name”) String name) {
      User user = userService.findUserById(id);
      user.setName(name);
      userService.update(user);
      return ImmutableMap.of(“ret”, 0, “msg”, “ok”);
      }
    }
    UserService.java package com.answer.service; import java.util.List; import com.answer.entity.User; /**
    • Created by zhezhiyong@163.com on 2017/9/21.
      */
      public interface UserService { List list(); User findUserById(Long id); User findInfoById(Long id); void update(User user); void remove(Long id); User upuser(Long id);
    }
    UserServiceImpl.java package com.answer.service.impl;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map; import org.springframework.cache.annotation.CacheEvict;
    import org.springframework.cache.annotation.CachePut;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Service; import com.answer.entity.Info;
    import com.answer.entity.User;
    import com.answer.service.UserService; import lombok.extern.slf4j.Slf4j; /**
    • Created by zhezhiyong@163.com on 2017/9/21.
      */
      @Service
      @Slf4j
      public class UserServiceImpl implements UserService { private Map userMap = new HashMap<>();
      private Map infoMap = new HashMap<>(); public UserServiceImpl() {
      User u1=new User();
      u1.setId(1L);
      u1.setName(“1111”);
      u1.setPassword(“11223434”);
      User u2=new User();
      u2.setId(1L);
      u2.setName(“1111”);
      u2.setPassword(“11223434”);
      User u3=new User();
      u3.setId(1L);
      u3.setName(“1111”);
      u3.setPassword(“11223434”); userMap.put(1L,u1); userMap.put(2L,u2); userMap.put(3L,u3); infoMap.put(1L, new Info("18559198715", "福州市")); } @Override
      public List list() {
      return Arrays.asList(userMap.values().toArray());
      } @Override
      @Cacheable(value = “user”, key = “‘user’.concat(#id.toString())”)
      public User findUserById(Long id) {
      //log.info(“findUserById query from db, id: {}”, id);
      System.out.println(“findUserById query from db, id: {}======”+id);
      return userMap.get(id);
      } @Override
      @Cacheable(value = “info”, key = “‘info’.concat(#id.toString())”)
      public User findInfoById(Long id) {
      // log.info(“findInfoById query from db, id: {}”, id);
      System.out.println(“findUserById query from db, id: {}======”+id);
      return userMap.get(id);
      } @Override
      @CachePut(value = “user”, key = “‘user’.concat(#user.id.toString())”)
      public void update(User user) {
      //log.info(“update db, user: {}”, user.toString());
      System.out.println(“findUserById query from db, id: {}======”);
      userMap.put(user.getId(), user);
      } @Override
      @CacheEvict(value = “user”, key = “‘user’.concat(#id.toString())”)
      public void remove(Long id) {
      //log.info(“remove from db, id: {}”, id);
      System.out.println(“findUserById query from db, id: {}======”);
      userMap.remove(id);
      } @Override
      @CacheEvict(value = “user”, key = “‘user’.concat(#id.toString())”)
      public User upuser(Long id) {
      User d= userMap.get(id);
      d.setName(“000000000000000000000000000000000000000000000000”);
      return d;
      }
      }
      注意:启动项目前一定要对 king-cache 进行打包(右键项目:Run As —Maven Install)
    然后:king-cache-1 右键 maven —Update Project 最后:以 Debug 模式启动 项目:king-cache-1  在以下方法出添加断点: 然后多次访问:http://localhost:8080/user/3 你会发现第一次进入断点,后面几次不再进入断点. 一定要添加上这个注解:@Cacheable 项目源码下载地址: https://download.csdn.net/download/guokezhongdeyuzhou/10322780 注意以下注解的作用: 缓存的注解介绍
    @Cacheable 触发缓存入口 @CacheEvict 触发移除缓存 @CacahePut 更新缓存 @Caching 将多种缓存操作分组 @CacheConfig 类级别的缓存注解,允许共享缓存名称 @CacheConfig
    该注解是可以将缓存分类,它是类级别的注解方式。我们可以这么使用它。 
    这样的话,UseCacheRedisService 的所有缓存注解例如@Cacheable 的 value 值就都为 user。 @CacheConfig(cacheNames = “user”)
    @Service
    public class UseCacheRedisService {}
    在 redis 的缓存中显示如下 127.0.0.1:6379> keys *
    1) “user~keys”
    2) “user_1”
    127.0.0.1:6379> get user~keys
    (error) WRONGTYPE Operation against a key holding the wrong kind of value
    127.0.0.1:6379> type user~keys
    zset
    127.0.0.1:6379> zrange user~keys 0 10
    1) “user_1”
    我们注意到,生成的 user~keys,它是一个 zset 类型的 key,如果使用 get 会报 WRONGTYPE Operation against a key holding the wrong kind of value。这个问题坑了我很久 @Cacheable
    一般用于查询操作,根据 key 查询缓存. 如果 key 不存在,查询 db,并将结果更新到缓存中。
    如果 key 存在,直接查询缓存中的数据。  查询的例子,当第一查询的时候,redis 中不存在 key,会从 db 中查询数据,并将返回的结果插入到 redis 中。 @Cacheable
    public List selectAllUser(){
    return userMapper.selectAll();
    }
    调用方式。 @Test
    public void selectTest(){
    System.out.println(“===========第一次调用=======”);
    List list = useCacheRedisService.selectAllUser();
    System.out.println(“===========第二次调用=======”);
    List list2 = useCacheRedisService.selectAllUser();
    for (User u : list2){
    System.out.println(“u = ” + u);
    }
    }
    打印结果,大家也可以试一试 
    只输出一次 sql 查询的语句,说明第二次查询是从 redis 中查到的。 ===========第一次调用=======
    keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
    keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
    DEBUG [main] – ==> Preparing: SELECT id,name,sex,age,password,account FROM user
    DEBUG [main] – ==> Parameters:
    DEBUG [main] – <== Total: 1
    ===========第二次调用=======
    keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
    u = User{id=1, name=’fsdfds’, sex=’fdsfg’, age=24, password=’gfdsg’, account=’gfds’} redis 中的结果 
    我们可以看到 redis 中已经存在 
    com.lzl.redisService.UseCacheRedisService.selectAllUser.记录了。 
    这么长的一串字符 key 是根据自定义 key 值生成的。 127.0.0.1:6379> keys *
    1) “user~keys”
    2) “com.lzl.redisService.UseCacheRedisService.selectAllUser.”
    3) “user_1”
    127.0.0.1:6379> get com.lzl.redisService.UseCacheRedisService.selectAllUser.
    “[\”java.util.ArrayList\”,[[\”com.lzl.bean.User\”,{\”id\”:1,\”name\”:\”fsdfds\”,\”sex\”:\”fdsfg\”,\”age\”:24,\”password\”:\”gfdsg\”,\”account\”:\”gfds\”}]]]”
    @CachePut
    一般用于更新和插入操作,每次都会请求 db 
    通过 key 去 redis 中进行操作。 
    1. 如果 key 存在,更新内容 
    2. 如果 key 不存在,插入内容。 /**
      • 单个 user 对象的插入操作,使用 user+id
      • @param user
      • @return
        */
        @CachePut(key = “\”user_\” + #user.id”)
        public User saveUser(User user){
        userMapper.insert(user);
        return user;
        }
        redis 中的结果 
        多了一条记录 user_2
    127.0.0.1:6379> keys *
    1) “user~keys”
    2) “user_2”
    3) “com.lzl.redisService.UseCacheRedisService.selectAllUser.”
    4) “user_1”
    127.0.0.1:6379> get user_2
    “[\”com.lzl.bean.User\”,{\”id\”:2,\”name\”:\”fsdfds\”,\”sex\”:\”fdsfg\”,\”age\”:24,\”password\”:\”gfdsg\”,\”account\”:\”gfds\”}]”
    @CacheEvict
    根据 key 删除缓存中的数据。allEntries=true 表示删除缓存中的所有数据。 @CacheEvict(key = “\”user_\” + #id”)
    public void deleteById(Integer id){
    userMapper.deleteByPrimaryKey(id);
    }
    测试方法 @Test
    public void deleteTest(){
    useCacheRedisService.deleteById(1);
    }
    redis 中的结果 
    user_1 已经移除掉。 127.0.0.1:6379> keys *
    1) “user~keys”
    2) “user_2”
    3) “com.lzl.redisService.UseCacheRedisService.selectAllUser.”
    测试 allEntries=true 时的情形。 @Test
    public void deleteAllTest(){
    useCacheRedisService.deleteAll();
    }
    @CacheEvict(allEntries = true)
    public void deleteAll(){
    userMapper.deleteAll();
    }
    redis 中的结果 
    redis 中的数据已经全部清空 127.0.0.1:6379> keys *
    (empty list or set)
    @Caching
    通过注解的属性值可以看出来,这个注解将其他注解方式融合在一起了,我们可以根据需求来自定义注解,并将前面三个注解应用在一起 public @interface Caching {
    Cacheable[] cacheable() default {}; CachePut[] put() default {}; CacheEvict[] evict() default {}; }
    使用例子如下 @Caching(
    put = {
    @CachePut(value = “user”, key = “\”user_\” + #user.id”),
    @CachePut(value = “user”, key = “#user.name”),
    @CachePut(value = “user”, key = “#user.account”)
    }
    )
    public User saveUserByCaching(User user){
    userMapper.insert(user);
    return user;
    } @Test
    public void saveUserByCachingTest(){
    User user = new User();
    user.setName(“dkjd”);
    user.setAccount(“dsjkf”);
    useCacheRedisService.saveUserByCaching(user);
    }
    redis 中的执行结果 
    一次添加三个 key 127.0.0.1:6379> keys *
    1) “user~keys”
    2) “dsjkf”
    3) “dkjd”
    4) “user_3”
    Spring Boot2.X 自定义 Redis 的 cacheManager,保存 Json 格式到 Redis
    1231 在自义定 cacheManager 方面,SpringBoot2.X 与SpringBoot1.X 有很大不同,SpringBoot2 用的是 按照它的样子,你也可以自定义一个 cacheManager,@Co… 来自: caojidasabi 作者:果壳中 de 宇宙
    来源:CSDN
    原文:https://blog.csdn.net/guokezhongdeyuzhou/article/details/79789629
    版权声明:本文为博主原创文章,转载请附上博文链接!

露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Springboot 2.X中Spring-cache与redis整合
喜欢 (1)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址