• notice
  • Congratulations on the launch of the Sought Tech site

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 StringRedisTemplateobject . 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 RedisTemplatethe interface, StringRedisTemplate is equivalent to RedisTemplatethe 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 ObjectMapperand 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 Jackson2JsonRedisSerializerobject 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 GenericJackson2JsonRedisSerializerthe serialization method of .
The GenericJackson2JsonRedisSerializerserialization 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.


Tags

Technical otaku

Sought technology together

Related Topic

0 Comments

Leave a Reply

+