MyBatis Plus整合Redis实现分布式二级缓存的问题

 更新时间:2023年11月15日 09:43:09   作者:爱码猿  
Mybatis内置的二级缓存在分布式环境下存在分布式问题,无法使用,但是我们可以整合Redis来实现分布式的二级缓存,这篇文章给大家介绍MyBatis Plus整合Redis实现分布式二级缓存,感兴趣的朋友跟随小编一起看看吧

MyBatis缓存描述

MyBatis提供了两种级别的缓存, 分别时一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,只在SqlSession对象内部存储缓存数据,如果SqlSession对象不一样就无法命中缓存,二级缓存是mapper级别的缓存,只要使用的Mapper类一样就能够共享缓存。

在查询数据时,Mybatis会优先查询二级缓存,如果二级缓存没有则查询一级缓存,都没有才会进行数据库查询。

Mybatis的一级缓存默认是开启的,而二级缓存需要在mapper.xml配置文件内或通过@CacheNamespace注解手动开启。

需要注意的是,在于Spring进行整合时,必须开启事务一级缓存会生效,因为不开启缓存的话每次查询都会重新创建一个SqlSession对象,因此无法共享缓存。

通过@CacheNamespace开启某个Mapper的二级缓存。

@Mapper
@CacheNamespace 
public interface EmployeeMapper extends BaseMapper<Employee> {
}

开启所有的二级缓存:

