Mybatis、Mybatis-Plus二级缓存使用

Mybatis、Mybatis-Plus二级缓存使用

  • 注意点:

  • 在最新的3.x版本,实现二级缓存的配置也有了一些改变。

  • 官方建议在service使用缓存,但是你也可以直接在mapper层缓存,这里的二级缓存就是直接在Mapper层进行缓存操作

    Mybatis的二级缓存实现也十分简单,只要在springboot的配置文件打开二级缓存,即

    mybatis-plus:
      configuration:
        cache-enabled: true
    

    缓存接口的实现

    package com.qsmam.weixin.config;
    import com.qsmam.weixin.util.SpringUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.ibatis.cache.Cache;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import java.util.Set;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * \* Created with IntelliJ IDEA.
     * \* User: 一颗小土豆
     * \* Date: 2020/7/23
     * \* Time: 10:53
     * \* 配置mybatis二级缓存 保存到redis
     */
    @Slf4j
    public class MybatisRedisCache implements Cache {
        //读写锁
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
    	//RedisTemplate
        private RedisTemplate<String,Object> redisTemplate;
    
        {
            //RedisTemplate不能使用自动注入,所以使用工具类 BeanFactoryPostProcessor获得
            this.redisTemplate = SpringUtils.getBean(RedisTemplate.class);
        }
    
        private String id;
    
        public MybatisRedisCache(final String id){
            if (id == null){
                throw new IllegalArgumentException("Cache instances require an ID");
            }
            this.id = id;
        }
    
        @Override
        public String getId() {
            return this.id;
        }
    
        @Override
        public void putObject(Object key, Object value) {
            if (redisTemplate == null){
                //犹豫启动期间注入失败,只能运行期间注入,这段代码可以删除
                //redisTemplate = (RedisTemplate) ApplicationContextRegister.getApplicationContext().getBean("RedisTemplate");
            }
            if (value != null){
                redisTemplate.opsForValue().set(key.toString(),value);
            }
        }
    
        @Override
        public Object getObject(Object key) {
            try {
                return redisTemplate.opsForValue().get(key.toString());
            }catch (Exception e){
                log.error("缓存出错!");
                log.error(e.getMessage());
            }
            return null;
        }
    
        /**
         * 删除指定的缓存
         * @param key
         * @return
         */
        @Override
        public Object removeObject(Object key) {
            if (key!=null)
            redisTemplate.delete(key.toString());
            return null;
        }
    
        /**
         * 删除全部 缓存
         */
        @Override
        public void clear() {
            log.debug("清楚缓存");
            if (redisTemplate!=null){
                Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
                if (!CollectionUtils.isEmpty(keys)){
                    redisTemplate.delete(keys);
                }
            }
        }
    
        @Override
        public int getSize() {
            return (int) redisTemplate.execute(new RedisCallback() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.dbSize();
                }
            });
        }
    
        @Override
        public ReadWriteLock getReadWriteLock() {
            return this.readWriteLock;
        }
    }
    

    Spring工具类 方便在非spring管理环境中获取bean

    package com.qsmam.weixin.util;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * \* Created with IntelliJ IDEA.
     * \* User: 一颗小土豆
     * \* Date: 2020/7/23
     * \* Time: 11:57
     * \spring工具类 方便在非spring管理环境中获取bean
     */
    @Component
    public class SpringUtils implements BeanFactoryPostProcessor {
        /** Spring应用上下文环境 */
        private static ConfigurableListableBeanFactory beanFactory;
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            SpringUtils.beanFactory = configurableListableBeanFactory;
        }
        /**
         * 获取对象
         *
         * @param name
         * @return Object 一个以所给名字注册的bean的实例
         * @throws org.springframework.beans.BeansException
         *
         */
        @SuppressWarnings("unchecked")
        public static <T> T getBean(String name) throws BeansException
        {
            return (T) beanFactory.getBean(name);
        }
    
        /**
         * 获取类型为requiredType的对象
         *
         * @param clz
         * @return
         * @throws org.springframework.beans.BeansException
         *
         */
        public static <T> T getBean(Class<T> clz) throws BeansException
        {
            T result = (T) beanFactory.getBean(clz);
            return result;
        }
    
        /**
         * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
         *
         * @param name
         * @return boolean
         */
        public static boolean containsBean(String name)
        {
            return beanFactory.containsBean(name);
        }
    
        /**
         * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
         *
         * @param name
         * @return boolean
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
        {
            return beanFactory.isSingleton(name);
        }
    
        /**
         * @param name
         * @return Class 注册对象的类型
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
        {
            return beanFactory.getType(name);
        }
    
        /**
         * 如果给定的bean名字在bean定义中有别名,则返回这些别名
         *
         * @param name
         * @return
         * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
         *
         */
        public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
        {
            return beanFactory.getAliases(name);
        }
    }
    
    

    mapper.xml文件声明缓存,这里3.x只需要这样配置

    
    
    
        
    
    
    Mapper接口使用注解
    @Repository
    @CacheNamespace(implementation=MybatisRedisCache.class,eviction=MybatisRedisCache.class)
    public interface CarouselMapper extends BaseMapper {
    }
    

注意:

当我们重新配置过 RedisTemplate 并且交给spring进行管理的时候就需要制定一个主Bean,

方法:使用@Primary注解

@Primary

package com.qsmam.weixin.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * \* Created with IntelliJ IDEA.
 * \* User: 一颗小土豆
 * \* Date: 2020/7/1
 * \* Time: 17:14
 * \
 */
@Configuration
public class SpringWebConfig {

    /**
     * 配置reids 序列化 防止乱码等问题
     * @param redisConnectionFactory
     * @return
     */
    @Primary //使用@Primary修饰的bean优先级会更高
    @Bean("redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(redisConnectionFactory);
        //使用JSON格式的序列化,保存
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

    //自定义cacheManager缓存管理器
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory)
    {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ZERO)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

若果不指定就会出现以下的错误:↓↓↓↓↓↓

***************************
APPLICATION FAILED TO START
***************************

Description:

file [E:\码云项目\easy_mom_service_number\weixin\target\classes\com\qsmam\weixin\mapper\security\UserInfoMapper.class] required a single bean, but 2 were found: 需要一个单例的Bean,但是发现了2个
	- redisTemplate: defined by method 'redisTemplate' in class path resource [com/qsmam/weixin/config/SpringWebConfig.class]
	- stringRedisTemplate: defined by method 'stringRedisTemplate' in class path resource [org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.class]

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

感谢大佬:云扬四海、Mark_LiuLiu

你可能感兴趣的:(mybatis,mybatis-plus)