Spring Boot使用redis做数据缓存 1 添加redis支持 2 redis配置 3 redis服务器配置 4 应用 5 检验 1. 依赖包安装 2. Spring 项目集成进缓存支持 3. 缓存某些方法的执行结果 4. 缓存数据一致性保证 5. 自定义缓存数据 key 生成策略 6. 缓存的验证 7. 注意事项 后记 参考资料

Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料

在pom.xml中添加

Xml代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. <dependency>  
  2.           <groupId>org.springframework.boot</groupId>  
  3.           <artifactId>spring-boot-starter-redis</artifactId>  
  4.       </dependency>  

2 redis配置

Java代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. package com.wisely.ij.config;  
  2.   
  3. import com.fasterxml.jackson.annotation.JsonAutoDetect;  
  4. import com.fasterxml.jackson.annotation.PropertyAccessor;  
  5. import com.fasterxml.jackson.databind.ObjectMapper;  
  6. import org.springframework.cache.CacheManager;  
  7. import org.springframework.cache.annotation.CachingConfigurerSupport;  
  8. import org.springframework.cache.annotation.EnableCaching;  
  9. import org.springframework.cache.interceptor.KeyGenerator;  
  10. import org.springframework.context.annotation.Bean;  
  11. import org.springframework.context.annotation.Configuration;  
  12. import org.springframework.data.redis.cache.RedisCacheManager;  
  13. import org.springframework.data.redis.connection.RedisConnectionFactory;  
  14. import org.springframework.data.redis.core.RedisTemplate;  
  15. import org.springframework.data.redis.core.StringRedisTemplate;  
  16. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;  
  17.   
  18. import java.lang.reflect.Method;  
  19.   
  20.   
  21. @Configuration  
  22. @EnableCaching  
  23. public class RedisConfig extends CachingConfigurerSupport{  
  24.   
  25.     @Bean  
  26.     public KeyGenerator wiselyKeyGenerator(){  
  27.         return new KeyGenerator() {  
  28.             @Override  
  29.             public Object generate(Object target, Method method, Object... params) {  
  30.                 StringBuilder sb = new StringBuilder();  
  31.                 sb.append(target.getClass().getName());  
  32.                 sb.append(method.getName());  
  33.                 for (Object obj : params) {  
  34.                     sb.append(obj.toString());  
  35.                 }  
  36.                 return sb.toString();  
  37.             }  
  38.         };  
  39.   
  40.     }  
  41.   
  42.     @Bean  
  43.     public CacheManager cacheManager(  
  44.             @SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {  
  45.         return new RedisCacheManager(redisTemplate);  
  46.     }  
  47.   
  48.     @Bean  
  49.     public RedisTemplate<String, String> redisTemplate(  
  50.             RedisConnectionFactory factory) {  
  51.         StringRedisTemplate template = new StringRedisTemplate(factory);  
  52.         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);  
  53.         ObjectMapper om = new ObjectMapper();  
  54.         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);  
  55.         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  
  56.         jackson2JsonRedisSerializer.setObjectMapper(om);  
  57.         template.setValueSerializer(jackson2JsonRedisSerializer);  
  58.         template.afterPropertiesSet();  
  59.         return template;  
  60.     }  
  61. }  

3 redis服务器配置

Properties代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. # REDIS (RedisProperties)  
  2. spring.redis.database= # database name  
  3. spring.redis.host=localhost # server host  
  4. spring.redis.password= # server password  
  5. spring.redis.port=6379 # connection port  
  6. spring.redis.pool.max-idle=8 # pool settings ...  
  7. spring.redis.pool.min-idle=0  
  8. spring.redis.pool.max-active=8  
  9. spring.redis.pool.max-wait=-1  
  10. spring.redis.sentinel.master= # name of Redis server  
  11. spring.redis.sentinel.nodes= # comma-separated list of host:port pairs  

4 应用

测试两个实体类

