Spring Boot使用redis做数据缓存

释放双眼,带上耳机,听听看~!

1 添加redis支持

在pom.xml中添加

Xml代码  

  1. <

dependency

  

  1.           

<
groupId

org.springframework.boot

</
groupId

  

  1.           

<
artifactId

spring-boot-starter-redis

</
artifactId

  

  1.       

</
dependency

  

 

2 redis配置

Java代码  

  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{  

  1.   
  2.     

@Bean  

  1.     

public KeyGenerator wiselyKeyGenerator(){  

  1.         

return 
new KeyGenerator() {  

  1.             

@Override  

  1.             

public Object generate(Object target, Method method, Object… params) {  

  1.                 StringBuilder sb = 

new StringBuilder();  

  1.                 sb.append(target.getClass().getName());  
  2.                 sb.append(method.getName());  
  3.                 

for (Object obj : params) {  

  1.                     sb.append(obj.toString());  
  2.                 }  
  3.                 

return sb.toString();  

  1.             }  
  2.         };  
  3.   
  4.     }  
  5.   
  6.     

@Bean  

  1.     

public CacheManager cacheManager(  

  1.             

@SuppressWarnings(
"rawtypes") RedisTemplate redisTemplate) {  

  1.         

return 
new RedisCacheManager(redisTemplate);  

  1.     }  
  2.   
  3.     

@Bean  

  1.     

public RedisTemplate<String, String> redisTemplate(  

  1.             RedisConnectionFactory factory) {  
  2.         StringRedisTemplate template = 

new StringRedisTemplate(factory);  

  1.         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = 

new Jackson2JsonRedisSerializer(Object.
class);  

  1.         ObjectMapper om = 

new ObjectMapper();  

  1.         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);  
  2.         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  
  3.         jackson2JsonRedisSerializer.setObjectMapper(om);  
  4.         template.setValueSerializer(jackson2JsonRedisSerializer);  
  5.         template.afterPropertiesSet();  
  6.         

return template;  

  1.     }  
  2. }  

 

 

3 redis服务器配置

Properties代码  

  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  

  1. spring.redis.pool.max-idle=

8 # pool settings …  

  1. spring.redis.pool.min-idle=

0  

  1. spring.redis.pool.max-active=

8  

  1. spring.redis.pool.max-wait=-

1  

  1. spring.redis.sentinel.master= # name of Redis server  
  2. spring.redis.sentinel.nodes= # comma-separated list of host:port pairs  

 

4 应用

测试两个实体类

Java代码  

  1. package com.wisely.ij.domain;  
  2.   
  3.   
  4. public 

class Address {  

  1.     

private Long id;  

  1.     

private String province;  

  1.     

private String city;  

  1.   
  2.     

public Address(Long id,String province, String city) {  

  1.         

this.id = id;  

  1.         

this.province = province;  

  1.         

this.city = city;  

  1.     }  
  2.   
  3.     

public Address() {  

  1.     }  
  2.   
  3.     

public Long getId() {  

  1.         

return id;  

  1.     }  
  2.   
  3.     

public 
void setId(Long id) {  

  1.         

this.id = id;  

  1.     }  
  2.   
  3.     

public String getProvince() {  

  1.         

return province;  

  1.     }  
  2.   
  3.     

public 
void setProvince(String province) {  

  1.         

this.province = province;  

  1.     }  
  2.   
  3.     

public String getCity() {  

  1.         

return city;  

  1.     }  
  2.   
  3.     

public 
void setCity(String city) {  

  1.         

this.city = city;  

  1.     }  
  2. }  

 

Java代码  

  1. package com.wisely.ij.domain;  
  2.   
  3.   
  4. public 

class User {  

  1.     

private Long id;  

  1.     

private String firstName;  

  1.     

private String lastName;  

  1.   
  2.     

public User(Long id,String firstName, String lastName) {  

  1.         

this.id = id ;  

  1.         

this.firstName = firstName;  

  1.         

this.lastName = lastName;  

  1.     }  
  2.   
  3.     

public User() {  

  1.     }  
  2.   
  3.     

public Long getId() {  

  1.         

return id;  

  1.     }  
  2.   
  3.     

public 
void setId(Long id) {  

  1.         

this.id = id;  

  1.     }  
  2.   
  3.     

public String getFirstName() {  

  1.         

return firstName;  

  1.     }  
  2.   
  3.     

public 
void setFirstName(String firstName) {  

  1.         

this.firstName = firstName;  

  1.     }  
  2.   
  3.     

public String getLastName() {  

  1.         

return lastName;  

  1.     }  
  2.   
  3.     

public 
void setLastName(String lastName) {  

  1.         

this.lastName = lastName;  

  1.     }  
  2. }  

 

 使用演示

