Springboot integrates redis stepping on the pit diary

Time:2021-7-30

Springboot integrates redis stepping on the pit diary

In addition to providing excellent automated testing for commonly used relational databases, springboot also provides automated configuration support for many NoSQL databases, including redis, mongodb, elasticsearch, Solr and Cassandra.

01 integrate redis

Redis is a very fast non relational database. It can store the mapping between keys and five different types of values, and persist the key value pair data stored in memory to the hard disk. Replication features can be used to extend read performance, and client sharding can be used to extend 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 configurations of redis server in 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 (0 by default) spring. Redis. Database = 0 # maximum connections in connection pool (use negative value to indicate no limit) spring. Redis. Jedis. Pool. Max active = 50 # maximum blocking waiting time in connection pool (use negative value to indicate no limit) spring.red Is. Jedis. Pool. Max wait = 3000ms # maximum free connections in connection pool spring. Redis. Jedis. Pool. Max idle = 20 # minimum free connections in connection pool spring. Redis. Jedis. Pool. Min idle = 2# connection timeout (MS) spring. Redis. Timeout = 5000ms

Spring.redis.database is usually configured with 0. Redis can set the number of databases during configuration. The default is 16, which can be understood as the schema of the database

1.3 test access

Write test cases to illustrate how to access redis.

@RunWith(SpringRunner.class)@SpringBootTestpublic 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"));    }}

The above case is automatically configured  StringRedisTemplate  The object is used to write to redis. From the object name, you can notice that the supported type is string. If you have used spring data redis, you must be familiar with it  RedisTemplate<K,V>  Interface, stringredistemplate is equivalent to  RedisTemplate<String, String>  Implementation of.

In addition to the string type, objects are often stored in redis in practice. We need to serialize the objects when storing them. The following is an example 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

@ [email protected] Class redisconfiguration extensions cacheingconfigurersupport {/ * * * rediscachemanager is used as the cache manager * @ param connectionfactory * / @ bean public CacheManager CacheManager (redisconnectionfactory) {rediscachemanager rediscachemanager = rediscachemanager.create e(connectionFactory);         return  redisCacheManager;    }     @ Bean public redistemplate < string, string > redistemplate (redisconnectionfactory) {// 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);         jackson2JsonRedisSerializer.setObjectMapper(om);         template.setValueSerializer(jackson2JsonRedisSerializer);         template.afterPropertiesSet();         return template;    }

After completing the configuration work, write test cases and test results

@RunWith(SpringRunner.class)@ [email protected]  class FirstSampleApplicationTests {    @Autowired    RedisTemplate redisTemplate;    @ Test public void test() throws exception {// save object 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. However, in a deeper understanding of redis, I accidentally stepped into the pit. Here is a record of the pit redis stepped on.

02 pit record

2.1 stepping on Pit 1: garbled code caused by cacheable annotation

@[email protected]("/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("[email protected]");        return user;    }}

When using springboot1. X, simply configure redistemplet. When upgrading to springboot2.0, spring boot starter data redis will also rise, and @ cacheable will be garbled. You can change the above configuration file redisconfiguration as follows:

@ [email protected]  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 = rediscacheconfiguration. Defaultcacheconfiguration(); 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 get cache exception: java.lang.classcastexception: java.util.linkedhashmap cannot be cast to XXX.

If this exception occurs, we need to customize it  ObjectMapper, set some parameters instead of directly using the objectmapper recognized in the jackson2jsonredisserializer class. You can see from the source code that the objectmapper in the jackson2jsonredisserializer is directly created using new objectmapper(), so that the objectmapper will deserialize the string in redis into java.util.linkedhashmap type, This causes subsequent spring to convert it into an error. In fact, we just need it to return the object type.

Use the following method to build a  Jackson2JsonRedisSerializer  Object and inject it into rediscachemanager.

/*** build redis's JSON serializer through custom configuration * @ return jackson2jsonredisserializer 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 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 pit: type 3 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: [[email protected]; 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: [[email protected]; line: 1, column: 11]

Interceptors are 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 to save the login status. Therefore, for URLs other than the white list, this operation is required for other requests. Through log printing, it is obvious that the steps of serialization and deserialization of userinfoext objects 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 the serialization of key uses stringredisserializer serialization, and the serialization of value is  GenericJackson2JsonRedisSerializer  Serialization method for.
among  GenericJackson2JsonRedisSerializer  The serialization method will record the @ class information of the class in redis, as shown below:

{"@ class": "com.pa.market.common.util.userinfoext", "URL": "Baidu, you will know", "name": "Baidu"}

“@ class”: “com.pa.market.common.util.userinfoext”. Each object has this ID (it can be seen from the source code that there is this @ class). If the user has been logged in, the serialization operation is carried out through the path com.pa.market.common.util.userinfoext. However, 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, so all requests fail, and the logged in user can’t do anything.
OK recorded all the pits he stepped on and finally breathed out his last breath. He could easily avoid such pits in the future, but there are still many pits in redis, and he may jump in easily in the future.