Spring boot integrates Redis to implement caching

Posted by menwn on Sun, 23 Jan 2022 21:06:27 +0100

Spring boot integrates Redis to implement caching

Redis

redis is simply a memory based non relational database that stores data in the form of key value pairs.

The advantage is obvious. Because it is stored in memory, it reads very fast. This speed is completely unmatched by mysql and other databases, so we often use redis as cache to reduce queries to local databases.

Redis can be used as resource sharing between system processes, such as spring session sharing using redis

I won't say much about the installation and operation of redis here. Baidu has a very detailed tutorial. I still suggest deploying redis on linux virtual machine or server.

cache

The function of cache is to store data in memory. After querying the same data, you can get it directly from memory instead of executing the method again. This will undoubtedly save a lot of time.

For example, I have a method getById(Integer id), which queries the database through the id and obtains the entity class. When I first query, there is no cache, so I execute this method to get data from the database through mybatis. However, in the second query, if you still check the id passed in before, the program will find the result in the cache and directly get the result in memory instead of executing the method.

Application in SpringBoot

Program source code

Mysql preparation

Because it is only a simple demo, we continue to use the previous Customer table here

CREATE TABLE `customer` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(64) DEFAULT NULL COMMENT 'full name',
  `age` smallint DEFAULT NULL COMMENT 'Age',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Establishment time',
  `modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8

Establish connection

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: 1234
    
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log.impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-aliases-package: com.cwh.springboot.redis.model.entity
  mapper-locations: classpath:mapper/*Mapper.xml

Customer
@Data
@TableName("customer")
public class Customer {
    //    Set the primary key and the primary key generation policy
//    AUTO is database AUTO increment
    @TableId(type = IdType.AUTO)
    private Long id;

    //    @TableField(value="",exist=true)
//    Mapping non primary key field value field name exist indicates whether the attribute is in the database
    @TableField(value = "name")
    private String name;

    private Integer age;

    @TableField(value = "create_time",fill = FieldFill.INSERT)
//    Fill auto fill policy
//    INSERT plug-in filling, filling when UPDATE is updated, INSERT_UPDATE is populated when inserting updates
    private Date createTime;

    @TableField(value = "modify_time",fill = FieldFill.INSERT_UPDATE)
    private Date modifyTime;
}

Simple use of Redis

First, let's take a look at the redis configuration in spring boot

pom.xml
<!--        redis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
application.yml
spring:
  redis:
#    Database index, 0 by default
    database: 0
#    redis host ip
    host: 192.168.56.129
#    redis connection port
    port: 6379
#    Server connection password (blank by default)
    password:
#    Connection timeout (MS)
    timeout: 1000
    jedis:
#      Connection pool configuration
      pool:
#        Maximum connections in connection pool
        max-active: 8
#        Maximum blocking waiting time of connection pool (negative value indicates no limit)
        max-wait: -1
#        Connection pool maximum free connections
        max-idle: 8
#        Connection pool minimum free connections
        min-idl: 0

Here we connect to db0 and configure jedis connection pool

The main function of connection pool is to buffer connections, avoid repeated creation and destruction of connection objects, and improve performance

Redistemplate

redis provides two template s in springboot by default:

Stringredistemplate < String, String >: implements operations in which key value pairs are of String type

Redistemplate < object, Object > generic class, which is the parent class of StringRedisTempalte. It is generally used to customize the redistemplate of type key value pairs

    /**
     *  Set redistemplate < string, obejct > serialization
     * @param factory
     * @return
     */
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(factory);

//        Replace the default serialization with JackSon2JsonRedisSerialize
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//        Configure ObjecetMapper
        ObjectMapper om = new ObjectMapper();

//        Specify the fields to be serialized, field,get, set, and modifier range. ANY includes private and public
//        setVisibility(forMethod,visibility)
//        Used to replace the default serialization detection (default public files or public getXXX())
//        Formmethod is the affected property (field/getter/setter)
//        visibility setting attribute minimum setting (can be PUBLIC,ANY,PRIVATE)
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//      Specify the type of serialized input. The class must be non final modified. Final modified classes, such as string and integer, will run exceptions 
        			activeDefaultTyping(PolymorphicTypeValidator,ObjectMapper.DefaultTyping,JsonTypeInfo.As)
//        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
//        Configure redistemplate serialization
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

The above code mainly implements the operation template of redis whose key value pair is < string, Object >

In fact, the main work of user-defined Redistemplate is to set the serialization of key value pairs

public class StringRedisTemplate extends RedisTemplate<String, String> {
    public StringRedisTemplate() {
        this.setKeySerializer(RedisSerializer.string());
        this.setValueSerializer(RedisSerializer.string());
        this.setHashKeySerializer(RedisSerializer.string());
        this.setHashValueSerializer(RedisSerializer.string());
    }

StringRedisTemplate is mainly used to set serialization to stringredisserializer UTF_ eight

If we don't set anything, it can also be saved in redis, but the default JdkSerializationRedisSerializer is used, which stores data in redis in the form of binary stream, which has poor operability and readability.

So here we use JackSon2JsonRedisSerialize to replace the default serialization

JackSon2JsonRedisSerialize stores data in Redis in JSON

We need to configure JackSon2JsonRedisSerialize

public void setObjectMapper(ObjectMapper objectMapper) sets ObjectMapper

ObjectMapper is the simplest way for Jackson to parse JSON. It can parse java objects into JSON or create java objects by parsing JSON (that is, deserialization)

Here, we set the serialized field and the type of serialized input through ObjectMapper

  • Setvisibility (for method, visibility): Specifies the domain to serialize

    • forMethod: affected properties (file / getter / setter)
    • visibility: attribute minimum setting (PUBLIC/ANY/PRIVATE)
    • om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY) refers to all properties that set and serialize all access permissions
  • activateDefaultTyping(PolymorphicTypeValidator,ObjectMapper.DefaultTyping): it is used to save the full class name of the object during serialization to facilitate deserialization

    Many online tutorials use objectmapper enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)

    This is the practice that the old version has been abandoned, which is said to have major security risks

    • PolymorphicTypeValidator: polymorphic validator, mainly used to obtain class name
    • ObjectMapper.DefaultTyping: serializes the default type, NON_FINAL non final object

After setting jackson2JsonRedisSerializer, you only need to serialize the key value pairs of redistemplate

Because I customize it as redistemplate < string, Object >, I still use StringRedisSerializer for KEY and jackson2JsonRedisSerializer for VALUE

        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer)

In this way, we have set the RedisTemplate for the < string, Object > key value pair. Next, let's look at the simple operations and results

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private CustomerService customerService;

    /**
     * Add data for < name, customer >
     * @param name
     * @return
     */
    @PostMapping("/add")
    public Customer addCustomer(@RequestParam("name")String name){
        Customer customer = customerService.getByName(name);
        redisTemplate.opsForValue().set(name,customer);
        return customer;
    }

Here we first obtain the incoming name parameters, then call the service method to get the Customer object, then name is KEY, Customer object is VALUE stored in redis.

You can see that the storage is successful

RDM tool is used here to connect redis for visual management

redisTemplate.opsForValue().set(key,value): encapsulates redis related operations for springboot

opsForValue is the operation on the string,

set(key,value) adds data to the database

See the native redis operation guide for details This link , it's very comprehensive

Spring Cache

spring cache is actually an annotation based caching function using the idea of AOP

You only need to add @ Cacheable() and other annotations to the method to be cached to complete the annotation

to configure

First, we configure the project to use redis cache

  cache:
    type: redis

Define our cache configuration class

@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * Configure cacheManager
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
//      The redisCacheManager constructor needs to provide a redisCacheWriter and a redisCacheConfigurer
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//      Configure cache serialization as jsonSerializer
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//      Set the default expiration time to one day
        defaultCacheConfig.entryTtl(Duration.ofDays(1));

//        It can also be built by builder
//        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(defaultCacheConfig).transactionAware().build();
        return new RedisCacheManager(redisCacheWriter,defaultCacheConfig);

    }

Here, the cache is enabled by adding @ EnableCaching, which inherits CachingConfigurerSupport, indicating that this is a cache configuration class

Our main task is to define our own cache manager

cacheManager: as the name suggests, it is used to manage and configure the cache

There are two ways to build a cacheManager:

  1. new RedisCacheManager(redisCacheWriter,redisCacheConfig): the constructor constructs and passes in a cached writer object and a cached configuration object
  2. RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(defaultCacheConfig).transactionAware().build(): call builder().build() method and can assign values to attributes

There are two concepts

  1. redisCacheWriter is a cache write object, which can be built through nonLockingRedisCacheWriter() and lockingRedisCacaheWriter, corresponding to locked writes and unlocked writes respectively
  2. defaultCacheConfig is a cache configuration object that configures some cache properties, such as setting the serialization party and cache expiration time of the cache

You can configure the cache manager by building and setting these two objects

@Cacheable
    @Cacheable(cacheNames = "customer",key = "#id" )
    public Customer getCustomerById(Integer id) {
        log.info("===========Call method============");
        return baseMapper.selectById(id);
    }

We put the return value into the cache by adding @ Cacheable

He has several attributes:

  • cacheNames/value: Specifies the name of the cache component. It can be simply understood as the table name in the database, but it is not accurate
  • Key: the key used to cache data, which can be specified. key="#param" can specify parameter values or other attributes
  • keyGenerator: the generator of the key. It is used to customize the generation of keys. It can be selected from the key and cannot be saved at the same time
  • Condition: set condition = "#id > 0" to be cached only under specified conditions
  • Unless: negative caching. When the condition in unless is true, the method return value will not be cached. unless = "result==null"
  • sync: whether to use asynchronous mode
    @Cacheable(cacheNames = "customer",key = "#id" )
    public Customer getCustomerById(Integer id) {
        log.info("===========Call method============");
        return baseMapper.selectById(id);
    }

Here we test whether to call the method or get the value directly from the cache by outputting the log

First request:

The console shows that the method was called for the first request

The cache has also been successfully added to redis

Second request:

You can find that instead of calling the method this time, you get the value directly from the cache

@CachePut
//  cacheput calls the method first, and then updates the data in the cache
    @CachePut(cacheNames = "customer",key = "#customer.id")
    public Customer updateCustomer(Customer customer){
        baseMapper.updateById(customer);
        return customer;
    }

@CachePut indicates that the method is called first, and then the data is updated in the cache

Property is the same as @ Cacheable

@CacheEvict
    @CacheEvict(cacheNames = "customer",key = "#id" ,beforeInvocation = false)
    public int deleteById(Integer id){        
        return baseMapper.deleteById(id);
    }

@CacheEvict indicates to clear the cache

Properties:

  • key: Specifies the data to delete

  • Before invocation: whether the cache is cleared before the method is executed,

    • True means to delete before the method, false means to delete after the method. If an exception occurs, it will not be cleared (it will be deleted if true)
    • The default is false
  • allEntries: indicates whether to clear all data

@Caching()
    @Caching(
            cacheable = {
                    @Cacheable(cacheNames = "customer",key = "#name" )
            },
            put = {
                    @CachePut(cacheNames = "customer",key = "#result.id")
            }
    )
    public Customer getByName(String name){
        QueryWrapper<Customer> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name",name);
        return baseMapper.getByName(queryWrapper);
    }

Define complex caching rules, in which multiple @ Cacheable,@CachePut,@CacheEvict can be nested

keyGenerator

As mentioned earlier, you can customize the keyGenerator to store data. The KEY can be the format we expect

@Component
public class MyKeyGenerator implements KeyGenerator {

//    Define item prefix
    private String prefix = "redis";

    @Override
    public Object generate(Object target, Method method, Object... objects) {
        char sp =':';
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(prefix);
        stringBuilder.append(sp);
//        Class name
        stringBuilder.append(target.getClass().getSimpleName());
        stringBuilder.append(sp);
//        Method name
        stringBuilder.append(method.getName());
        stringBuilder.append(sp);
//        Parameter name
        if(objects.length>0){
            for(Object object: objects){
                stringBuilder.append(object);
            }
        }
        else {
            stringBuilder.append("0");
        }
        return stringBuilder.toString();
    }
}

By implementing the KeyGenerator interface, we implement the generate method to define the generation rules of keys

Target: target object, which can be accessed through target getClass(). Getsimplename() gets the class name

Method: target method, which can be through method Getname() get method name

objects: parameter name

    @Autowired
    private MyKeyGenerator myKeyGenerator;

    @Cacheable(cacheNames = "getall",keyGenerator = "myKeyGenerator" )
    public List<Customer> getAll(){
        return baseMapper.getAll();
    }

Here, we simply define a method to generate keys using our custom myKeyGenerator

You can see that the keys stored in redis are generated logically

summary

Here is a brief introduction to the use of redis as a cache in spring boot. In fact, redis has many other uses, such as using redis to realize session sharing, etc.

Refer to the related operations of redis database This link

etName() ` get method name

objects: parameter name

    @Autowired
    private MyKeyGenerator myKeyGenerator;

    @Cacheable(cacheNames = "getall",keyGenerator = "myKeyGenerator" )
    public List<Customer> getAll(){
        return baseMapper.getAll();
    }

Here, we simply define a method to generate keys using our custom myKeyGenerator

[external chain picture transferring... (img-PFV8wYAe-1624582894635)]

You can see that the keys stored in redis are generated logically

summary

Here is a brief introduction to the use of redis as a cache in springboot. In fact, there are many uses of redis, such as using redis to realize session sharing and so on.

Refer to the related operations of redis database This link

Topics: Redis Spring Boot