mybatis-plus:
    mapper-locations: classpath:mybatis/mapper/*.xml
    configuration:
      cache-enabled: true

MybatisPlus整合Redis实现分布式二级缓存

Mybatis内置的二级缓存在分布式环境下存在分布式问题,无法使用,但是我们可以整合Redis来实现分布式的二级缓存。

1.引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.4.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.24.3</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.22</version>
</dependency>

2.配置RedisTemplate

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfiguration {
    private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
    private static final GenericJackson2JsonRedisSerializer JACKSON__SERIALIZER = new GenericJackson2JsonRedisSerializer();
    @Bean
    @Primary
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        //设置缓存过期时间
        RedisCacheConfiguration redisCacheCfg = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(STRING_SERIALIZER))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(JACKSON__SERIALIZER));
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheCfg)
                .build();
    }
    @Bean
    @Primary
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        // key序列化
        redisTemplate.setKeySerializer(STRING_SERIALIZER);
        // value序列化
        redisTemplate.setValueSerializer(JACKSON__SERIALIZER);
        // Hash key序列化
        redisTemplate.setHashKeySerializer(STRING_SERIALIZER);
        // Hash value序列化
        redisTemplate.setHashValueSerializer(JACKSON__SERIALIZER);
        // 设置支持事务
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    @Bean
    public RedisSerializer<Object> redisSerializer() {
        //创建JSON序列化器
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }
}

3.自定义缓存类

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
@Slf4j
public class MybatisRedisCache implements Cache {
    // redisson 读写锁
    private final RReadWriteLock redissonReadWriteLock;
    // redisTemplate
    private final RedisTemplate redisTemplate;
    // 缓存Id
    private final String id;
    //过期时间 10分钟
    private final long expirationTime = 1000*60*10;
    public MybatisRedisCache(String id) {
        this.id = id;
        //获取redisTemplate
        this.redisTemplate = SpringUtil.getBean(RedisTemplate.class);
        //创建读写锁
        this.redissonReadWriteLock = SpringUtil.getBean(RedissonClient.class).getReadWriteLock("mybatis-cache-lock:"+this.id);
    }
    @Override
    public void putObject(Object key, Object value) {
        //使用redis的Hash类型进行存储
        redisTemplate.opsForValue().set(getCacheKey(key),value,expirationTime, TimeUnit.MILLISECONDS);
    }
    @Override
    public Object getObject(Object key) {
        try {
            //根据key从redis中获取数据
            Object cacheData = redisTemplate.opsForValue().get(getCacheKey(key));
            log.debug("[Mybatis 二级缓存]查询缓存,cacheKey={},data={}",getCacheKey(key), JSONUtil.toJsonStr(cacheData));
            return cacheData;
        } catch (Exception e) {
            log.error("缓存出错",e);
        }
        return null;
    }
    @Override
    public Object removeObject(Object key) {
        if (key != null) {
            log.debug("[Mybatis 二级缓存]删除缓存,cacheKey={}",getCacheKey(key));
            redisTemplate.delete(key.toString());
        }
        return null;
    }
    @Override
    public void clear() {
        log.debug("[Mybatis 二级缓存]清空缓存,id={}",getCachePrefix());
        Set keys = redisTemplate.keys(getCachePrefix()+":*");
        redisTemplate.delete(keys);
    }
    @Override
    public int getSize() {
        Long size = (Long) redisTemplate.execute((RedisCallback<Long>) RedisServerCommands::dbSize);
        return size.intValue();
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.redissonReadWriteLock;
    }
    @Override
    public String getId() {
        return this.id;
    }
    public String getCachePrefix(){
        return "mybatis-cache:%s".formatted(this.id);
    }
    private String getCacheKey(Object key){
        return getCachePrefix()+":"+key;
    }
}

4.Mapper接口上开启二级缓存

//开启二级缓存并指定缓存类
@CacheNamespace(implementation = MybatisRedisCache.class,eviction = MybatisRedisCache.class)
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

到此这篇关于MyBatis Plus整合Redis实现分布式二级缓存的文章就介绍到这了,更多相关MyBatis Plus整合Redis二级缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot集成mybatis-plus全过程

    springboot集成mybatis-plus全过程

    本文详细介绍了如何在SpringBoot环境下集成MyBatis-Plus,包括配置maven依赖、application.yaml文件、创建数据库和Java实体类、Mapper层、Service层和Controller层的设置,同时,还涵盖了时间自动填充、分页查询、多对一和一对多的数据库映射关系设置
    2024-09-09
  • JavaSE实战之酒店订房系统的实现

    JavaSE实战之酒店订房系统的实现

    这篇文章主要为大家详细介绍了如何利用JavaSE实现酒店订房系统,文中的示例代码讲解详细,对我们学习JavaSE开发有一定的帮助,需要的可以参考一下
    2022-07-07
  • JAVA接入DeepSeek满血版的详细指南(问答接入、流式接入双模式)

    JAVA接入DeepSeek满血版的详细指南(问答接入、流式接入双模式)

    硅基流动推出了功能完备的DeepSeek满血版,然而众多用户在尝试接入大型模型时仍面临诸多挑战,特别是在流式接入方面,今天,我将引领大家通过Java实现双模式接入DeepSeek满血版,需要的朋友可以参考下
    2025-03-03
  • Java源码分析:Guava之不可变集合ImmutableMap的源码分析

    Java源码分析:Guava之不可变集合ImmutableMap的源码分析

    今天给大家带来的是关于Java源码的相关知识,文章围绕着Java ImmutableMap展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下,希望能给你带来帮助
    2021-06-06
  • 利用spring的拦截器自定义缓存的实现实例代码

    利用spring的拦截器自定义缓存的实现实例代码

    这篇文章主要介绍了利用spring的拦截器自定义缓存的实现实例代码,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • java web在高并发和分布式下实现订单号生成唯一的解决方案

    java web在高并发和分布式下实现订单号生成唯一的解决方案

    这篇文章主要介绍了java web在高并发和分布式下实现订单号生成唯一的解决方案,需要的朋友可以参考下
    2017-11-11
  • Swing常用组件之文本框和文本区

    Swing常用组件之文本框和文本区

    这篇文章主要为大家详细介绍了Swing常用组件之文本框(JTestField)和文本区(JTextArea),Swing是一个用于开发Java应用程序用户界面的开发工具包,本文开始带大家学习Swing
    2016-05-05
  • SpringBoot+STOMP协议实现私聊、群聊

    SpringBoot+STOMP协议实现私聊、群聊

    本文将结合实例代码,介绍SpringBoot+STOMP协议实现私聊、群聊,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-06-06
  • 重新实现hashCode()方法

    重新实现hashCode()方法

    hashCode()是Java中的一个重要方法,用于计算对象的哈希码。本文介绍了如何重新实现hashCode()方法,包括使用对象的属性计算哈希码、使用字符串拼接计算哈希码、使用随机数计算哈希码等方法。同时,还介绍了如何避免哈希冲突,提高哈希表的效率。
    2023-04-04
  • Spring Cloud Feign报错问题解决

    Spring Cloud Feign报错问题解决

    这篇文章主要介绍了Spring Cloud Feign报错问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12

最新评论