springboot+mybatis-plus二级缓存+redis

利用mybatis的二级缓存自定义到redis,适用于分布式集群应用;当有频繁查询的数据库的需求时,可以减少数据库压力,提升性能。

操作步骤如下:

1.引入mybatis-plus依赖

2.创建MybatisRedisCache类



import config.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;

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



@Slf4j
public class MybatisRedisCache implements Cache {

    private final String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final RedisTemplate<String, Object> redisTemplate;

    // 添加带 String id 参数的构造函数
    public MybatisRedisCache(String id) {
        log.debug("MybatisRedisCache id:{}",id);
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
        // 假设这里从 Spring 上下文中获取 RedisTemplate,在此之前需要确保 Spring 上下文已经初始化
        //这里需要注意获取的是null,需要考虑怎么从spring上下文获取,看启动类的配置
        this.redisTemplate = SpringContextHolder.getBean("redisTemplateMybatis");

    }



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

    @Override
    public void putObject(Object key, Object value) {
        if (key != null && value != null) {
            try {
                String cacheKey = getCacheKey(key);
                redisTemplate.opsForValue().set(cacheKey, value, 60, TimeUnit.MINUTES);
            } catch (Exception e) {
                log.error("Failed to put object into Redis cache", e);
            }
        }
    }

    @Override
    public Object getObject(Object key) {
        if (key != null) {
            try {
                String cacheKey = getCacheKey(key);
                return redisTemplate.opsForValue().get(cacheKey);
            } catch (Exception e) {
                log.error("Failed to get object from Redis cache", e);
            }
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        if (key != null) {
            try {
                String cacheKey = getCacheKey(key);
                Object value = redisTemplate.opsForValue().get(cacheKey);
                redisTemplate.delete(cacheKey);
                return value;
            } catch (Exception e) {
                log.error("Failed to remove object from Redis cache", e);
            }
        }
        return null;
    }

    @Override
    public void clear() {
        try {
            redisTemplate.delete(this.id);//TODO 这里有需要后续可以优化清除粒度,越细越好
        } catch (Exception e) {
            log.error("Failed to clear Redis cache", e);
        }
    }

    @Override
    public int getSize() {
        try {
            return redisTemplate.keys(id + "*").size();
        } catch (Exception e) {
            log.error("Failed to get Redis cache size", e);
            return 0;
        }
    }

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

    private String getCacheKey(Object key) {
        return this.id + ":" + key.toString();
    }



}

3.创建MybatisRedisConfig这个是redis的管理对象

@Configuration
public class MybatisRedisConfig {
    @Bean(name = "redisTemplateMybatis")
    public RedisTemplate<String, Object> redisTemplateMybatis(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 开启默认类型信息,这样在序列化时会保存对象的类型信息
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        // 注册 JavaTimeModule 模块
        om.registerModule(new JavaTimeModule());
        // 禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
//        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;

    }
}

4.创建上下文操作对象,用于读取上面创建的redis管理对象

@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)  // 最高优先级
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
        log.info("setApplicationContext 方法被调用,Spring 上下文已初始化");
    }

    public static <T> T getBean(String name) {
        if (applicationContext == null) {
            log.error("Spring 上下文未初始化,无法获取 Bean: {}", name);
            return null;
        }
        return (T) applicationContext.getBean(name);
    }
}

此处需要注意将启动类配置读取,特别是在maven聚合的时候,可能会出现上下文对象读取redis管理对象为空的情况:

@ComponentScan(basePackages = {
        "com.meeting.dao.config" //SpringContextHolder 类所在包,确保在应用启动的时候加载到
})

5.配置redis存入和读取的序列化方式

@Configuration
public class MybatisRedisConfig {
    @Bean(name = "redisTemplateMybatis")
    public RedisTemplate<String, Object> redisTemplateMybatis(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 开启默认类型信息,这样在序列化时会保存对象的类型信息
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        // 注册 JavaTimeModule 模块
        om.registerModule(new JavaTimeModule());
        // 禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
//        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;

    }
}

.6.在mapper层添加注解@CacheNamespace,这个还有其他参数比如最大存入数量等;需要的话自行研究;

@Mapper
@CacheNamespace(implementation = MybatisRedisCache.class)
public interface MeetingInfoMapper extends BaseMapper<MeetingInfo> {

}

7.最后是开启mybatis二级缓存

mybatis-plus:
  configuration:
    cache-enabled: true

或者在springboot启动类添加注解

@EnableCaching //开启缓存

做完以上步骤后启动项目,观察redis是否存入对象,再次请求后看看是否打印获取对象,建议开启mybatis的日志打印:

mybatis-plus:
  configuration:
    cache-enabled: true
    #mybatis-plus配置控制台打印完整带参数SQL语句
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 日志配置
logging:
  level:
    com.midea.mx.paas.meeting: debug
    org.springframework: warn
    com.baomidou.dynamic.datasource: DEBUG

常见问题:加载顺序导致获取空值,需要调整加载顺序:如下:

1.在启动类配置@MapperScan(basePackages = {"com.xx.dao.mapper"},lazyInitialization = "true"),延后加载mapper层,不至于导致上下文对象还没注入到spring就获取,会为空;
2.升级上下文对象优先加载:在SpringContextHolder上下文对象加上注解@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容