Mybatis-Plus cache

1 Introduction

1. Introduction to Mybatis-Plus

Mybatis-Plus is an enhanced plug-in for the Mybatis framework. According to the official description, MP only enhances and does not change. The introduction of it will not affect existing projects and is as smooth as silk. And it can be done quickly with simple configuration CRUD operation, thus saving a lot of time. Code generation, paging, performance analysis and other functions are all available. The 3.X series officially recommends using the cache in the service, but the service generally does not use it, but directly uses @Cacheable

2. Mybatis-Plus cache level

The cache has a first-level cache and a second-level cache.

The first level cache is the cache at the SqlSession level. When operating the database, it is necessary to construct a sqlSession object, and there is a data structure (HashMap) in the object for storing cached data. The cache data area (HashMap) between different sqlSessions does not affect each other. The first-level cache is enabled by default and does not need to be configured.

The second-level cache is a cache at the mapper level. Multiple SqlSessions operate the SQL statement of the same Mapper. Multiple SqlSessions can share the second-level cache. The second-level cache is across SqlSessions. The second-level cache is enabled (entity classes must be serialized), and then configured in the configuration file.

2. Code

1. Pom dependencies

...
<!--redis-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.3.2</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
...

2. Open the secondary cache in the configuration file

mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  call-setters-on-nulls: true
  configuration:
    #缓存开启
    cache-enabled: true

3. Tool screenshot

包括:ApplicationContextUtil.java、MybatisPlusRedisCache.java、RedisConfig.java、RedisUtils.java

4. Tool class code

1. Write the Redis tool class RedisUtils

import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 *
 * Redis工具类,一般企业开发常用的一个工具类,不会去用原生的redis配置类
 *
 */

public final class RedisUtils {

    private RedisTemplate<String, Object> redisTemplate;

    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    public Object execute(RedisCallback action){
        Object execute = redisTemplate.execute(action);
        return execute;
    }

    /**
     * 删除Collection<String>集合中的key
     * @param keys
     * @return
     */
    public Long del(Collection<String> keys){
        Long delete = redisTemplate.delete(keys);
        return delete;
    }

    /**
     * 模糊查询key
     * @param str 模糊查询的条件
     * @return
     */
    public Set<String> keys(String str){
        Set<String> keys = redisTemplate.keys(str);
        return keys;
    }

    /**
     * 消息订阅与发布:发布
     * @param channel
     * @param message
     */
    public void convertAndSend(String channel, Object message){
        redisTemplate.convertAndSend(channel,message);
    }