Java代码  

  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 {  

  1.     

@Cacheable(value = 
"usercache",keyGenerator = 
"wiselyKeyGenerator")  

  1.     

public User findUser(Long id,String firstName,String lastName){  

  1.         System.out.println(

"无缓存的时候调用这里");  

  1.         

return 
new User(id,firstName,lastName);  

  1.     }  
  2.     

@Cacheable(value = 
"addresscache",keyGenerator = 
"wiselyKeyGenerator")  

  1.     

public Address findAddress(Long id,String province,String city){  

  1.         System.out.println(

"无缓存的时候调用这里");  

  1.         

return 
new Address(id,province,city);  

  1.     }  
  2. }  

 

Java代码  

  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 {  

  1.   
  2.     

@Autowired  

  1.     DemoService demoService;  
  2.   
  3.     

@RequestMapping(
"/test")  

  1.     

@ResponseBody  

  1.     

public String putCache(){  

  1.         demoService.findUser(1l,

"wang",
"yunfei");  

  1.         demoService.findAddress(1l,

"anhui",
"hefei");  

  1.         System.out.println(

"若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功");  

  1.         

return 
"ok";  

  1.     }  
  2.     

@RequestMapping(
"/test2")  

  1.     

@ResponseBody  

  1.     

public String testCache(){  

  1.         User user = demoService.findUser(1l,

"wang",
"yunfei");  

  1.         Address address =demoService.findAddress(1l,

"anhui",
"hefei");  

  1.         System.out.println(

"我这里没执行查询");  

  1.         System.out.println(

"user:"+
"/"+user.getFirstName()+
"/"+user.getLastName());  

  1.         System.out.println(

"address:"+
"/"+address.getProvince()+
"/"+address.getCity());  

  1.         

return 
"ok";  

  1.     }  
  2. }  

 

5 检验

 

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

Spring Boot使用redis做数据缓存

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

 

Spring Boot使用redis做数据缓存

 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

  

  1.     

<
groupId

org.springframework.data

</
groupId

  

  1.     

<
artifactId

spring-data-redis

</
artifactId

  

  1.     

<
version

1.6.0.RELEASE

</
version

  

  1. </

dependency

  

  1. <

dependency

  

  1.     

<
groupId

redis.clients

</
groupId

  

  1.     

<
artifactId

jedis

</
artifactId

  

  1.     

<
version

2.7.3

</
version

  

  1. </

dependency

  

  1. <!– 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 {  

  1.   
  2.     

@Bean  

  1.     

public JedisConnectionFactory redisConnectionFactory() {  

  1.         JedisConnectionFactory redisConnectionFactory = 

new JedisConnectionFactory();  

  1.   
  2.         

// Defaults  

  1.         redisConnectionFactory.setHostName(

"192.168.1.166");  

  1.         redisConnectionFactory.setPort(

6379);  

  1.         

return redisConnectionFactory;  

  1.     }  
  2.   
  3.     

@Bean  

  1.     

public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {  

  1.         RedisTemplate<String, String> redisTemplate = 

new RedisTemplate<String, String>();  

  1.         redisTemplate.setConnectionFactory(cf);  
  2.         

return redisTemplate;  

  1.     }  
  2.   
  3.     

@Bean  

  1.     

public CacheManager cacheManager(RedisTemplate redisTemplate) {  

  1.         RedisCacheManager cacheManager = 

new RedisCacheManager(redisTemplate);  

  1.   
  2.         

// Number of seconds before expiration. Defaults to unlimited (0)  

  1.         cacheManager.setDefaultExpiration(

3000); 
// Sets the default expire time (in seconds)  

  1.         

return cacheManager;  

  1.     }  
  2.       
  3. }  

当然也别忘了把这些 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")  

  1. public List<City> provinceCities(String province) {  
  2.     logger.debug(

"province=" + province);  

  1.     

return 
this.cityMapper.provinceCities(province);  

  1. }  
  2.   
  3. // R  
  4. @Cacheable(

"searchCity")  

  1. public City searchCity(String city_code){  
  2.     logger.debug(

"city_code=" + city_code);  

  1.     

return 
this.cityMapper.searchCity(city_code);     

  1. }  

 

