注意点:
在最新的3.x版本,实现二级缓存的配置也有了一些改变。
官方建议在service使用缓存,但是你也可以直接在mapper层缓存,这里的二级缓存就是直接在Mapper层进行缓存操作
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;
}
}
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);
}
}
@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