mybatis与redis二级缓存总结

本文详细介绍了如何在Spring Boot环境中整合Mybatis的二级缓存,并使用Redis作为缓存工具,包括mapper级和语句级的配置,以及Redis缓存工具类的实现,如putObject、getObject和Clear方法。同时,文章讨论了使用Mybatis二级缓存需要注意的事项,如缓存一致性问题和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、在spring boot 环境整合

二、Mybatis映射文件使用二级缓存

mapper级标签

语句级

三、Redis缓存工具类

mybatis二级缓存接口

redis-string缓存工具类

putObject

getObject

Clear

redis-HashMap缓存工具类

putObject

getObject

clear

四、使用Mybatis二级缓存的注意事项


一、在spring boot 环境整合

https://blog.youkuaiyun.com/xushiyu1996818/article/details/89036631

二、Mybatis映射文件使用二级缓存

mapper级标签

    <!--
    eviction LRU
    flushInterval缓存时间,以毫秒为单位
    size缓存大小
    readOnly如果为false的话,缓存对象必须是可序列化的-->
    <cache eviction="LRU"
           type="com.xusy.util.MybatisCacheRedisMap"
           flushInterval="120000"
           size="1024"
           readOnly="true"/>

开启二级缓存,在XML配置文件中添加Cache节点即可,

eviction为缓存的策略有, 默认的是 LRU:

LRU – 最近最少使用的:移除最长时间不被使用的对象。

FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

type为使用的redis缓存工具类

flushInterval为缓存时间,以毫秒为单位,此时为120秒

size为保存多少个结果,现在为1024个

readOnly为是否是只读的,现在为true,在不同线程中的调用者之间修改它们会 导致冲突

只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改的,这样性能会好一些,缺点是因为他是只读的,所以不能被修改。如果设置为false的话,读写的缓存会通过序列化返回该缓存对象的拷贝,因为会把对象进行拷贝,这会慢一些,但是安全,因此默认是 false。

cache-ref标签

<cache-ref namespace="mapper.StudentMapper"/>

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。(这个当语句涉及联表操作要用到)

 

语句级

一旦在mapper里面写了cache标签,整个mapper的所有语句,都会默认使用缓存,默认方式如下

当为select语句时:

flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。

useCache默认为true,表示会将本条语句的结果进行二级缓存。

当为insert、update、delete语句时:

flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。

useCache属性在该情况下没有。

当为select语句的时候,如果没有去配置flushCache、useCache,那么默认是启用缓存的,所以,如果有必要,那么就需要人工修改配置,修改结果类似下面:

<select id="save" parameterType="XX" flushCache="true" useCache="false">
    ……
</select>


update 的时候如果 flushCache="false",则当你更新后,查询的数据数据还是老的数据。
 

三、Redis缓存工具类

mybatis二级缓存接口

/*jadclipse*/// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache
{

    public abstract String getId();

    public abstract void putObject(Object obj, Object obj1);

    public abstract Object getObject(Object obj);

    public abstract Object removeObject(Object obj);

    public abstract void clear();

    public abstract int getSize();

    public abstract ReadWriteLock getReadWriteLock();
}


总共分为7个方法,最重要的是put,get,clear方法

分别会在缓存加入(第一次select),缓存命中(第二次select),缓存清除(insert,update,delete时)

在mapper里的cache标签里的type的属性就是实现了这个cache接口的缓存工具类

redis-string缓存工具类

package com.xusy.util;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

public class MybatisCacheRedisString implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(MybatisCacheRedisString.class);
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id; 
    private RedisTemplate redisTemplate;
    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
    public MybatisCacheRedisString(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
        logger.debug("创建了mybatis的redis缓存"+id);
    }
    @Override
    public String getId() {
        return id;
    }
    /**
     * Put query result to redis
     *
     * @param key
     * @param value
     */
    @Override
    @SuppressWarnings("unchecked")
    public void putObject(Object key, Object value) {
        RedisTemplate redisTemplate = getRedisTemplate();
        //redisTemplate进行value操作(key-string)
        ValueOperations opsForValue = redisTemplate.opsForValue();
        //进行字符串操作,放入key和value,redis过期时间=EXPIRE_TIME_IN_MINUTES
        opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
        logger.debug("redis cache "+id+" put key: "+key.toString()+" value: "+value.toString());
    }
    /**
     * Get cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        ValueOperations opsForValue = redisTemplate.opsForValue();
        logger.debug("redis cache "+id+" get key: "+key.toString());
        //得到key的value
        return opsForValue.get(key);
    }
    /**
     * Remove cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object removeObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        //删除这个key
        redisTemplate.delete(key);
        logger.debug("redis cache "+id+" remove key: "+key.toString());
        return null;
    }
    /**
     * Clears this cache instance
     */
    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.execute((RedisCallback) connection -> {
        	//将数据库清空
            connection.flushDb();
            return null;
        });
        logger.debug("Clear all the cached query result from redis "+id);
    }
    @Override
    public int getSize() {
        return 0;
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}

