SpringBoot1.0&2.0与Redis集成
SpringBoot与Redis集成
前奏:Redis的入门知识、SpringBoot与缓存、(转)分布式之Redis
这次就记录一下在SpringBoot应用中如何集成并使用Redis。其他介绍就不再赘述了。
RedisTemplate
当引入并使用redsi依赖之后,就不再是默认的SimpleCacheConfiguration起效了,而是RedisAutoConfiguration在起作用了,RedisAutoConfiguration中向容器中引入了RedisTemplate组件,方便操作缓存:
@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进行自动注入即可:
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
RedisTemplate和StringRedisTemplate中提供了一系列数据存取操作,例如常用的五种数据结构的操作:
- redisTemplate.opsForValue();//操作字符串
- redisTemplate.opsForHash();//操作hash
- redisTemplate.opsForList();//操作list
- redisTemplate.opsForSet();//操作set
- redisTemplate.opsForZSet();//操作有序set
@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对象(对象定义时需要实现序列化接口,否则抛运行时异常):
@Test
public void demo2(){
User user = userDao.selectUserById(133);
redisTemplate.opsForValue().set("user01",user);
}
运行之后,redis缓存中确实存入了对应的键值,但是键值都是序列化后的值,不方便查看和维护使用,我还是更习惯json格式。将数据以json的方式保存有以下方法:
-
自己手动转换json然后去缓存。直接使用一些json工具转换就行,较为简单。
-
RedisTemplate默认的序列化器,就是jdk的序列化器:
if (this.defaultSerializer == null) { this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader()); }
可以自己写一个配置类,并给组件设置我们需要的序列化器:
@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字符串存储。
整合实例:
-
第一步当然是添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
创建实体类,实体类一定要进行序列化,因为Redis保存对象的时候要求对象是序列化的:
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... }
-
接下来就和之前使用默认SimpleCache类似了:
@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的序列化器。
自定义的方法如下:
@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方法:
@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:
@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标注)。**
在很多场景下,可能需要我们再方法中手动对缓存中的数据进行操作:
@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具体如何处理等等问题,择日再起新篇。
下一篇: touch示例程序