SpringBoot+Mybatis uses Redis as the secondary cache to implement and solve the problem that the cache [call clear() method] cannot be updated when deleting / modifying / adding.

Posted by JustGotAQuestion on Thu, 09 Dec 2021 02:21:30 +0100

1, Build environment

  1. Configure Maven.
  2. Spring boot integrates Redis.
  3. SpringBoot integrates Mybatis.

The parent pom.xml file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.6</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhao</groupId>
    <artifactId>Redis</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <jedis.version>4.0.0-beta4</jedis.version>
        <junit.version>4.13.1</junit.version>
        <spring.boot.starter.data.redis>2.5.4</spring.boot.starter.data.redis>
        <lombok.version>1.18.20</lombok.version>
        <mybatis.spring.boot.starter>2.2.0</mybatis.spring.boot.starter>
        <druid.spring.boot.starter>1.2.5</druid.spring.boot.starter>
        <mysql.connect.java>8.0.26</mysql.connect.java>
    </properties>
    
    <modules>
        <module>01-Jedis</module>
        <module>02-SpringBoot-Redis</module>
        <module>03-Mybatis-Redis</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>${jedis.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <version>${spring.boot.starter.data.redis}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.starter}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.spring.boot.starter}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connect.java}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

The configuration file application.yaml is as follows:

server:
  port: 8809
spring:
  redis:
    database: 0
    port: 6379
    host: localhost
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8
      username: root
      password: 123
      driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.zhao.entity
  configuration:
    cache-enabled: true
logging:
  level:
    com:
      zhao:
        dao: debug

2, Get Redis client's tool class

RedisTemplate is used here as the Redis client.

package com.zhao.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

//Get the factory created by SpringBoot
@Configuration
@Slf4j
public class ApplicationContextUtils implements ApplicationContextAware {

    //Retained factories
    private static ApplicationContext applicationContext;

    //Pass the created factory to this class as a parameter
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //Provides methods to get objects in the factory
    //RedisTemplate redisTemplate
    public static Object getBean(String beanName) {
        if (beanName == null && beanName.equals(" ")) {
            return null;
        }
        return applicationContext.getBean(beanName);
    }
}

3, Customize Redis Cache class and implement Cache interface.

Note: 1. There must be a string member variable. 2. There must be a constructor to pass values for this member variable. 3.pojo/entity object must implement serialization interface.

package com.zhao.cache;

import com.zhao.util.ApplicationContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.concurrent.locks.ReadWriteLock;

@Slf4j
public class RedisCache implements Cache {

    private final String id;

    public RedisCache(String id) {
        if (id == null) {
           throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

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

    @Override
    public void putObject(Object key, Object value) {
        //Use the hash type in redis as the storage type of the cache
        getRedisTemplate().opsForHash().put(id.toString(), key.toString(), value);
        log.info("Query results stored in cache: id:{} key:{} value:{}", id.toString(), key.toString(), value.toString());
    }

    @Override
    public Object getObject(Object key) {
        //Get stored data
        Object value = getRedisTemplate().opsForHash().get(id.toString(), key.toString());
        log.info("Get cached content:{}", value);
        return value;
    }

    @Override
    public Object removeObject(Object key) {
        log.info("implement removeObject Method to remove the cache.");
        getRedisTemplate().opsForHash().delete(id, key.toString());
        return null;
    }

    @Override
    public void clear() {
        log.info("implement clear Method to empty the cache.");
        getRedisTemplate().delete(id.toString());//wipe cache 
    }

    @Override
    public int getSize() {
        //Gets the number of key values in the hash
        int num = getRedisTemplate().opsForHash().size(id).intValue();
        log.info("Gets the number of key value pairs in the cache:", num);
        return num;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    //Encapsulate redisTemplate
    private RedisTemplate getRedisTemplate() {
        //Get redisTemplate
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
  • getId(): get the Id value. This Id value is the same as namespace.
  • putObject(Object key, Object value): store the queried value into the cache. Since both keys and values are objects, we choose redisTemplate as the client for Redis operation and use hash type data to store cached values. Key corresponds to the member variable id, hkey corresponds to the method parameter Object key, and hvalue corresponds to the method parameter Object value.
  • getObject(Object key): get value according to the key.
  • Removeobject (object key): a reserved method in mybatis. It can only be called when fallback. It can not be implemented.
  • clear(): update/delete/insert this method is called at the bottom when updating the cache. What I implement here is to delete the cache under the current namespace according to the member variable id.
  • getReadWriteLock(): read-write lock is used to keep synchronization, which can not be implemented.
  • getRedisTemplate(): This is a method we added ourselves, not a method in the Cache interface. Use this method to obtain the RedisTemplate object in the ApplicationContext. In addition, key and hkey do not need to store objects, but only strings. So I set key and hkey to use StringRedisSerializer instead of the default JDK serializer.

4, Enable L2 cache and set cache

xml is used here

    <!--open mybatis L2 cache for-->
    <cache type="com.zhao.cache.RedisCache"/>

5, Special attention

If transaction control is added to the server layer, if @ Transactional(propagation = Propagation.SUPPORTS) or @ Transactional(propagation = Propagation.NOT_SUPPORTED) annotations are added to update / delete / add methods, update / delete / add will not call the clear() method in the buffer class, and the cache in Redis cannot be cleared.

Topics: Mybatis Redis Spring Boot Cache bug