    /**
     * 如果不存在且保存的时间大于0即可创建成功,否则创建失败
     * @param key 键
     * @param value 值
     * @param time 缓存的时间
     * @return
     */
    public boolean setNx(String key,Object value,long time){
        try {
            if (time > 0) {
                return redisTemplate.opsForValue().setIfAbsent(key,value,time,TimeUnit.SECONDS);
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 如果不存在才创建成功,否则就创建失败
     * @param key
     * @param value
     * @return
     */
    public boolean setNx(String key,Object value){
        try {
            return redisTemplate.opsForValue().setIfAbsent(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    // =============================common============================
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================String=============================
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ================================Map=================================
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    // ============================set=============================
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

2. Write ApplicationContextUtil (obtain beans manually)

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @ClassName : ApplicationContextUtil
 * @Description : 手动获取bean的工具类
 */
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
    private static ApplicationContext context;

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

    /**
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return context.getBean(beanName);
    }
}

 3. Write Mybatis-Plus cache implementation class MybatisRedisCache

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName : MybatisRedisCache
 * @Description : mybatis-plus结合redis自定义缓存管理
 * @author ZJ
 */
@Slf4j
public class MybatisPlusRedisCache implements Cache {


    /**
     * 注意,这里无法通过@Autowired等注解的方式注入bean,只能手动获取
     */
    private RedisUtils redisUtil;

    /**
     * 手动获取bean
     *
     * @return
     */
    private void getRedisUtil() {
        redisUtil = (RedisUtils) ApplicationContextUtil.getBean("redisUtils");
    }


    // 读写锁
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);


    private String id;

    public MybatisPlusRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        log.info("二级缓存id:{}",id);
        this.id = id;
    }


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

    @Override
    public void putObject(Object key, Object value) {
        log.info("存入缓存");

        if (redisUtil == null) {
            getRedisUtil();//获取bean
        }

        try {
            //将key加密后存入
            redisUtil.hset(this.id,this.MD5Encrypt(key),value);
        } catch (Exception e) {
            log.error("存入缓存失败!");
            e.printStackTrace();
        }


    }

    @Override
    public Object getObject(Object key) {
        log.info("获取缓存");

        if (redisUtil == null) {
            getRedisUtil();//获取bean
        }

        try {
            if (key != null) {
                return redisUtil.hget(this.id,this.MD5Encrypt(key));
            }
        } catch (Exception e) {
            log.error("获取缓存失败!");
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public Object removeObject(Object key) {
        log.info("删除缓存");

        if (redisUtil == null) {
            getRedisUtil();//获取bean
        }


        try {
            if (key != null) {
                redisUtil.del(this.MD5Encrypt(key));
            }
        } catch (Exception e) {
            log.error("删除缓存失败!");
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public void clear() {
        log.info("清空缓存");
        if (redisUtil == null) {
            getRedisUtil();//获取bean
        }

        try {
            redisUtil.del(this.id);
        } catch (Exception e) {
            log.error("清空缓存失败!");
            e.printStackTrace();
        }

    }

    @Override
    public int getSize() {
        if (redisUtil == null) {
            getRedisUtil();//获取bean
        }
        Long size = (Long)redisUtil.execute((RedisCallback<Long>) RedisServerCommands::dbSize);
        return size.intValue();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    /**
     * MD5加密存储key,以节约内存
     */
    private String MD5Encrypt(Object key){
        String s = DigestUtils.md5DigestAsHex(key.toString().getBytes());
        return s;
    }

}

 4. Write RedisConfig configuration class [register redisTemplate and redisUtils tools]

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfig {

    /**redis配置模板**/
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        //为了开发的方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //json的序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);

        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);

        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;
    }

    @Bean(name="redisUtils")
    public RedisUtils redisUtils(final RedisTemplate<String, Object> redisTemplate) {
        final RedisUtils jedisService = new RedisUtils(redisTemplate);
        return jedisService;
    }

}

5. Entity class code

The entity class must implement Serializable in order to be serialized into redis

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TPatient implements Serializable {
    
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String patientid;
    private String cardNo;
    private String myMobile;
    /**
     * 创建时间(LocaldateTime类型的字段需要加上两个注解才能顺利序列化)
     */
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createTime;
    ....
}

6. Mapper code

Add @CacheNamespace to the mapper class( implementation = MybatisPlusRedisCache.class, eviction =MybatisPlusRedisCache.class)


@Mapper
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)
public interface PatientMapper extends BaseMapper<TPatient> {
    //查询示例
    List<TPatient> getPatientList(TPatient tPatient);
    //保存示例
    Integer savePatient(TPatient tPatient);
}

7.XML code

Add the code <cache-ref namespace="mapper address"/> to the <mapper> tag
 

<?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.example.sc.thirdapp.h5.mapper.PatientMapper">
    <cache-ref namespace="com.example.sc.thirdapp.h5.mapper.PatientMapper"/>
    <!-- 保存示例 -->
    <insert id="savePatient">
        insert into t_patient(patientid,card_no) values(#{patientid},#{cardNo})
    </insert>
    <!-- 查询示例 -->
    <select id="getPatientList" resultType="com.example.sc.thirdapp.h5.entity.TPatient">
        select id,patientid,card_no
        from t_patient
        where my_mobile = #{myMobile}
    </select>
</mapper>

8. Test

@SpringBootTest
@Slf4j
public class MapperTest {

    @Resource
    private PatientMapper patientMapper;

    @Test
    public void findTest(){

        log.info("*****************************************************************************************");
        TPatient tPatient=new TPatient();
        tPatient.setMyMobile("123456");
        List<TPatient> list=patientMapper.getPatientList(tPatient);
        log.info("列表:{}",list.hashCode());
    }


    @Test
    public void saveTest(){

        log.info("*****************************************************************************************");
        TPatient tPatient=new TPatient();
        tPatient.setMyMobile("123456");
        tPatient.setPatientid("1");
        tPatient.setCardNo("1");
        tPatient.setCardType("1");
        tPatient.setCreateTime(LocalDateTime.now());
        Integer result=patientMapper.savePatient(tPatient);
        log.info("保存结果:{}",result);
    }
}

No-cache query list test results: query the cache first, and then query the data, print sql, and store in the cache after finding no data

Existing cache query list test results: first query the cache, find data, return directly, do not print sql

Saving sample test results: clearing the cache 

Guess you like

Origin blog.csdn.net/JohnGene/article/details/122621056