查看一条日志

2019-04-11 16:59:20.597 DEBUG 19980 --- [nio-8080-exec-9] com.xusy.util.MybatisCacheRedisMap       : redis cache com.xusy.dao.UserMapper2 put key: -1962129931:1974203225:com.xusy.dao.UserMapper2.findById:0:2147483647:select * from user  where id=?:2:SqlSessionFactoryBean value: [ ]

这个mybatisCacheRedisString 缓存工具类有以下几个属性

readwritelock:读写锁,每个工具类实例各一个,并通过接口提供出去

id:工具类实例的标识,上面的例子就是com.xusy.dao.UserMapper2,就是这个mapper的namespace,可以看到不同mapper使用的工具类实例不同

redisTemplate:通过另外一个工具类applicationContextHolder从spring容器中获得。

putObject

        //redisTemplate进行value操作(key-string)
        ValueOperations opsForValue = redisTemplate.opsForValue();
        //进行字符串操作,放入key和value,redis过期时间=EXPIRE_TIME_IN_MINUTES
        opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);

先得到string操作的模板ValueOperations

然后进行string操作,key=key,value=value

可以看到上面那一次:key=-1962129931:1974203225:com.xusy.dao.UserMapper2.findById:0:2147483647:select * from user  where id=?:2:SqlSessionFactoryBean

相当于是mapper的包+方法+sql语句+sql参数

value为[]  相当于是返回的结果,也可以是value: [User [id=4, name=xsy, age=10]]

getObject

从数据库里得到key为key,字符串的结果

Clear

redisTemplate.execute((RedisCallback) connection -> {
        	//将数据库清空
            connection.flushDb();
            return null;
        });

可以看到这个clear操作将整个redis数据库清空,包括不属于这个namespace的数据,需要改进

 

redis-HashMap缓存工具类

package com.xusy.util;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

public class MybatisCacheRedisMap implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(MybatisCacheRedisMap.class);
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id; // 在构造函数自动注入,为namespace的名字
    private RedisTemplate redisTemplate;
    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
    public MybatisCacheRedisMap(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
        logger.debug("创建了mybatis的redis缓存"+id);
    }
    @Override
    public String getId() {
        return id;
    }
    /**
     * Put query result to redis
     *
     * @param key
     * @param value
     */
    @Override
    @SuppressWarnings("unchecked")
    public void putObject(Object key, Object value) {
        RedisTemplate redisTemplate = getRedisTemplate();
        HashOperations opsForHash=redisTemplate.opsForHash();
        opsForHash.put(id,key,value);
        logger.debug("redis cache "+id+" put key: "+key.toString()+" value: "+value.toString());
    }
    /**
     * Get cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        HashOperations opsForHash=redisTemplate.opsForHash();
        logger.debug("redis cache "+id+" get key: "+key.toString());
        return opsForHash.get(id, key);
    }
    /**
     * Remove cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object removeObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        logger.debug("redis cache "+id+" remove key: "+key.toString());
        HashOperations opsForHash=redisTemplate.opsForHash();
        opsForHash.delete(id, key);
        return null;
    }
    /**
     * Clears this cache instance
     */
    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.execute((RedisCallback) connection -> {
        	redisTemplate.delete(id);
            return null;
        });
        logger.debug("Clear all the cached query result from redis "+id);
    }
    @Override
    public int getSize() {
        return 0;
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}

 

putObject

HashOperations opsForHash=redisTemplate.opsForHash();
        opsForHash.put(id,key,value);

 

可以看到这次的put操作,是以id为key(就是namespace为key),数据结构是hashmap,map里的key为key,value为value

getObject

HashOperations opsForHash=redisTemplate.opsForHash();
        logger.debug("redis cache "+id+" get key: "+key.toString());
        return opsForHash.get(id, key);

这个get操作,是以id为key,取出hashmap中key=key的value

clear

redisTemplate.delete(id);

在mybatis缓存工具类中hashmap优于string的原因就是这个clear操作,string的clear是将数据库清空,而hashmap的clear是将key为id的value删除,不会删除其他namespace(key为其他id)的缓存数据

四、使用Mybatis二级缓存的注意事项

在一个命名空间下使用二级缓存
二级缓存对于不同的命名空间namespace的数据是互不干扰的,倘若多个namespace中对一个表进行操作的话,就会导致这不同的namespace中的数据不一致的情况。

在单表上使用二级缓存
在做关联关系查询时,就会发生多表的操作,此时有可能这些表存在于多个namespace中,这就会出现上一条内容出现的问题了。

查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值