Spring boot integrates redis trampling diary

Keywords: Database Redis Spring Java

In addition to providing excellent automatic testing for common relational databases, spring boot also provides automatic configuration support for many NoSQL databases, including redis, mongodb, elastic search, Solr and Cassandra.

01 integrate redis

Redis is a very fast non relational database. It can store the mapping between key and five different types of values. It can persist the key value pair data stored in memory to hard disk. You can use the replication feature to extend read performance and client sharding to extend write performance.

  • redis official website
  • redis Chinese community

1.1 introduce dependency

Spring Boot provides a component package for Redis integration: Spring Boot starter data Redis, which 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

At application.properties Add the relevant configuration of Redis server:

#Redis configuration - redis server address spring.redis.host=127 . 0.0.1 - redis server connection port spring.redis.port=6379#Redis Database index (default is 0) spring.redis.database=0   #Maximum number of connections in the connection pool (use a negative value to indicate no limit) spring.redis.jedis.pool.max-active=50 × connection pool maximum blocking waiting time (use negative value to indicate no limit) spring.redis.jedis.pool.max-wait=3000ms × maximum free connection in connection pool spring.redis.jedis.pool.max-idle=20 × minimum free connections in the connection pool spring.redis.jedis.pool.min-idle=2 × even Connect timeout (MS) spring.redis.timeout=5000ms

Among them spring.redis.database Usually, 0 is used for configuration of. Redis can set the number of databases when configuring. The default value is 16, which can be understood as the schema of the database

1.3 test visit

By writing test cases, this paper illustrates how to access Redis.

@RunWith(SpringRunner.class)@SpringBootTestpublic class FirstSampleApplicationTests {    @Autowired    StringRedisTemplate stringRedisTemplate;    @Test    public void test() throws Exception {        // Save string stringRedisTemplate.opsForValue().set("name", "chen");        Assert.assertEquals("chen", stringRedisTemplate.opsForValue().get("name"));    }}

The above case uses the automatically configured StringRedisTemplate object to write to redis. From the object naming, it can be noticed that the supported type is string. If the developers who have used spring data redis are familiar with the redistemplate < K, V > interface, StringRedisTemplate is equivalent to the implementation of redistemplate < string, string >.

In addition to String type, objects are often stored in redis in practice. We need to serialize the objects when we store them. Next, an instance is used to complete the object write operation.

Create User entity

@Datapublic class User implements Serializable {     private String userName;     private Integer age;}

Configure RedisTemplate instances for objects

@Configuration@EnableCachingpublic 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<String, String> redisTemplate(RedisConnectionFactory factory) {        //Solve the problem of key and value serialization: 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);        jackson2 JsonRedisSerializer.setObjectMapper (om);         template.setValueSerializer (jackson2JsonRedisSerializer);         template.afterPropertiesSet ();        return template;    }

After completing the configuration work, write the test case to test the effect

@RunWith(SpringRunner.class)@SpringBootTest@Slf4jpublic 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 objects. But in a deeper understanding of redis, I stepped into the pit accidentally. Next, I will record the pit that redis stepped on.

02 pit record

2.1 step 1: code scrambling caused by cacheable annotation

@RestController@RequestMapping("/chen/user")@Slf4jpublic 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;    }}

When using SpringBoot1.x, you can simply configure redistemplet, upgrade to SpringBoot2.0, spring boot starter data redis will also rise, and @ Cacheable will be in disorder, which can be solved by making the following changes to the above configuration file RedisConfiguration:

@Configuration@EnableCachingpublic class RedisConfiguration extends CachingConfigurerSupport {    @Bean(name="redisTemplate")    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {        RedisTemplate<String, String> template = new RedisTemplate<>();        RedisSerializer<String> 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<String> redisSerializer = new StringRedisSerializer();        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer( Object.class );         //Configure serialization rediscacheconfiguration config = red isCacheConfiguration.defaultCacheConfig ();         RedisCacheConfiguration redisCacheConfiguration =  config.serializeKeysWith (Redis SerializationContext.SerializationPair.fromSerializer (redisSerializer))             .serializeValuesWith(Redis SerializationContext.SerializationPair.fromSerializer (jackson2JsonRedisSerializer));        RedisCacheManager cacheManager =  RedisCacheManager.builder (factory)                 .cacheDefaults(redisCacheConfiguration)                .build();        return cacheManager;    }}

2.2 step 2: redis gets cache exception

Error message:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.tuhu.twosample.chen.entity.User

Redis get cache exception: java.lang.ClassCastException : java.util.LinkedHashMap cannot be cast to XXX.

In case of this exception, we need to customize ObjectMapper and set some parameters instead of directly using the ObjectMapper recognized in the Jackson2JsonRedisSerializer class. From the source code, we can see that the ObjectMapper in Jackson2JsonRedisSerializer is created directly using new ObjectMapper(), so that the ObjectMapper will deserialize the string in redis to java.util.LinkedHashMap Type, causing subsequent Spring to convert it into an error. In fact, we just need to return the Object type.

Use the following method to build a Jackson 2jsonredisserializer object and inject it into RedisCacheManager.

    /**     * Build the Json serializer of Redis through custom configuration * @ return Jackson 2jsonredisserializer object     */    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){        Jackson2JsonRedisSerializer<Object> 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 be reported 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);        jackson2 JsonRedisSerializer.setObjectMapper (objectMapper);        return jackson2JsonRedisSerializer;    }

2.3 step 3: class transfer path

Abnormal 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-b83f5ca2ed3019:32:50 WARN  - /market/renewal/homePage/indexorg.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]

Interceptors are used in the project to intercept each http request. Through the token passed from the front end, get the user information UserInfoExt from the redis cache. The user information is stored in the redis cache when the user logs in. According to the obtained user information, judge whether to save the login status. So this operation is required for all requests except the url in the white list. Through log printing, it is obvious that the operation steps of serialization and deserialization appear when UserInfoExt objects are stored in redis.

terms of settlement:

@Beanpublic RedisTemplate<K, V> redisTemplate() {    RedisTemplate<K, V> redisTemplate = new RedisTemplate<K, V>();    redisTemplate.setConnectionFactory(jedisConnectionFactory());    redisTemplate.setKeySerializer(new StringRedisSerializer());    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());    return redisTemplate; }

Looking at the Bean definition of redis, it is found that StringRedisSerializer is used for key serialization, and the serialization of value value value is the serialization method of GenericJackson2JsonRedisSerializer.
Where, the GenericJackson2JsonRedisSerializer serialization method will record the @ class information of the class in redis, as shown below:

{    "@class": "com.pa.market.common.util.UserInfoExt",    "url": "Baidu once, you will know",    "name": "baidu"}

"@class": "com.pa.market.common.util.UserInfoExt ", each object will have this id (as can be seen from the source code, @ class). If the user has been in the login state, the com.pa.market.common.util.UserInfoExt Serialization by this path. But after moving the classpath of userinfoext, the full package name changes. Therefore, an exception of no such class found will be thrown. In this way, an exception is thrown when judging whether the user exists. Therefore, all requests fail, and the logged in user cannot perform any operation.
ok records all the pits he stepped on, and finally breathes out his last breath. When he encounters such pits, he can avoid them calmly. However, there are still many pits in redis, and he may jump in easily later.

Posted by carrotcake1029 on Tue, 16 Jun 2020 21:16:33 -0700