In addition to providing excellent automated testing for common relational databases, SpringBoot also provides automated configuration support for many NoSQL databases, including Redis, MongoDB, Elasticsearch, Solr and Cassandra.

Integrate redis

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

  • Redis website
  • Redis Chinese Community

Introduction of depend on

Spring Boot provides component packages that integrate with Redis: 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>
Copy the code

Parameter configuration

Add Redis server configuration to application.properties:

Port =6379 #redis database index (default: 0) Spring. Redis. Database = 0 # connection pool maximum number of connections (use a negative value indicates no limit) spring. Redis. Jedis. Pool. Max - active = 50 # connection pool biggest jam waiting time (use a negative value indicates no limit) Spring. Redis. Jedis. Pool. Max - wait = 3000 ms # connection pool the largest free connection spring. Redis. Jedis. Pool. The Max - idle = # 20 minimum idle connections in the connection pool Spring. Redis. Jedis. Pool. Min - idle = 2 # connection timeout time (milliseconds) spring. Redis. Timeout = 5000 msCopy the code

Spring.redis. Database is usually configured with 0. When configuring Redis, you can set the number of databases

Test access

Write test cases that illustrate how to access Redis.

@RunWith(SpringRunner.class) @SpringBootTest public class FirstSampleApplicationTests { @Autowired StringRedisTemplate stringRedisTemplate; @ Test public void Test () throws the Exception {/ / save the string stringRedisTemplate opsForValue (). The set (" name ", "Chen"); Assert.assertEquals("chen", stringRedisTemplate.opsForValue().get("name")); }}Copy the code

The above example uses an automatically configured StringRedisTemplate object to write to redis. As you can see from the name of the object, string is supported. If you’ve used spring-data-Redis, you’ll be familiar with the RedisTemplate

interface. StringRedisTemplate is equivalent to the implementation of RedisTemplate

.
,>
,v>

In addition to the String type, we often store objects in Redis, so we need to serialize the objects as we store them. An example is used to write the object.

Create User entity

@Data
public class User implements Serializable {

    private String userName;

    private Integer age;
}
Copy the code

Configure the RedisTemplate instance for the object

@Configuration @EnableCaching public class RedisConfiguration extends CachingConfigurerSupport { /** * Use redisconnectionemanager as the CacheManager * @param connectionFactory */ @bean public CacheManager CacheManager (RedisConnectionFactory) connectionFactory) { RedisCacheManager redisCacheManager = RedisCacheManager.create(connectionFactory); return redisCacheManager; } @Bean public RedisTemplate<String, String> 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; }}Copy the code

After completing the configuration, write test cases to experiment with the 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); log.info("result:{}",redisTemplate.opsForValue().get("chen")); }}Copy the code

So we can cache the object. But in the deeper understanding of Redis, accidentally stepped into the pit, the next face of redis stepped on the pit to make a record.

Record on pit

Tread 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; }}Copy the code

When using SpringBoot1.x, simply configure RedisTemplete. Upgrade to SpringBoot2.0 and spring-boot-starter-data-redis will also be raised. Garbled @cacheable can be resolved by making the following changes to the RedisConfiguration file above:

@Configuration @EnableCaching public 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 forms 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); / / configuration 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; }}Copy the code

Stomp 2: Redis failed to get cache

Error message:

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

Redis for abnormal cache: Java. Lang. ClassCastException: Java. Util. LinkedHashMap always be cast to XXX.

Appear this kind of abnormal, we need a custom ObjectMapper, set some parameters, rather than directly using Jackson2JsonRedisSerializer class 黙 recognize ObjectMapper, see the source code can know, The ObjectMapper Jackson2JsonRedisSerializer is directly use the new ObjectMapper () to create, ObjectMapper will deserialize the string in Redis to java.util.linkedhashMap, causing Spring to convert it to an error later. All we need is for it to return Object.

Use the following methods, build a Jackson2JsonRedisSerializer object, will inject RedisCacheManager can.

/ * * * through custom configuration building Redis Json serializer * @ 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 must be configured, or you will quote 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; }Copy the code

Tread pit 3: class transfer path

Abnormal print:

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]Copy the code

Cause of the problem:

Interceptors are used in the project to intercept each HTTP request. The token passed from the front end is used to obtain the user information UserInfoExt from the Redis cache. The user information is stored in the Redis cache when the user logs in. Determine whether the login status exists according to the obtained user information. Therefore, all requests except whitelisted urls need to perform this operation. Through log printing, it is obvious that the steps of serialization and deserialization of the UserInfoExt object stored in Redis occur.

Solutions:

@Bean
public 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;
 }
Copy the code

Check redis Bean definitions, found that the key of serialization is used StringRedisSerializer series, the value value of serialization is GenericJackson2JsonRedisSerializer serialization methods.

Including GenericJackson2JsonRedisSerializer serialization methods will record class in redis @ the class information, as shown below:

{
    "@class": "com.pa.market.common.util.UserInfoExt",
    "url": "www.baidu.com",
    "name": "baidu"
}
Copy the code

“@class”: “Com.pa.market.com mon. Util. UserInfoExt”, each object can have this id (you can see through the source code for this @ class), if the user has been in a state, login Is com.pa.market.com mon. Util. UserInfoExt the path for the serialization of operations. However, after moving UserInfoExt’s classpath, the package’s full name was changed. Therefore, the exception no such class found will be raised. This throws an exception where the user exists, so all requests fail, and the user who has logged in cannot do anything.

Ok recorded all the pits she stepped on and finally exhaled her last breath. In the future, she could easily avoid such pits. However, there are still many pits in Redis, and she may easily jump into them in the future.