Java代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. package com.wisely.ij.domain;  
  2.   
  3.   
  4. public class Address {  
  5.     private Long id;  
  6.     private String province;  
  7.     private String city;  
  8.   
  9.     public Address(Long id,String province, String city) {  
  10.         this.id = id;  
  11.         this.province = province;  
  12.         this.city = city;  
  13.     }  
  14.   
  15.     public Address() {  
  16.     }  
  17.   
  18.     public Long getId() {  
  19.         return id;  
  20.     }  
  21.   
  22.     public void setId(Long id) {  
  23.         this.id = id;  
  24.     }  
  25.   
  26.     public String getProvince() {  
  27.         return province;  
  28.     }  
  29.   
  30.     public void setProvince(String province) {  
  31.         this.province = province;  
  32.     }  
  33.   
  34.     public String getCity() {  
  35.         return city;  
  36.     }  
  37.   
  38.     public void setCity(String city) {  
  39.         this.city = city;  
  40.     }  
  41. }  
Java代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. package com.wisely.ij.domain;  
  2.   
  3.   
  4. public class User {  
  5.     private Long id;  
  6.     private String firstName;  
  7.     private String lastName;  
  8.   
  9.     public User(Long id,String firstName, String lastName) {  
  10.         this.id = id ;  
  11.         this.firstName = firstName;  
  12.         this.lastName = lastName;  
  13.     }  
  14.   
  15.     public User() {  
  16.     }  
  17.   
  18.     public Long getId() {  
  19.         return id;  
  20.     }  
  21.   
  22.     public void setId(Long id) {  
  23.         this.id = id;  
  24.     }  
  25.   
  26.     public String getFirstName() {  
  27.         return firstName;  
  28.     }  
  29.   
  30.     public void setFirstName(String firstName) {  
  31.         this.firstName = firstName;  
  32.     }  
  33.   
  34.     public String getLastName() {  
  35.         return lastName;  
  36.     }  
  37.   
  38.     public void setLastName(String lastName) {  
  39.         this.lastName = lastName;  
  40.     }  
  41. }  

 使用演示

Java代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. package com.wisely.ij.service;  
  2.   
  3. import com.wisely.ij.domain.Address;  
  4. import com.wisely.ij.domain.User;  
  5. import org.springframework.cache.annotation.Cacheable;  
  6. import org.springframework.stereotype.Service;  
  7.   
  8. /** 
  9.  * Created by wisely on 2015/5/25. 
  10.  */  
  11. @Service  
  12. public class DemoService {  
  13.     @Cacheable(value = "usercache",keyGenerator = "wiselyKeyGenerator")  
  14.     public User findUser(Long id,String firstName,String lastName){  
  15.         System.out.println("无缓存的时候调用这里");  
  16.         return new User(id,firstName,lastName);  
  17.     }  
  18.     @Cacheable(value = "addresscache",keyGenerator = "wiselyKeyGenerator")  
  19.     public Address findAddress(Long id,String province,String city){  
  20.         System.out.println("无缓存的时候调用这里");  
  21.         return new Address(id,province,city);  
  22.     }  
  23. }  
Java代码  Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
  1. package com.wisely.ij.web;  
  2.   
  3. import com.wisely.ij.domain.Address;  
  4. import com.wisely.ij.domain.User;  
  5. import com.wisely.ij.service.DemoService;  
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Controller;  
  8. import org.springframework.web.bind.annotation.RequestMapping;  
  9. import org.springframework.web.bind.annotation.ResponseBody;  
  10.   
  11. /** 
  12.  * Created by wisely on 2015/5/25. 
  13.  */  
  14.   
  15. @Controller  
  16. public class DemoController {  
  17.   
  18.     @Autowired  
  19.     DemoService demoService;  
  20.   
  21.     @RequestMapping("/test")  
  22.     @ResponseBody  
  23.     public String putCache(){  
  24.         demoService.findUser(1l,"wang","yunfei");  
  25.         demoService.findAddress(1l,"anhui","hefei");  
  26.         System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功");  
  27.         return "ok";  
  28.     }  
  29.     @RequestMapping("/test2")  
  30.     @ResponseBody  
  31.     public String testCache(){  
  32.         User user = demoService.findUser(1l,"wang","yunfei");  
  33.         Address address =demoService.findAddress(1l,"anhui","hefei");  
  34.         System.out.println("我这里没执行查询");  
  35.         System.out.println("user:"+"/"+user.getFirstName()+"/"+user.getLastName());  
  36.         System.out.println("address:"+"/"+address.getProvince()+"/"+address.getCity());  
  37.         return "ok";  
  38.     }  
  39. }  

