Redis是一个速度非常快的非关系型数据库(non-relational database),它可以存储键(key)与5种不同类型的值(value)之间的映射(mapping),可以将存储在内存的键值对数据持久化到硬盘。可以使用复制特性来扩展读性能,还可以使用客户端分片来扩展写性能。
Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,spring-boot-starter-data-redis 依赖于spring-data-redis 和 lettuce 。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application.properties 中加入Redis服务端的相关配置 :
#redis配置
#Redis服务器地址
spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database=0
#连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=50
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=3000ms
#连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=20
#连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=2
#连接超时时间(毫秒)
spring.redis.timeout=5000ms
其中 spring.redis.database 的配置通常使用0即可,Redis 在配置的时候可以设置数据库数量,默认为16,可以理解为数据库的 schema
通过编写测试用例,举例说明如何访问Redis。
@RunWith(SpringRunner.class)
@SpringBootTest
public class FirstSampleApplicationTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void test() throws Exception {
// 保存字符串
stringRedisTemplate.opsForValue().set("name", "chen");
Assert.assertEquals("chen", stringRedisTemplate.opsForValue().get("name"));
}
}
上面的案例通过自动配置的 StringRedisTemplate
对象进行 redis 的对写操作,从对象命名就可注意到支持的是 string 类型,如果有用过 spring-data-redis 的开发者一定熟悉 RedisTemplate
接口,StringRedisTemplate 就相当于 RedisTemplate
的实现。
除了 String 类型,实战中经常会在 redis 中储存对象,我们就要在储存对象时对对象进行序列化。下面通过一个实例来完成对象的对写操作。
创建 User 实体
@Data
public class User implements Serializable {
private String userName;
private Integer age;
}
配置针对对象的RedisTemplate实例
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
/**
* 采用RedisCacheManager作为缓存管理器
* @param connectionFactory
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheManager redisCacheManager = RedisCacheManager.create(connectionFactory);
return redisCacheManager;
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
//解决键、值序列化问题
StringRedisTemplate template = new StringRedisTemplate(factory);
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);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
完成了配置工作后,编写测试用例实验效果
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class FirstSampleApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
public void test() throws Exception {
//保存对象
User user = new User();
user.setUserName("chen");
user.setAge(22);
redisTemplate.opsForValue().set(user.getUserName(), user);
Home("result:{}",redisTemplate.opsForValue().get("chen"));
}
}
这样我们就能对对象进行缓存了。但是在对 redis 更深入的了解中,一不小心就踩进坑里去了,下面对 redis 踩的坑做下记录。
@RestController
@RequestMapping("/chen/user")
@Slf4j
public class UserController {
@Autowired
IUserService userService;
@GetMapping("/hello")
@Cacheable(value = "redis_key",key = "#name",unless = "#result == null")
public User hello(@RequestParam("name")String name){
User user = new User();
user.setName(name);
user.setAge(22);
user.setEmail("chen_ti@outlook.com");
return user;
}
}
在使用 SpringBoot1.x 的时候,通过简单的配置 RedisTemplete 就可以了,升级到 SpringBoot2.0,spring-boot-starter-data-redis 也跟着升起来了,@Cacheable 就出现了乱码的情况,可以通过将上面的配置文件 RedisConfiguration 做如下更改解决 :
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
@Bean(name="redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate<>();
RedisSerializer redisSerializer = new StringRedisSerializer();
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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(redisCacheConfiguration)
.build();
return cacheManager;
}
}
报错信息:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.tuhu.twosample.chen.entity.User
Redis获取缓存异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX。
出现这种异常,我们需要自定义 ObjectMapper
,设置一些参数,而不是直接使用
Jackson2JsonRedisSerializer 类中黙认的
ObjectMapper,看源代码可以知道,Jackson2JsonRedisSerializer 中的 ObjectMapper
是直接使用new ObjectMapper() 创建的,这样 ObjectMapper 会将 redis 中的字符串反序列化为
java.util.LinkedHashMap类型,导致后续 Spring 对其进行转换成报错。其实我们只要它返回 Object 类型就可以了。
使用以下方法,构建一个 Jackson2JsonRedisSerializer
对象,将其注入 RedisCacheManager 即可。
/**
* 通过自定义配置构建Redis的Json序列化器
* @return Jackson2JsonRedisSerializer对象
*/
private Jackson2JsonRedisSerializer jackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
异常打印:
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。