长大后想做什么?做回小孩!

0%

SpringBoot与Redis集成

这次就记录一下在SpringBoot应用中如何集成并使用Redis。其他介绍就不再赘述了。

RedisTemplate

当引入并使用redsi依赖之后,就不再是默认的SimpleCacheConfiguration起效了,而是RedisAutoConfiguration在起作用了,RedisAutoConfiguration中向容器中引入了RedisTemplate组件,方便操作缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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;
}

RedisTemplate和StringRedisTemplate就像是之前学习的JDBCTemplate一个意思,可以使用这两个redisTemplate组件对Redis进行操作。因为String类型的操作比较多,所以将StringRedisTemplate单独抽取出来作为一个类,这个类就是继承了RedisTemplate<String,String>。

使用这两个组件时让Spring进行自动注入即可:

1
2
3
4
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;

RedisTemplate和StringRedisTemplate中提供了一系列数据存取操作,例如常用的五种数据结构的操作:

  • redisTemplate.opsForValue();//操作字符串
  • redisTemplate.opsForHash();//操作hash
  • redisTemplate.opsForList();//操作list
  • redisTemplate.opsForSet();//操作set
  • redisTemplate.opsForZSet();//操作有序set
1
2
3
4
5
6
7
8
@Test
public void demo(){
stringRedisTemplate.opsForValue().append("msg","world");
Object msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg.toString());
stringRedisTemplate.opsForList().leftPush("mylist","1");
stringRedisTemplate.opsForList().leftPush("mylist","2");
}

使用StringRedisTemplate存储string类型比较简单,一般不会出现什么问题。redis自身提供的所有数据操作命令,在coding时基本上都可以可以“.”出来。

需要注意的是保存java对象(对象定义时需要实现序列化接口,否则抛运行时异常):

1
2
3
4
5
@Test
public void demo2(){
User user = userDao.selectUserById(133);
redisTemplate.opsForValue().set("user01",user);
}

运行之后,redis缓存中确实存入了对应的键值,但是键值都是序列化后的值,不方便查看和维护使用,我还是更习惯json格式。将数据以json的方式保存有以下方法:

  1. 自己手动转换json然后去缓存。直接使用一些json工具转换就行,较为简单。

  2. RedisTemplate默认的序列化器,就是jdk的序列化器:

    1
    2
    3
    if (this.defaultSerializer == null) {
    this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
    }

    可以自己写一个配置类,并给组件设置我们需要的序列化器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration
    public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    RedisTemplate<Object, User> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    Jackson2JsonRedisSerializer serializer=new Jackson2JsonRedisSerializer<User>(User.class);
    template.setDefaultSerializer(serializer);
    return template;
    }
    }

    完成上述的工作,就可以在之前的测试类中自动注解RedisTemplate<Object, User>用来完成json字符串存储。

整合实例:

  1. 第一步当然是添加依赖:

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 创建实体类,实体类一定要进行序列化,因为Redis保存对象的时候要求对象是序列化的:

    1
    2
    3
    4
    5
    6
    7
    8
    public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String username;
    private String password;
    private String address;
    setter/getter/tostring...
    }
  3. 接下来就和之前使用默认SimpleCache类似了:

    1
    2
    3
    4
    5
    @Override
    @Cacheable(value = "user",key = "#id")
    public User selectUserById(Integer id) {
    return userDao.selectUserById(id);
    }

    连续发起请求,从控制台看到第一次查询数据库,第二次没有查询数据库,说明缓存起效了,redis缓存中也存入了数据,但是key是user::133,value是序列化后的值,这是为什么呢?

    答:用默认的SimpleCache时,配置类会自动给容器注册一个CacheManager[ConcurrentMapCacheManager],这个CacheManager创建对应的Cahce组件进行实际的CRUD操作。

    现在使用Redis也不例外,RedisCacheConfiguration也会自动注册一个默认的CacheManager。RedisCacheManager帮我们创建RedisCahce来作为缓存组件,RedisCacheManager中设置了操作缓存数据的各种规则,这就是问题的原因。

    那么如果要将缓存数据序列化为JSON格式后保存,就需要自定义CacheManager

    需要注意:springboot1.0和2.0创建RedisCacheManager 的方法是不同的:

    • springboot 1.0

      1.0默认创建的RedisCacheManager操作redis时,是使用RedisTemplate<object,object>。而RedisTemplate<object,object>默认使用的序列化机制是JDK的序列化器。

      自定义的方法如下:

      1
      2
      3
      4
      5
      6
      7
      @Bean
      public RedisCacheManager userCacheManager(RedisTemplate<Object, User> redisTemplate){
      //传入自定义的RedisTemplate,给这个自定义的RedisTemplate设置一个需要的json序列化器
      RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
      //true使用cacheName作为key的前缀,再将指定的key拼接上
      redisCacheManager.setUsePrefix(true);
      }

      SpringBoot 1.0默认的redisCacheManager方法最后会调用CacheManagerCustomizers对象,这个对象可以定制缓存的一些规则。(不常用)

    • springboot 2.0中默认的cacheManager方法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Bean
      public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
      RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(resourceLoader.getClassLoader()));
      List<String> cacheNames = this.cacheProperties.getCacheNames();
      if (!cacheNames.isEmpty()) {
      builder.initialCacheNames(new LinkedHashSet(cacheNames));
      }

      return (RedisCacheManager)this.customizerInvoker.customize(builder.build());
      }

      查看RedisCacheManager源码+参考百度,摸索着自定义了一个CacheManager:

      1
      2
      3
      4
      5
      6
      7
      8
      @Bean
      public RedisCacheManager userRedisCacheManager(RedisConnectionFactory redisConnectionFactory) {
      RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
      //.entryTtl(Duration.ofDays(1)) //设置缓存过期时间为一天
      //.disableCachingNullValues() //禁用缓存空值,不缓存null校验
      .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // 设置CacheManager的值序列化方式为json序列化,可加入@Class属性
      return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build(); // 设置默认的cache组件
      }

      经过我的测试,都可以正确的将查询到数据正确的转换成json格式并缓存。


SpringBoot 1.0整合注意事项

在上文中SpringBoot 1.0中自定义CacheManager,已经可以实现需求。但是当有多个表和实体时(除了例子中的User、又添加一个Dept表和对应实体),直接对新添加的表进行查询并使用注解缓存,则会抛出运行时异常。

因为在userCacheManager中指定了对User进行json格式序列化,所以如果需要对新添加的表进行查询时需要添加新的自定义缓存管理器(deptCacheManager),并给UserServiceImpl和DeptServiceImpl中的缓存注解的CacheManager属性设置各自所用的自定义缓存器。而且,需要注意的是如果存在两个及两个以上的自定义缓存管理器时,需要用@Primary注解来标注一个默认使用的缓存管理器(一般是将源码中默认的缓存管理器复制到自定义配置类中用@Primary标注)。


在很多场景下,可能需要我们再方法中手动对缓存中的数据进行操作:

1
2
3
4
5
6
7
8
9
10
11
12
	@Autowired
@Qualifier("userRedisCacheManager")//用id指定注入的bean
private RedisCacheManager userRedisCacheManager;

public User selectUserById(Integer id){
User user = userDao.selectUserById(id);
// 获取某个缓存组件
Cache userCache = userRedisCacheManager.getCache("user");
// 使用缓存组件对缓存数据操作
userCache.put(id,user);
return user;
}

至于整合redis时踩的坑、尚未解决的细节和数据库查询结果为空时缓存和controller具体如何处理等等问题,择日再起新篇。