5 检验

先访问http://localhost:8080/test 保存缓存


Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
 

再访问http://localhost:8080/test2 调用缓存里的数据


Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
 

 http://wiselyman.iteye.com/blog/2184884

整合 spring 4(包括mvc、context、orm) + mybatis 3 示例》一文简要介绍了最新版本的 Spring MVC、IOC、MyBatis ORM 三者的整合以及声明式事务处理。现在我们需要把缓存也整合进来,缓存我们选用的是 Redis,本文将在该文示例基础上介绍 Redis 缓存 + Spring 的集成。关于 Redis 服务器的搭建请参考博客《Redhat5.8 环境下编译安装 Redis 并将其注册为系统服务》。

1. 依赖包安装

pom.xml 加入:

[html] view plain copy
 
 print?
  1. <!-- redis cache related.....start -->  
  2. <dependency>  
  3.     <groupId>org.springframework.data</groupId>  
  4.     <artifactId>spring-data-redis</artifactId>  
  5.     <version>1.6.0.RELEASE</version>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>redis.clients</groupId>  
  9.     <artifactId>jedis</artifactId>  
  10.     <version>2.7.3</version>  
  11. </dependency>  
  12. <!-- redis cache related.....end -->  

2. Spring 项目集成进缓存支持

要启用缓存支持,我们需要创建一个新的 CacheManager bean。CacheManager 接口有很多实现,本文演示的是和 Redis 的集成,自然就是用 RedisCacheManager 了。Redis 不是应用的共享内存,它只是一个内存服务器,就像 MySql 似的,我们需要将应用连接到它并使用某种“语言”进行交互,因此我们还需要一个连接工厂以及一个 Spring 和 Redis 对话要用的 RedisTemplate,这些都是 Redis 缓存所必需的配置,把它们都放在自定义的 CachingConfigurerSupport 中:

[java] view plain copy
 
 print?
  1. /** 
  2.  * File Name:RedisCacheConfig.java 
  3.  * 
  4.  * Copyright Defonds Corporation 2015  
  5.  * All Rights Reserved 
  6.  * 
  7.  */  
  8. package com.defonds.bdp.cache.redis;  
  9.   
  10. import org.springframework.cache.CacheManager;  
  11. import org.springframework.cache.annotation.CachingConfigurerSupport;  
  12. import org.springframework.cache.annotation.EnableCaching;  
  13. import org.springframework.context.annotation.Bean;  
  14. import org.springframework.context.annotation.Configuration;  
  15. import org.springframework.data.redis.cache.RedisCacheManager;  
  16. import org.springframework.data.redis.connection.RedisConnectionFactory;  
  17. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;  
  18. import org.springframework.data.redis.core.RedisTemplate;  
  19.   
  20. /** 
  21.  *  
  22.  * Project Name:bdp  
  23.  * Type Name:RedisCacheConfig  
  24.  * Type Description: 
  25.  *  Author:Defonds 
  26.  * Create Date:2015-09-21 
  27.  *  
  28.  * @version 
  29.  *  
  30.  */  
  31. @Configuration  
  32. @EnableCaching  
  33. public class RedisCacheConfig extends CachingConfigurerSupport {  
  34.   
  35.     @Bean  
  36.     public JedisConnectionFactory redisConnectionFactory() {  
  37.         JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();  
  38.   
  39.         // Defaults  
  40.         redisConnectionFactory.setHostName("192.168.1.166");  
  41.         redisConnectionFactory.setPort(6379);  
  42.         return redisConnectionFactory;  
  43.     }  
  44.   
  45.     @Bean  
  46.     public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {  
  47.         RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();  
  48.         redisTemplate.setConnectionFactory(cf);  
  49.         return redisTemplate;  
  50.     }  
  51.   
  52.     @Bean  
  53.     public CacheManager cacheManager(RedisTemplate redisTemplate) {  
  54.         RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);  
  55.   
  56.         // Number of seconds before expiration. Defaults to unlimited (0)  
  57.         cacheManager.setDefaultExpiration(3000); // Sets the default expire time (in seconds)  
  58.         return cacheManager;  
  59.     }  
  60.       
  61. }  


当然也别忘了把这些 bean 注入 Spring,不然配置无效。在 applicationContext.xml 中加入以下:

[html] view plain copy
 
 print?
  1. <context:component-scan base-package="com.defonds.bdp.cache.redis" />  

3. 缓存某些方法的执行结果

设置好缓存配置之后我们就可以使用 @Cacheable 注解来缓存方法执行的结果了,比如根据省份名检索城市的 provinceCities 方法和根据 city_code 检索城市的 searchCity 方法:

[java] view plain copy
 
 print?
  1. // R  
  2. @Cacheable("provinceCities")  
  3. public List<City> provinceCities(String province) {  
  4.     logger.debug("province=" + province);  
  5.     return this.cityMapper.provinceCities(province);  
  6. }  
  7.   
  8. // R  
  9. @Cacheable("searchCity")  
  10. public City searchCity(String city_code){  
  11.     logger.debug("city_code=" + city_code);  
  12.     return this.cityMapper.searchCity(city_code);     
  13. }  

4. 缓存数据一致性保证

CRUD (Create 创建,Retrieve 读取,Update 更新,Delete 删除) 操作中,除了 R 具备幂等性,其他三个发生的时候都可能会造成缓存结果和数据库不一致。为了保证缓存数据的一致性,在进行 CUD 操作的时候我们需要对可能影响到的缓存进行更新或者清除。

[java] view plain copy
 
 print?
  1. // C  
  2. @CacheEvict(value = { "provinceCities"}, allEntries = true)  
  3. public void insertCity(String city_code, String city_jb,   
  4.         String province_code, String city_name,  
  5.         String city, String province) {  
  6.     City cityBean = new City();  
  7.     cityBean.setCityCode(city_code);  
  8.     cityBean.setCityJb(city_jb);  
  9.     cityBean.setProvinceCode(province_code);  
  10.     cityBean.setCityName(city_name);  
  11.     cityBean.setCity(city);  
  12.     cityBean.setProvince(province);  
  13.     this.cityMapper.insertCity(cityBean);  
  14. }  
  15. // U  
  16. @CacheEvict(value = { "provinceCities", "searchCity" }, allEntries = true)  
  17. public int renameCity(String city_code, String city_name) {  
  18.     City city = new City();  
  19.     city.setCityCode(city_code);  
  20.     city.setCityName(city_name);  
  21.     this.cityMapper.renameCity(city);  
  22.     return 1;  
  23. }  
  24.   
  25. // D  
  26. @CacheEvict(value = { "provinceCities", "searchCity" }, allEntries = true)  
  27. public int deleteCity(String city_code) {  
  28.     this.cityMapper.deleteCity(city_code);  
  29.     return 1;  
  30. }  


业务考虑,本示例用的都是 @CacheEvict 清除缓存。如果你的 CUD 能够返回 City 实例,也可以使用 @CachePut 更新缓存策略。笔者推荐能用 @CachePut 的地方就不要用 @CacheEvict,因为后者将所有相关方法的缓存都清理掉,比如上面三个方法中的任意一个被调用了的话,provinceCities 方法的所有缓存将被清除。

5. 自定义缓存数据 key 生成策略

对于使用 @Cacheable 注解的方法,每个缓存的 key 生成策略默认使用的是参数名+参数值,比如以下方法:

[java] view plain copy
 
 print?
  1. @Cacheable("users")  
  2. public User findByUsername(String username)  


这个方法的缓存将保存于 key 为 users~keys 的缓存下,对于 username 取值为 "赵德芳" 的缓存,key 为 "username-赵德芳"。一般情况下没啥问题,二般情况如方法 key 取值相等然后参数名也一样的时候就出问题了,如:

[java] view plain copy
 
 print?
  1. @Cacheable("users")  
  2. public Integer getLoginCountByUsername(String username)  


这个方法的缓存也将保存于 key 为 users~keys 的缓存下。对于 username 取值为 "赵德芳" 的缓存,key 也为 "username-赵德芳",将另外一个方法的缓存覆盖掉。
解决办法是使用自定义缓存策略,对于同一业务(同一业务逻辑处理的方法,哪怕是集群/分布式系统),生成的 key 始终一致,对于不同业务则不一致:

[java] view plain copy
 
 print?
  1. @Bean  
  2. public KeyGenerator customKeyGenerator() {  
  3.     return new KeyGenerator() {  
  4.         @Override  
  5.         public Object generate(Object o, Method method, Object... objects) {  
  6.             StringBuilder sb = new StringBuilder();  
  7.             sb.append(o.getClass().getName());  
  8.             sb.append(method.getName());  
  9.             for (Object obj : objects) {  
  10.                 sb.append(obj.toString());  
  11.             }  
  12.             return sb.toString();  
  13.         }  
  14.     };  
  15. }  


于是上述两个方法,对于 username 取值为 "赵德芳" 的缓存,虽然都还是存放在 key 为 users~keys 的缓存下,但由于 key 分别为 "类名-findByUsername-username-赵德芳" 和 "类名-getLoginCountByUsername-username-赵德芳",所以也不会有问题。
这对于集群系统、分布式系统之间共享缓存很重要,真正实现了分布式缓存。
笔者建议:缓存方法的 @Cacheable 最好使用方法名,避免不同的方法的 @Cacheable 值一致,然后再配以以上缓存策略。

6. 缓存的验证

6.1 缓存的验证

为了确定每个缓存方法到底有没有走缓存,我们打开了 MyBatis 的 SQL 日志输出,并且为了演示清楚,我们还清空了测试用 Redis 数据库。
先来验证 provinceCities 方法缓存,Eclipse 启动 tomcat 加载项目完毕,使用 JMeter 调用 /bdp/city/province/cities.json 接口:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
Eclipse 控制台输出如下:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
说明这一次请求没有命中缓存,走的是 db 查询。JMeter 再次请求,Eclipse 控制台输出:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
标红部分以下是这一次请求的 log,没有访问 db 的 log,缓存命中。查看本次请求的 Redis 存储情况:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
同样可以验证 city_code 为 1492 的 searchCity 方法的缓存是否有效:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
图中标红部分是 searchCity 的缓存存储情况。

6.2 缓存一致性的验证

先来验证 insertCity 方法的缓存配置,JMeter 调用 /bdp/city/create.json 接口:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
之后看 Redis 存储:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
可以看出 provinceCities 方法的缓存已被清理掉,insertCity 方法的缓存奏效。
然后验证 renameCity 方法的缓存配置,JMeter 调用 /bdp/city/rename.json 接口:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
之后再看 Redis 存储:
Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料
searchCity 方法的缓存也已被清理,renameCity 方法的缓存也奏效。

7. 注意事项

  1. 要缓存的 Java 对象必须实现 Serializable 接口,因为 Spring 会将对象先序列化再存入 Redis,比如本文中的 com.defonds.bdp.city.bean.City 类,如果不实现 Serializable 的话将会遇到类似这种错误:nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.defonds.bdp.city.bean.City]]。
  2. 缓存的生命周期我们可以配置,然后托管 Spring CacheManager,不要试图通过 redis-cli 命令行去管理缓存。比如 provinceCities 方法的缓存,某个省份的查询结果会被以 key-value 的形式存放在 Redis,key 就是我们刚才自定义生成的 key,value 是序列化后的对象,这个 key 会被放在 key 名为 provinceCities~keys key-value 存储中,参考下图"provinceCities 方法在 Redis 中的缓存情况"。可以通过 redis-cli 使用 del 命令将 provinceCities~keys 删除,但每个省份的缓存却不会被清除。
  3. CacheManager 必须设置缓存过期时间,否则缓存对象将永不过期,这样做的原因如上,避免一些野数据“永久保存”。此外,设置缓存过期时间也有助于资源利用最大化,因为缓存里保留的永远是热点数据。
  4. 缓存适用于读多写少的场合,查询时缓存命中率很低、写操作很频繁等场景不适宜用缓存。

Spring Boot使用redis做数据缓存
1 添加redis支持
2 redis配置
3 redis服务器配置
4 应用
5 检验
1. 依赖包安装
2. Spring 项目集成进缓存支持
3. 缓存某些方法的执行结果
4. 缓存数据一致性保证
5. 自定义缓存数据 key 生成策略
6. 缓存的验证
7. 注意事项
后记
参考资料

后记

本文完整 Eclipse 下的开发项目示例已上传 CSDN 资源,有兴趣的朋友可以去下载下来参考:http://download.csdn.net/detail/defonds/9137505

参考资料

http://blog.csdn.net/defonds/article/details/48716161

