mybatis二級緩存及redis整合,附帶@CacheNamespace失效避坑

實測版本

    <!-- tk.mybatis , 其中mybatis版本 3.5.3 -->
    <dependency>
        <groupId>tk.mybatis</groupId>
        <artifactId>mapper-spring-boot-starter</artifactId>
        <version>2.0.4</version>
    </dependency>

    <!-- springboot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.3.0.RELEASE</version>
    </dependency>

mybatis二級緩存相關(guān)注解

@org.apache.ibatis.annotations.CacheNamespace
  • 作用于mapper接口上, 標(biāo)記當(dāng)前mapper開啟二級緩存
  • 對應(yīng)mapper.xml文件中的 <cache/> 標(biāo)簽
@CacheNamespace
public interface DeviceInfoMapper {
    ...
}

這種方式標(biāo)記的,對mapper.xml中的sql無效, 但是對于使用@Select注解中的sql有效

@org.apache.ibatis.annotations.CacheNamespaceRef.CacheNamespace

標(biāo)記當(dāng)前mapper接口引用了指定class的緩存

  • 對應(yīng)mapper.xml文件中的 <cache-ref/> 標(biāo)簽
@CacheNamespaceRef(DeviceInfoMapper.class) // 標(biāo)記當(dāng)前mapper使用與 DeviceInfoMapper 類相同的namespace
public interface DeviceNameDefinitionMapper {
    ...
}

如果在這個接口中,有增刪改相關(guān)操作, 將會清空指定namespace下的緩存,特別是有關(guān)聯(lián)查詢的幾個mapper,需要在同一個namespace下,防止緩存中的臟數(shù)據(jù)產(chǎn)生

@org.apache.ibatis.annotations.Property
  • 標(biāo)記在接口方法上,用于傳入相關(guān)配置屬性
  • 對應(yīng)mapper.xml文件中的<property></property> 標(biāo)簽
    <cache>
        <property name="cacheSec" value="600"/>
    </cache>

開啟二級緩存

簡單講就是加注解,加配置

  1. springboot項目,如果沒有指定的mybatis.xml配置文件,建議在 yaml或 properties 配置文件中增加配置項:
mybatis:
  configuration:
    cache-enabled: true

如果有 mybatis.xml的配置文件, 在文件中增加配置

<settings>
    <setting name = "cacheEnabled" value = "true" />
</settings>
  1. 對應(yīng)mapper.xml里增加
<!-- type屬性中,指定的緩存的實現(xiàn)類,這里用到自定義實現(xiàn),將緩存放到redis里,以確保分布式服務(wù)緩存一致 -->
<cache type="com.kartist.demo.common.config.MybatisRedisCache"/>
  1. 如果在其他mapper中,有關(guān)聯(lián)查詢的, 在相關(guān)的mapper里增加 <cache-ref/>標(biāo)簽
<!-- namespace為引用的mapper路徑,一個原則 有關(guān)聯(lián)查詢的相關(guān)表,在同一個namespace下,具體是哪個不重要 -->
<cache-ref namespace="com.kartist.demo.worker.dao.BusinessUnitInfoMapper"/>

例如:

<?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.kartist.demo.device.dao.DeviceNameDefinitionMapper">
    <cache type="com.kartist.demo.common.config.MybatisRedisCache"/>
    <cache-ref namespace="com.kartist.demo.device.dao.DeviceInfoMapper"/>
    ...
</mapper>
  1. 在接口上增加注解 @CacheNamespaceRef(DeviceInfoMapper.class)

如果接口上不增加這個注解,在調(diào)用tk.mybatismybatis-plus提供的原生接口時有可能導(dǎo)致緩存中臟數(shù)據(jù)的產(chǎn)生

整合redis

簡單講就是自定義緩存方式,在用的地方指定自定義的緩存方式

  1. 需要實現(xiàn) org.apache.ibatis.cache.Cache 接口
  2. 在開啟二級緩存的地方,指定使用實現(xiàn)類
  • MybatisRedisCache

import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;

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


@Slf4j
public class MybatisRedisCache implements Cache {

    final static String NAME_SPACE ="mybatis-cache:";


    private static RedisTemplate<String, Object> redisTemplate;
    private static int cacheSec;
    private final String id;
    /**
     * The {@code ReadWriteLock}.
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        log.warn("MybatisRedisCache:id=" + id);
        this.id = id;
    }

    public static void setCacheSec(int cacheSec) {
        MybatisRedisCache.cacheSec = cacheSec;
    }

    public static void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        MybatisRedisCache.redisTemplate = redisTemplate;
    }

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

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

    @Override
    public void putObject(Object key, Object value) {
        try {
            log.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject: key=" + key + ",value=" + value);
            if (null != value) {
                if (cacheSec > 0) {
                    // 這里簡單起見, 先固定好了緩存的時長
                    // 也可以嘗試 結(jié)合<property name="cacheSec" value="600"/> 在不同的mapper中指定特殊的緩存時長
                    // 也可以根據(jù)實際業(yè)務(wù)情況,制定緩存策略
                    redisTemplate.opsForValue().set(NAME_SPACE + key.toString(), value, cacheSec, TimeUnit.SECONDS);
                } else {
                    redisTemplate.opsForValue().set(NAME_SPACE + key.toString(), value);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            log.error("redis保存數(shù)據(jù)異常!");
        }
    }

    @Override
    public Object getObject(Object key) {
        try {
            log.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject: key=" + key);
            int size = this.getSize();

            if (null != key) {
                // 這里很坑, 如果選用的redis序列化反序列化的方式不合適,在返回結(jié)果后可能會報類轉(zhuǎn)換異常
                return redisTemplate.opsForValue().get(NAME_SPACE + key.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("redis獲取數(shù)據(jù)異常!");
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        try {
            if (null != key)
                return redisTemplate.delete(NAME_SPACE + key.toString());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("redis獲取數(shù)據(jù)異常!");
        }
        return null;
    }

    @Override
    public void clear() {
        Set<String> keys = redisTemplate.keys(NAME_SPACE + "*");
        if (CollectionUtil.isNotEmpty(keys)) {
            redisTemplate.delete(keys);
        }
        log.debug(">>>>>>>>>>>>>>>>>>>>>>>>clear");
    }

    @Override
    public int getSize() {
        Set<String> keys = redisTemplate.keys(NAME_SPACE + "*");
        if (CollectionUtil.isNotEmpty(keys)) {
            return keys.size();
        }
        return 0;
    }
}

由于需要用到redis,因此還需要寫一個配置,用于注入redisTemplate

@Configuration
public class MybatisRedisCacheConfiguration {

    @Value("${mybatis.cache-sec:600}")
    private int cacheSec;


    @Autowired
    public void config(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置連接工廠
        template.setConnectionFactory(factory);

        // 序列化 這里也可根據(jù)實際情況,使用其他的序列化實現(xiàn)類,
        JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
        template.setValueSerializer(serializer);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 設(shè)置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();

        MybatisRedisCache.setRedisTemplate(template);
        MybatisRedisCache.setCacheSec(cacheSec);
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容