4. 缓存数据一致性保证

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

[java] view plain
 copy

 

 print?

  1. // C  
  2. @CacheEvict(value = { 

"provinceCities"}, allEntries = 
true)  

  1. public 

void insertCity(String city_code, String city_jb,   

  1.         String province_code, String city_name,  
  2.         String city, String province) {  
  3.     City cityBean = 

new City();  

  1.     cityBean.setCityCode(city_code);  
  2.     cityBean.setCityJb(city_jb);  
  3.     cityBean.setProvinceCode(province_code);  
  4.     cityBean.setCityName(city_name);  
  5.     cityBean.setCity(city);  
  6.     cityBean.setProvince(province);  
  7.     

this.cityMapper.insertCity(cityBean);  

  1. }  
  2. // U  
  3. @CacheEvict(value = { 

"provinceCities", 
"searchCity" }, allEntries = 
true)  

  1. public 

int renameCity(String city_code, String city_name) {  

  1.     City city = 

new City();  

  1.     city.setCityCode(city_code);  
  2.     city.setCityName(city_name);  
  3.     

this.cityMapper.renameCity(city);  

  1.     

return 
1;  

  1. }  
  2.   
  3. // D  
  4. @CacheEvict(value = { 

"provinceCities", 
"searchCity" }, allEntries = 
true)  

  1. public 

int deleteCity(String city_code) {  

  1.     

this.cityMapper.deleteCity(city_code);  

  1.     

return 
1;  

  1. }  

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

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

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

[java] view plain
 copy

 

 print?

  1. @Cacheable(

"users")  

  1. public User findByUsername(String username)  

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

[java] view plain
 copy

 

 print?

  1. @Cacheable(

"users")  

  1. 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() {  

  1.         

@Override  

  1.         

public Object generate(Object o, Method method, Object… objects) {  

  1.             StringBuilder sb = 

new StringBuilder();  

  1.             sb.append(o.getClass().getName());  
  2.             sb.append(method.getName());  
  3.             

for (Object obj : objects) {  

  1.                 sb.append(obj.toString());  
  2.             }  
  3.             

return sb.toString();  

  1.         }  
  2.     };  
  3. }  

于是上述两个方法,对于 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 接口:
Eclipse 控制台输出如下:
说明这一次请求没有命中缓存,走的是 db 查询。JMeter 再次请求,Eclipse 控制台输出:
标红部分以下是这一次请求的 log,没有访问 db 的 log,缓存命中。查看本次请求的 Redis 存储情况:
同样可以验证 city_code 为 1492 的 searchCity 方法的缓存是否有效:
图中标红部分是 searchCity 的缓存存储情况。

6.2 缓存一致性的验证

先来验证 insertCity 方法的缓存配置,JMeter 调用 /bdp/city/create.json 接口:
之后看 Redis 存储:
可以看出 provinceCities 方法的缓存已被清理掉,insertCity 方法的缓存奏效。
然后验证 renameCity 方法的缓存配置,JMeter 调用 /bdp/city/rename.json 接口:
之后再看 Redis 存储:
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 名为 provinceCitieskeys key-value 存储中,参考下图"provinceCities 方法在 Redis 中的缓存情况"。可以通过 redis-cli 使用 del 命令将 provinceCitieskeys 删除,但每个省份的缓存却不会被清除。
  3. CacheManager 必须设置缓存过期时间,否则缓存对象将永不过期,这样做的原因如上,避免一些野数据“永久保存”。此外,设置缓存过期时间也有助于资源利用最大化,因为缓存里保留的永远是热点数据。
  4. 缓存适用于读多写少的场合,查询时缓存命中率很低、写操作很频繁等场景不适宜用缓存。

后记

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

参考资料

  • Caching Data with Spring
      1. Cache Abstraction Part VII. Integration
    • Caching Data in Spring Using Redis
    • Caching with Spring Data Redis
    • spring-redis-caching-example

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

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

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

 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1group &#x27;com.gkatzioura.spring&#x27;