本文介绍了如何使用注解的方式,将Redis缓存整合到你的Spring项目。

首先我们将使用jedis驱动,进而开始配置我们的Gradle。

group 'com.gkatzioura.spring'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE")
    }
}
jar {
    baseName = 'gs-serving-web-content'
    version =  '0.1.0'
}
sourceCompatibility = 1.8
repositories {
    mavenCentral()
}
dependencies {
    compile "org.springframework.boot:spring-boot-starter-thymeleaf"
    compile 'org.slf4j:slf4j-api:1.6.6'
    compile 'ch.qos.logback:logback-classic:1.0.13'
    compile 'redis.clients:jedis:2.7.0'
    compile 'org.springframework.data:spring-data-redis:1.5.0.RELEASE'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}
task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

紧接着我们将使用Spring注解,继续执行Redis装载配置。

package com.gkatzioura.spring.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setUsePool(true);
        return jedisConnectionFactory;
    }
    @Bean
    public RedisSerializer redisStringSerializer() {
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        return stringRedisSerializer;
    }
    @Bean(name="redisTemplate")
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf,RedisSerializer redisSerializer) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(cf);
        redisTemplate.setDefaultSerializer(redisSerializer);
        return redisTemplate;
    }
    @Bean
    public CacheManager cacheManager() {
        return new RedisCacheManager(redisTemplate(redisConnectionFactory(),redisStringSerializer()));
    }
}

下一步将创建缓存接口CacheService。

package com.gkatzioura.spring.cache;
import java.util.Date;
import java.util.List;
public interface CacheService {
    public void addMessage(String user,String message);
    public List<String> listMessages(String user);
}

当然用户既可以增加一条消息也能取回一条消息。因此,在实现过程中,用户相关信息的存在时间将默认设为一分钟。

我们用Redis来继承实现CacheService接口。

package com.gkatzioura.spring.cache.impl;
import com.gkatzioura.spring.cache.CacheService;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
@Service("cacheService")
public class RedisService implements CacheService {
    @Resource(name = "redisTemplate")
    private ListOperations<String, String> messageList;
    @Resource(name = "redisTemplate")
    private RedisOperations<String,String> latestMessageExpiration;
    @Override
    public void addMessage(String user,String message) {
        messageList.leftPush(user,message);
        ZonedDateTime zonedDateTime = ZonedDateTime.now();
        Date date = Date.from(zonedDateTime.plus(1, ChronoUnit.MINUTES).toInstant());
        latestMessageExpiration.expireAt(user,date);
    }
    @Override
    public List<String> listMessages(String user) {
        return messageList.range(user,0,-1);
    }
}

我们的缓存机制将保留每个用户发送的消息列表。为了实现这个功能我们将调用ListOperations接口,同时将每个user作为一个key键值。通过RedisOperations接口,我们可以为key设置特定存在时长。在本例中,主要使用的是 user key。

下一步我们将创建一个controller注入缓存服务。

package com.gkatzioura.spring.controller;
import com.gkatzioura.spring.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class MessageController {
    @Autowired
    private CacheService cacheService;
    @RequestMapping(value = "/message",method = RequestMethod.GET)
    @ResponseBody
    public List<String> greeting(String user) {
        List<String> messages = cacheService.listMessages(user);
        return messages;
    }
    @RequestMapping(value = "/message",method = RequestMethod.POST)
    @ResponseBody
    public String saveGreeting(String user,String message) {
        cacheService.addMessage(user,message);
        return "OK";
    }
}

最后完成类Application的创建。

package com.gkatzioura.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

经过如上步骤,接下来直接运行Application即可。

原文链接:Integrate Redis into a Spring Project( 译者/丘志鹏 审校/朱正贵 责编/仲浩)

http://www.csdn.net/article/2015-09-01/2825600

使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决

应用场景

我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力。在执行DAO类的select***()query***()方法时,先从Redis中查询有没有缓存数据,如果有则直接从Redis拿到结果,如果没有再向数据库发起查询请求取数据。

序列化问题

要把domain object做为key-value对保存在redis中,就必须要解决对象的序列化问题。Spring Data Redis给我们提供了一些现成的方案:

  • JdkSerializationRedisSerializer. 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。
  • Jackson2JsonRedisSerializer. 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍。
    但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。

