Spring Boot integrates redis and garbled exception handling.
01 Integrate redis
Redis is a very fast non-relational database, which can store mappings between keys and 5 different types of values, and can store keys in memory The value pair data is persisted to disk. The replication feature can be used to scale read performance, and client-side sharding can be used to scale write performance.
redis official website
redis Chinese community
1.1 Introducing dependencies
Spring Boot provides a component package for Redis integration: spring-boot-starter-data-redis, spring-boot-starter-data-redis depends on spring-data-redis and lettuce.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
1.2 Parameter configuration
Add the relevant configuration of the Redis server to application.properties:
#redis configuration #Redis server address spring.redis.host=127.0.0.1 #Redis server connection port spring.redis.port=6379 #Redis database index (default 0) spring.redis.database=0 #Connection pool maximum number of connections (use a negative value to indicate no limit) spring.redis.jedis.pool.max-active=50 #Connection pool maximum blocking wait time (use a negative value to indicate no limit) spring.redis.jedis.pool.max-wait=3000ms #Maximum idle connections in the connection pool spring.redis.jedis.pool.max-idle=20 #Minimum idle connections in the connection pool spring.redis.jedis.pool.min-idle=2 #Connection timeout (milliseconds) spring.redis.timeout=5000ms
The configuration of spring.redis.database usually uses 0. Redis can set the number of databases when configuring. The default is 16, which can be understood as the schema of the database.
1.3 Test Access
Illustrate how to access Redis by writing test cases.
@RunWith(SpringRunner.class) @SpringBootTest public class FirstSampleApplicationTests { @Autowired StringRedisTemplate stringRedisTemplate; @Test public void test() throws Exception { // save the string stringRedisTemplate.opsForValue().set("name", "chen"); Assert.assertEquals("chen", stringRedisTemplate.opsForValue().get("name")); } }
In the above case, the redis write operation is performed through the automatically configured StringRedisTemplate
object . From the name of the object, it can be noticed that the string type is supported. If developers who have used spring-data-redis must be familiar with RedisTemplate
the interface, StringRedisTemplate is equivalent to RedisTemplate
the implementation of .
In addition to the String type, objects are often stored in redis in practice, and we need to serialize the objects when storing them. The following is an example to complete the object write operation.
Create User entity
@Data public class User implements Serializable { private String userName; private Integer age; }
Configure the RedisTemplate instance for the object
@Configuration @EnableCaching public class RedisConfiguration extends CachingConfigurerSupport { /** * Use RedisCacheManager as cache manager * @param connectionFactory */ @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheManager redisCacheManager = RedisCacheManager.create(connectionFactory); return redisCacheManager; } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // Solve the problem of serialization of keys and values 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; }
After completing the configuration work, write the test case experiment effect
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class FirstSampleApplicationTests { @Autowired RedisTemplate redisTemplate; @Test public void test() throws Exception { // save the object User user = new User(); user.setUserName("chen"); user.setAge(22); redisTemplate.opsForValue().set(user.getUserName(), user); Home("result:{}",redisTemplate.opsForValue().get("chen")); } }
So we can cache the object. But in the deeper understanding of redis, I accidentally stepped into the pit, and the following is a record of the pit that redis stepped on.
02 Stepping on the pit record
2.1 Stepping on pit 1: garbled characters caused by cacheable annotations
@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("[email protected]"); return user; } }
When using SpringBoot1.x, you can simply configure RedisTemplete, upgrade to SpringBoot2.0, spring-boot-starter-data-redis also rises, @Cacheable appears garbled, you can pass The above configuration file RedisConfiguration is solved by making the following changes:
@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 serialization method template.setKeySerializer(redisSerializer); //value serialization template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap serialization template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // configure serialization 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; } }
2.2 Stepping on pit 2: Redis gets cache exception
Error message:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.tuhu.twosample.chen.entity.User
Redis gets cache exception: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX.
When this kind of exception occurs, we need to customize ObjectMapper
and set some parameters, instead of directly using the recognized ObjectMapper in the Jackson2JsonRedisSerializer class. You can see from the source code that the ObjectMapper in Jackson2JsonRedisSerializer is created directly using new ObjectMapper(), so that ObjectMapper will The string in redis is deserialized into java.util.LinkedHashMap type, which causes subsequent Spring to convert it into an error. In fact, we only need it to return the Object type.
Use the following method to construct an Jackson2JsonRedisSerializer
object and inject it into the RedisCacheManager.
/** * Build Redis's Json serializer with custom configuration * @return Jackson2JsonRedisSerializer object */ 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); // This item must be configured, otherwise it will report 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; }
2.3 Stepping on the pit 3: class transfer path
Exception printing:
19:32:47 INFO - Started Application in 10.932 seconds (JVM running for 12.296) 19:32:50 INFO - get data from redis, key = 10d044f9-0e94-420b-9631-b83f5ca2ed30 19:32:50 WARN - /market/renewal/homePage/index org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve type id 'com.pa.market.common.util.UserInfoExt' into a subtype of [simple type, class java.lang.Object] : no such class found at [Source: [B@641a684c; line: 1, column: 11]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'com.pa.market.common.util.UserInfoExt ' into a subtype of [simple type, class java.lang.Object]: no such class found at [Source: [B@641a684c; line: 1, column: 11]
An interceptor is used in the project to intercept each http request. Through the token passed from the front end, the user information UserInfoExt is obtained from the redis cache. The user information is stored in the redis cache when the user logs in. According to the obtained user information, it is determined whether there is a login status. Therefore, other requests except the whitelisted urls need to do this operation. Through log printing, it is obvious that the operation steps of serialization and deserialization in UserInfoExt object storage to redis.
Solution:
@Bean public RedisTemplate redisTemplate() { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(jedisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; }
Looking at the bean definition of redis, it is found that the serialization of the key is the StringRedisSerializer serialization, and the serialization of the value value is GenericJackson2JsonRedisSerializer
the serialization method of .
The GenericJackson2JsonRedisSerializer
serialization method will record the @class information of the class in redis, as follows:
{ "@class": "com.pa.market.common.util.UserInfoExt", "url": "Baidu, you will know", "name": "baidu" }
"@class": "com.pa.market.common.util.UserInfoExt", each object will have this id (you can see why there is this @class through the source code), if the user has been logged in, yes Serialization with the path com.pa.market.common.util.UserInfoExt. But after moving the classpath of UserInfoExt, the full package name changed. So an exception of no such class found will be thrown. In this way, an exception is thrown where it is judged whether the user exists, so all requests fail, and the logged-in user cannot perform any operations.
Ok, I recorded all the pits I stepped on, and finally exhaled my last breath. I can easily avoid such pits in the future, but there are still many pits in redis, and I may jump in easily in the future.
0 Comments