2version &#x27;1.0-SNAPSHOT&#x27;
3apply plugin: &#x27;java&#x27;
4apply plugin: &#x27;eclipse&#x27;
5apply plugin: &#x27;idea&#x27;
6apply plugin: &#x27;spring-boot&#x27;
7buildscript {
8    repositories {
9        mavenCentral()
10    }
11    dependencies {
12        classpath(&quot;org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE&quot;)
13    }
14}
15jar {
16    baseName = &#x27;gs-serving-web-content&#x27;
17    version =  &#x27;0.1.0&#x27;
18}
19sourceCompatibility = 1.8
20repositories {
21    mavenCentral()
22}
23dependencies {
24    compile &quot;org.springframework.boot:spring-boot-starter-thymeleaf&quot;
25    compile &#x27;org.slf4j:slf4j-api:1.6.6&#x27;
26    compile &#x27;ch.qos.logback:logback-classic:1.0.13&#x27;
27    compile &#x27;redis.clients:jedis:2.7.0&#x27;
28    compile &#x27;org.springframework.data:spring-data-redis:1.5.0.RELEASE&#x27;
29    testCompile group: &#x27;junit&#x27;, name: &#x27;junit&#x27;, version: &#x27;4.11&#x27;
30}
31task wrapper(type: Wrapper) {
32    gradleVersion = &#x27;2.3&#x27;
33}
34

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1package com.gkatzioura.spring.config;
2import org.springframework.cache.CacheManager;
3import org.springframework.cache.annotation.CachingConfigurerSupport;
4import org.springframework.cache.annotation.EnableCaching;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.data.redis.cache.RedisCacheManager;
8import org.springframework.data.redis.connection.RedisConnectionFactory;
9import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
10import org.springframework.data.redis.core.RedisTemplate;
11import org.springframework.data.redis.serializer.RedisSerializer;
12import org.springframework.data.redis.serializer.StringRedisSerializer;
13@Configuration
14@EnableCaching
15public class RedisConfig extends CachingConfigurerSupport {
16    @Bean
17    public JedisConnectionFactory redisConnectionFactory() {
18        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
19        jedisConnectionFactory.setUsePool(true);
20        return jedisConnectionFactory;
21    }
22    @Bean
23    public RedisSerializer redisStringSerializer() {
24        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
25        return stringRedisSerializer;
26    }
27    @Bean(name=&quot;redisTemplate&quot;)
28    public RedisTemplate&lt;String, String&gt; redisTemplate(RedisConnectionFactory cf,RedisSerializer redisSerializer) {
29        RedisTemplate&lt;String, String&gt; redisTemplate = new RedisTemplate&lt;String, String&gt;();
30        redisTemplate.setConnectionFactory(cf);
31        redisTemplate.setDefaultSerializer(redisSerializer);
32        return redisTemplate;
33    }
34    @Bean
35    public CacheManager cacheManager() {
36        return new RedisCacheManager(redisTemplate(redisConnectionFactory(),redisStringSerializer()));
37    }
38}
39

 

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


1
2
3
4
5
6
7
8
1package com.gkatzioura.spring.cache;
2import java.util.Date;
3import java.util.List;
4public interface CacheService {
5    public void addMessage(String user,String message);
6    public List&lt;String&gt; listMessages(String user);
7}
8

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1package com.gkatzioura.spring.cache.impl;
2import com.gkatzioura.spring.cache.CacheService;
3import org.springframework.data.redis.core.ListOperations;
4import org.springframework.data.redis.core.RedisOperations;
5import org.springframework.data.redis.core.SetOperations;
6import org.springframework.stereotype.Service;
7import javax.annotation.Resource;
8import java.time.ZonedDateTime;
9import java.time.temporal.ChronoUnit;
10import java.util.Date;
11import java.util.List;
12@Service(&quot;cacheService&quot;)
13public class RedisService implements CacheService {
14    @Resource(name = &quot;redisTemplate&quot;)
15    private ListOperations&lt;String, String&gt; messageList;
16    @Resource(name = &quot;redisTemplate&quot;)
17    private RedisOperations&lt;String,String&gt; latestMessageExpiration;
18    @Override
19    public void addMessage(String user,String message) {
20        messageList.leftPush(user,message);
21        ZonedDateTime zonedDateTime = ZonedDateTime.now();
22        Date date = Date.from(zonedDateTime.plus(1, ChronoUnit.MINUTES).toInstant());
23        latestMessageExpiration.expireAt(user,date);
24    }
25    @Override
26    public List&lt;String&gt; listMessages(String user) {
27        return messageList.range(user,0,-1);
28    }
29}
30

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1package com.gkatzioura.spring.controller;
2import com.gkatzioura.spring.cache.CacheService;
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.web.bind.annotation.*;
5import java.util.List;
6@RestController
7public class MessageController {
8    @Autowired
9    private CacheService cacheService;
10    @RequestMapping(value = &quot;/message&quot;,method = RequestMethod.GET)
11    @ResponseBody
12    public List&lt;String&gt; greeting(String user) {
13        List&lt;String&gt; messages = cacheService.listMessages(user);
14        return messages;
15    }
16    @RequestMapping(value = &quot;/message&quot;,method = RequestMethod.POST)
17    @ResponseBody
18    public String saveGreeting(String user,String message) {
19        cacheService.addMessage(user,message);
20        return &quot;OK&quot;;
21    }
22}
23