如果用方案一,就必须付出缓存多占用4倍内存的代价,实在承受不起。如果用方案二,则必须给每一种domain对象都配置一个Serializer,即如果我的应用里有100种domain对象,那就必须在spring配置文件中配置100个Jackson2JsonRedisSerializer,这显然是不现实的。

通过google, 发现spring data redis项目中有一个#145 pull request, 而这个提交请求的内容正是解决Jackson必须提供类型信息的问题。然而不幸的是这个请求还没有被merge。但我们可以把代码copy一下放到自己的项目中:

/**
 * @author Christoph Strobl
 * @since 1.6
 */
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {

    private final ObjectMapper mapper;

    /**
     * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
     */
    public GenericJackson2JsonRedisSerializer() {
        this((String) null);
    }

    /**
     * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
     * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
     * {@link JsonTypeInfo.Id#CLASS} will be used.
     * 
     * @param classPropertyTypeName Name of the JSON property holding type information. Can be {@literal null}.
     */
    public GenericJackson2JsonRedisSerializer(String classPropertyTypeName) {

        this(new ObjectMapper());

        if (StringUtils.hasText(classPropertyTypeName)) {
            mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);
        } else {
            mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
        }
    }

    /**
     * Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
     * process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
     * specific types.
     * 
     * @param mapper must not be {@literal null}.
     */
    public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {

        Assert.notNull(mapper, "ObjectMapper must not be null!");
        this.mapper = mapper;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object)
     */
    @Override
    public byte[] serialize(Object source) throws SerializationException {

        if (source == null) {
            return SerializationUtils.EMPTY_ARRAY;
        }

        try {
            return mapper.writeValueAsBytes(source);
        } catch (JsonProcessingException e) {
            throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.serializer.RedisSerializer#deserialize(byte[])
     */
    @Override
    public Object deserialize(byte[] source) throws SerializationException {
        return deserialize(source, Object.class);
    }

    /**
     * @param source can be {@literal null}.
     * @param type must not be {@literal null}.
     * @return {@literal null} for empty source.
     * @throws SerializationException
     */
    public <T> T deserialize(byte[] source, Class<T> type) throws SerializationException {

        Assert.notNull(type,
                "Deserialization type must not be null! Pleaes provide Object.class to make use of Jackson2 default typing.");

        if (SerializationUtils.isEmpty(source)) {
            return null;
        }

        try {
            return mapper.readValue(source, type);
        } catch (Exception ex) {
            throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }
}

然后在配置文件中使用这个GenericJackson2JsonRedisSerializer:

<bean id="jacksonSerializer" class="com.fh.taolijie.component.GenericJackson2JsonRedisSerializer">
    </bean>

重新构建部署,我们发现这个serializer可以同时支持多种不同类型的domain对象,问题解决。

http://www.myexception.cn/database/1958643.html

     spring-data-redis提供了多种serializer策略,这对使用jedis的开发者而言,实在是非常便捷。sdr提供了4种内置的serializer:

  • JdkSerializationRedisSerializer:使用JDK的序列化手段(serializable接口,ObjectInputStrean,ObjectOutputStream),数据以字节流存储
  • StringRedisSerializer:字符串编码,数据以string存储
  • JacksonJsonRedisSerializer:json格式存储
  • OxmSerializer:xml格式存储

    其中JdkSerializationRedisSerializer和StringRedisSerializer是最基础的序列化策略,其中“JacksonJsonRedisSerializer”与“OxmSerializer”都是基于stirng存储,因此它们是较为“高级”的序列化(最终还是使用string解析以及构建java对象)。

    RedisTemplate中需要声明4种serializer,默认为“JdkSerializationRedisSerializer”:

    1) keySerializer :对于普通K-V操作时,key采取的序列化策略
    2) valueSerializer:value采取的序列化策略
    3) hashKeySerializer: 在hash数据结构中,hash-key的序列化策略
    4) hashValueSerializer:hash-value的序列化策略

    无论如何,建议key/hashKey采用StringRedisSerializer。

    接下来,通过实例描述如何使用它们,可以首先参考“spring-data-redis特性”:

http://shift-alt-ctrl.iteye.com/blog/1887370
http://www.cnblogs.com/google4y/p/3535106.html