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<String, Object>) 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只需要这样配置
<?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.zsy.mapper.CarouselMapper"> <cache-ref namespace="com.zsy.mapper.CarouselMapper"/> </mapper>
Mapper接口使用注解
@Repository @CacheNamespace(implementation=MybatisRedisCache.class,eviction=MybatisRedisCache.class) public interface CarouselMapper extends BaseMapper<Carousel> { }
注意:
当我们重新配置过 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