最后完成类Application的创建。


1
2
3
4
5
6
7
8
9
10
1package com.gkatzioura.spring;
2import org.springframework.boot.SpringApplication;
3import org.springframework.boot.autoconfigure.SpringBootApplication;
4@SpringBootApplication
5public class Application {
6    public static void main(String[] args) {
7        SpringApplication.run(Application.class, args);
8    }
9}
10

经过如上步骤,接下来直接运行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一下放到自己的项目中:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
1/**
2 * @author Christoph Strobl
3 * @since 1.6
4 */
5public class GenericJackson2JsonRedisSerializer implements RedisSerializer&lt;Object&gt; {
6
7    private final ObjectMapper mapper;
8
9    /**
10     * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
11     */
12    public GenericJackson2JsonRedisSerializer() {
13        this((String) null);
14    }
15
16    /**
17     * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
18     * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
19     * {@link JsonTypeInfo.Id#CLASS} will be used.
20     *
21     * @param classPropertyTypeName Name of the JSON property holding type information. Can be {@literal null}.
22     */
23    public GenericJackson2JsonRedisSerializer(String classPropertyTypeName) {
24
25        this(new ObjectMapper());
26
27        if (StringUtils.hasText(classPropertyTypeName)) {
28            mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);
29        } else {
30            mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
31        }
32    }
33
34    /**
35     * Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
36     * process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
37     * specific types.
38     *
39     * @param mapper must not be {@literal null}.
40     */
41    public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
42
43        Assert.notNull(mapper, &quot;ObjectMapper must not be null!&quot;);
44        this.mapper = mapper;
45    }
46
47    /*
48     * (non-Javadoc)
49     * @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object)
50     */
51    @Override
52    public byte[] serialize(Object source) throws SerializationException {
53
54        if (source == null) {
55            return SerializationUtils.EMPTY_ARRAY;
56        }
57
58        try {
59            return mapper.writeValueAsBytes(source);
60        } catch (JsonProcessingException e) {
61            throw new SerializationException(&quot;Could not write JSON: &quot; + e.getMessage(), e);
62        }
63    }
64
65    /*
66     * (non-Javadoc)
67     * @see org.springframework.data.redis.serializer.RedisSerializer#deserialize(byte[])
68     */
69    @Override
70    public Object deserialize(byte[] source) throws SerializationException {
71        return deserialize(source, Object.class);
72    }
73
74    /**
75     * @param source can be {@literal null}.
76     * @param type must not be {@literal null}.
77     * @return {@literal null} for empty source.
78     * @throws SerializationException
79     */
80    public &lt;T&gt; T deserialize(byte[] source, Class&lt;T&gt; type) throws SerializationException {
81
82        Assert.notNull(type,
83                &quot;Deserialization type must not be null! Pleaes provide Object.class to make use of Jackson2 default typing.&quot;);
84
85        if (SerializationUtils.isEmpty(source)) {
86            return null;
87        }
88
89        try {
90            return mapper.readValue(source, type);
91        } catch (Exception ex) {
92            throw new SerializationException(&quot;Could not read JSON: &quot; + ex.getMessage(), ex);
93        }
94    }
95}
96

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


1
2
3
1&lt;bean id=&quot;jacksonSerializer&quot; class=&quot;com.fh.taolijie.component.GenericJackson2JsonRedisSerializer&quot;&gt;
2    &lt;/bean&gt;
3

重新构建部署,我们发现这个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

 

 

 

 

 

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++中引用和指针的区别

2022-1-11 12:36:11

安全运维

史上最简单的 MySQL 教程(二十)「数据的高级操作 之 主键冲突」

2021-12-11 11:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索