问题:最近在做一个项目,通过jackson 的方式将数据 缓存到 redis 后,再根据key 取出数据为 null。
原因:SpringBoot 版本采用 SpringBoot 1.5.22 Release, 采用yml 配置 redis ,database 采用 1。但实际上系统一直使用的数据库0,所以导致取出的数据是null。
redis:database: 1host: 192.168.1.xxxpassword: xxxpool:max-active: 8max-idle: 8max-wait: -1min-idle: 0port: 6380timeout: 60000
原来的 redisConfig 配置:
@Bean(name="redisTemplate")public RedisTemplate redisTemplate(RedisConnectionFactory factory){RedisTemplate redisTemplate = new RedisTemplate();redisTemplate.setConnectionFactory(factory);RedisSerializer redisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(redisSerializer);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);redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);RedisSerializer stringSerializer = new StringRedisSerializer();redisTemplate.setHashKeySerializer(stringSerializer);redisTemplate.setHashValueSerializer(stringSerializer);return redisTemplate;}
Jedis和Lettuce的区别
jedis和Lettuce都是Redis的客户端,它们都可以连接Redis服务器,但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器。因为当使用Jedis客户端连接Redis服务器的时候,每个线程都要拿自己创建的Jedis实例去连接Redis客户端,当有很多个线程的时候,不仅开销大需要反复的创建关闭一个Jedis连接,而且也是线程不安全的,一个线程通过Jedis实例更改Redis服务器中的数据之后会影响另一个线程;
但是如果使用Lettuce这个客户端连接Redis服务器的时候,就不会出现上面的情况,Lettuce底层使用的是Netty,当有多个线程都需要连接Redis服务器的时候,可以保证只创建一个Lettuce连接,使所有的线程共享这一个Lettuce连接,这样可以减少创建关闭一个Lettuce连接时候的开销;而且这种方式也是线程安全的,不会出现一个线程通过Lettuce更改Redis服务器中的数据之后而影响另一个线程的情况;
spring-boot-starter-data-redis有两种实现:lettuce 和 jedis 。然而默认是使用lettuce.
spring boot 2的spring-boot-starter-data-redis中,默认使用的是lettuce作为redis客户端,它与jedis的主要区别如下:
1.Jedis:
Jedis是同步的,不支持异步,Jedis客户端实例不是线程安全的,需要每个线程一个Jedis实例,所以一般通过连接池来使用Jedis.
优点:
提供了比较全面的 Redis 操作特性的 API
API 基本与 Redis 的指令一一对应,使用简单易理解
缺点:
同步阻塞 IO
不支持异步
线程不安全
如果不使用默认的lettuce,使用jedis的话,可以排除lettuce的依赖,手动加入jedis依赖,配置如下:
org.springframework.boot spring-boot-starter-data-redis io.lettuce lettuce-core
redis.clients jedis 2.9.0
2.Lettuce:
Lettuce是基于Netty框架的事件驱动的Redis客户端,其方法调用是异步的,Lettuce的API也是线程安全的,所以多个线程可以操作单个Lettuce连接来完成各种操作,同时Lettuce也支持连接池.
优点:
线程安全
基于 Netty 框架的事件驱动的通信,可异步调用
适用于分布式缓存
缺点:
API 更抽象,学习使用成本高
pom:
org.springframework.boot spring-boot-starter-data-redis
org.apache.commons commons-pool2
SpringBoot 2.0 以下 RedisConnectionFactory 通过properties 文件配置database 没有任何问题,但是通过yml 文件时,默认为0,修改为其他database 不生效,需要通过JedisConnectionFactory 重新设置一下。
@Bean(name="redisTemplate")public RedisTemplate redisTemplate(RedisConnectionFactory factory){RedisTemplate redisTemplate = new RedisTemplate();//因为配置文件改为 yml以后,配置文件中的spring.redis.database 非0时无效,此处手动设置成配置文件中内容。int database = Integer.valueOf(environment.getProperty("spring.redis.database"));JedisConnectionFactory jedisConnectionFactoryFactory = (JedisConnectionFactory)factory;jedisConnectionFactoryFactory.setDatabase(database);redisTemplate.setConnectionFactory(jedisConnectionFactoryFactory);RedisSerializer redisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(redisSerializer);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);jackson2JsonRedisSerializer.setObjectMapper(om);redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);RedisSerializer stringSerializer = new StringRedisSerializer();redisTemplate.setHashKeySerializer(stringSerializer);redisTemplate.setHashValueSerializer(stringSerializer);return redisTemplate;}
完整代码:
RedisCacheConfig
package com.wzw.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
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.core.env.Environment;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;@Configuration
@EnableCaching
public class RedisCacheConfig {@Resourceprivate Environment environment;@Beanpublic CacheManager cacheManager(RedisTemplate, ?> redisTemplate){RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);cacheManager.setDefaultExpiration(3600);//默认缓存1小时Map expiresMap=new HashMap();expiresMap.put("ibsLongTimeCache", 7*24*3600L);//长时间缓存区域 7天 缓存基本不会改变的数据expiresMap.put("ibsTempCache", 600L);//临时缓存区域 10分钟 改动基本稍多实时性要求不高的数据cacheManager.setExpires(expiresMap);return cacheManager;}@Bean(name="redisTemplate")public RedisTemplate redisTemplate(RedisConnectionFactory factory){RedisTemplate redisTemplate = new RedisTemplate();//因为配置文件改为 yml以后,配置文件中的spring.redis.database 非0时无效,此处手动设置成配置文件中内容。int database = Integer.valueOf(environment.getProperty("spring.redis.database"));JedisConnectionFactory jedisConnectionFactoryFactory = (JedisConnectionFactory)factory;jedisConnectionFactoryFactory.setDatabase(database);redisTemplate.setConnectionFactory(jedisConnectionFactoryFactory);RedisSerializer redisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(redisSerializer);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);jackson2JsonRedisSerializer.setObjectMapper(om);redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);RedisSerializer stringSerializer = new StringRedisSerializer();redisTemplate.setHashKeySerializer(stringSerializer);redisTemplate.setHashValueSerializer(stringSerializer);return redisTemplate;}@Beanpublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic 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();}};}}
RedisService
package com.wzw.service;import java.util.List;
import java.util.Map;public interface RedisService {/*** 批量删除对应的value** @param keys*/public void remove(final String... keys);/*** 批量删除key** @param pattern*/public void removePattern(final String pattern);/*** 删除对应的value** @param key*/public void remove(final String key);/*** 判断缓存中是否有对应的value** @param key* @return*/public boolean exists(final String key);/*** 读取缓存** @param key* @return*/public Object get(final String key);/*** 读取缓存** @param key* @return*/public Map
RedisServiceImpl
package com.wzw.service.impl;import com.wzw.service.RedisService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;@Service("redisService")
public class RedisServiceImpl implements RedisService{@Autowired@Qualifier("redisTemplate")private RedisTemplate redisTemplate;/*** 批量删除对应的value** @param keys*/@Overridepublic void remove(final String... keys) {for (String key : keys) {remove(key);}}/*** 批量删除key** @param pattern*/@Overridepublic void removePattern(final String pattern) {Set keys = redisTemplate.keys(pattern);if (keys.size() > 0)redisTemplate.delete(keys);}/*** 删除对应的value** @param key*/@Overridepublic void remove(final String key) {if (exists(key)) {redisTemplate.delete(key);}}/*** 判断缓存中是否有对应的value** @param key* @return*/@Overridepublic boolean exists(final String key) {return redisTemplate.hasKey(key);}/*** 读取缓存** @param key* @return*/@Overridepublic Object get(final String key) {Object result = null;ValueOperations operations = redisTemplate.opsForValue();result = operations.get(key);return result;}/*** 读取缓存** @param key* @return*/@Overridepublic Map getHashKey(final String key) {Map objectMap = redisTemplate.opsForHash().entries(key);;return objectMap;}/*** 读取缓存** @param key* @return*/@Overridepublic List getHashKey(final String key, List hashkeys) {List objectMap = redisTemplate.opsForHash().multiGet(key,hashkeys);return objectMap;}/*** 写入缓存** @param key* @param value* @return*/@Overridepublic boolean set(final String key, Object value) {boolean result = false;try {ValueOperations operations = redisTemplate.opsForValue();operations.set(key, value);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 写入缓存* @param key* @param value* @return*/@Overridepublic boolean set(final String key, Object value, Integer expireTime) {boolean result = false;try {ValueOperations operations = redisTemplate.opsForValue();operations.set(key, value);redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 设置失效时间* @param key* @param expireTime* @return* @author Jone* create date:2018年2月27日*/@Overridepublic boolean expire(final String key, Integer expireTime) {boolean result = false;try {redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);result = true;} catch (Exception e) {e.printStackTrace();}return result;}
}
yml
spring:redis:database: 1host: 192.168.1.xxxpassword: asdffdsapool:max-active: 8max-idle: 8max-wait: -1min-idle: 0port: 6380timeout: 60000
int database = Integer.valueOf(environment.getProperty("spring.redis.database"));JedisConnectionFactory jedisConnectionFactoryFactory = (JedisConnectionFactory)factory;jedisConnectionFactoryFactory.setDatabase(database);redisTemplate.setConnectionFactory(jedisConnectionFactoryFactory);