Redis -- distributed cache

Keywords: Database Redis Cache memcached

What is distributed caching?

In the original application, the cache is usually stored in the JVM of the local application. However, with the trend of project microservicing, after the project is reconstructed in the way of microservice, the cache should be managed and stored by a separate server and deployed in different physical areas to achieve high availability.

Mybatis original cache storage method

When the distributed cache is not used, mybatis has its own cache. Mybatis stores the results of each query in the HashMap attribute of a class, The cache processing interface of mybatis is org.apache.ibatis.cache. The implementation class of cache operation is org.apache.ibatis.cache.impl PerpetualCache Mybatis caches the queried data into the cache attribute of the secondary implementation class.

The following is part of the source code

public class PerpetualCache implements Cache {

	//Cache id
    private final String id;
    
    //Final storage cache
    private final Map<Object, Object> cache = new HashMap();
	

    public PerpetualCache(String id) {
        this.id = id;
    }

	//Query cache id
    public String getId() {
        return this.id;
    }
	
	//Get cache size
    public int getSize() {
        return this.cache.size();
    }

	//Add data to cache
    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }

	//Get cache
    public Object getObject(Object key) {
        return this.cache.get(key);
    }

	//This method mybatis does not implement. The caller is a reserved method
    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

	//Clear cache
    public void clear() {
        this.cache.clear();
    }
    
    //... the source code is not fully displayed


Steps:
When the request arrives, access the dao layer in the application. mybatis resolves whether the < cache / > cache tag is added in the mapping file, indicating that the cache is enabled. In the default cache implementation class PerpetualCache, use the getObject(Object key) method to try to get the cache. If yes, the contents in the cache will be returned. Otherwise, the database will be queried, and the obtained results will be stored in the cache and returned to the user.

Implementation principle of distributed cache

The environment of this example is SpringBoot + Mybatis + Redis as the basic environment of distributed cache. Mybatis is used as the operator for storing, obtaining and deleting the cache, and Redis is used as the storer of the cache.

Steps:
When the request arrives, access the dao layer in the application. mybatis resolves whether the < cache / > cache tag is added in the mapping file, indicating that the cache is enabled.
If the class cache is enabled, the implementation class specified by us will be used to obtain the cache in redis. If yes, the cache will be returned. If not, the data will be queried from the database, and then stored in redis and returned to the user.

Our purpose is to replace the default cache processing class in mybatis and let it use the implementation class provided by us to operate the cache in Redis.

Implementation example

Dependencies:

Omit Springboot dependency

Database three piece set

	<dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid-spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
       </dependency>
     <dependency>

Use this dependency operation to Redis database

  		<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

step

Note that the entity class is implemented as a serialization interface

  1. Create a normal SSM project
  2. Configure the application.
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 907282050
    url: jdbc:mysql://127.0.0.1:3306/db_test?serverTimezone=Asia/Shanghai&useSSL=false
    type: com.alibaba.druid.pool.DruidDataSource

# Configure redis connection information	
  redis:
    host: localhost
    port: 6379
    database: 0

mybatis:
  mapper-locations: classpath:mappers/*.xml
  type-handlers-package: com.ccnn.test_01.com.ccnn.test_01.entity

logging:
  level:
    com.ccnn.test_01.dao: DEBUG

  1. Get the RedisTemplate object injected into the spring boot container to operate redis
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext = applicationContext;
    }

    public static Object getRedisTemplate(String name){
        return applicationContext.getBean(name);
    }
}

  1. The custom class implements the org.apache.ibatis.cache.Cache interface and its methods
import com.ccnn.test_01.jedis.ApplicationContextUtil;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

public class RedisCache implements Cache {

    /* id The value of is the fully qualified class name of this class (com.ccnn.test_01.dao.UserMapper) and the namespace in the mapper.xml mapping file
    The values in the label are the same*/
    private final String id;
	
	/* RedisTemplate is used as a property declaration for convenience*/
    private final RedisTemplate<Object,Object> redisTemplate;

    public RedisCache(String id) {
        this.id = id;
        /* Get RedisTemplate object and set serialization rules */
        redisTemplate = (RedisTemplate<Object,Object> ) ApplicationContextUtil.getRedisTemplate("redisTemplate");
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
    }

    @Override
    public String getId() {
        return this.id;
    }


    /*  Get data from cache

        If no data is obtained in the cache, it will be queried from the database,
        Use putObject() of this class for the queried data; Add to cache
        Storage (default cache implementation class used in mybatis: org.apache. ORG)
        ibatis.cache.impl.PerpetualCache A HashMap object is used in
        (for storage)

        Therefore, here we take the data from Redis and return it as cache.

     */
    @Override
    public Object getObject(Object o) {
        /*Here, the parameter O and O in putObject(Object o, Object o1) are the same parameter*/
        System.out.println("Get data——————————"+o)
        return redisTemplate.opsForHash().get(id,o.toString());
    }


    /*  Store data to cache

        If no result is found in the cache, it will be queried from the database
        Stored in the cache, so the data is stored in Redis here.

     */
    @Override
    public void putObject(Object o, Object o1) {
        //The following is the output of the parameter
        /*2057322197:1276118677:com.ccnn.test_01.dao.UserMapper.
        selectUser:0:2147483647:select * from user where name
        = ?:Xiao Li: sqlsessionfactorybean - [user {name = 'Xiao Li', age=15}]*/
        System.out.println(o.toString()+"---"+o1.toString());

        redisTemplate.opsForHash().put(id,o.toString(),o1);
    }

    /*mybatis Calls that do not implement this send have no callers*/
    @Override
    public Object removeObject(Object o) {
        return null;
    }

    //Remove data from cache
    @Override
    public void clear() {
       redisTemplate.delete(id);
    }

    @Override
    public int getSize() {
        return redisTemplate.opsForHash().size(id).intValue();
    }

	  //Encryption method
    private static String getKeyToM5(String k){
        return DigestUtils.md5DigestAsHex(k.getBytes());
    }
}

When updating, deleting and adding, the value in the redis database will be deleted according to the value of the id attribute. In the next query, the value will be added from mysql to redis.

  1. Add < cache type = "custom implementation class" / > to Mapper.xml mapping file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.ccnn.test_01.dao.UserMapper">

	<!--Specifies the cache processing class-->
    <cache type="com.ccnn.test_01.cache.RedisCache"/>

    <select id="selectUser" resultType="com.ccnn.test_01.entity.User" parameterType="String">
        select * from user where name = #{name}
    </select>

    <update id="updateUser" parameterType="String">
        update user set name = #{name} where age = 15
    </update>

</mapper>
  1. It is suitable for multi table joint query.

During multi table associated query, please modify the cache tag of the main party shared by one of the two associated mapper.xml files to:

  <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.ccnn.test_01.dao.EmpMapper">
		
	<cache-ref namespace="Shared mapper.xml The value of the namespace of the file"/>	
	
    <select >
       .......
    </select>

   
</mapper>

Enable both parties to operate the cache in a redis. When either party modifies the data, the cache will be cleared.

optimization

Optimize the cached data stored in redis.

When we see the key of the printed cached data, we find that the key is too long, so we use the MD5 encryption tool class of spring connotation (the value obtained after encrypting two files with the same content must be the same) to encrypt, generate a 32-bit hexadecimal string, and store it in Redis as a key. Decrypt when getting the cache.

Added above.

Posted by Blockis on Wed, 13 Oct 2021 12:11:08 -0700