Redis实现Mybatis的二级缓存

一、Mybatis 缓存机制

1、一级缓存:

√ \color{#FF7D00}{√}  默认开启,存在于 SqlSession 的生命周期中,Mybatis 会把执行的方法和参数通过算法生成缓存的键值对,并和查询结果存入一个 map 中,如果同一个 SqlSession 中执行的方法和参数完全一致时,通过算法生成相同的键值对,并在 map 中查找发现已存在,则会返回缓存中的对象。任何 insert、update、delete 操作都会清空一级缓存。

√ \color{#FF7D00}{√}  一般的我们将 Mybatis 和 Spring 整合时,mybatis-spring 包会自动分装 sqlSession,而 Spring通过动态代理 sqlSessionProxy 使用一个模板方法封装了 select() 等操作,每一次 select() 查询都会自动先执行 openSession(), 执行完 close() 以后调用 close() 方法,相当于生成了一个新的 session 实例,所以我们无需手动的去关闭这个 session(),当然也无 法使用 mybatis 的一级缓存,也就是说 mybatis 的一级缓存在 spring 中是没有作用的。

2、二级缓存:

√ \color{#FF7D00}{√}  二级缓存存在于 SqlSessionFactory 的生命周期中,可以理解为跨 SqlSession 的,缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。

√ \color{#FF7D00}{√}  setting 参数 cacheEnabled,这个参数是二级缓存的全局开关,默认值是 true,如果把这个参数设置为 false,即使有后面的二级缓存配置,也不会生效。

√ \color{#FF7D00}{√}  要开启二级缓存,需要在 sql 映射文件中添加配置:

<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true" />

● \color{#FF7D00}{●}   映射语句文件中的所有 select 语句会被缓存;

● \color{#FF7D00}{●}   映射语句文件中的所有 insert、update、delete 语句会刷新缓存;

● \color{#FF7D00}{●}   缓存会使用 LRU (最近最少使用)算法来回收;

● \color{#FF7D00}{●}   60000ms 刷新间隔,存储 1024 个引用数目(不论查询返回什么);

● \color{#FF7D00}{●}   可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false 。

√ \color{#FF7D00}{√}   在集群环境下,使用自带的二级缓存只是针对单个的节 点,所以我们采用分布式的二级缓存功能(redis)。

二、Redis 实现 Mybatis 的二级缓存

2、实现 cache 接口
public enum CacheManager {
    /**
     * singleton instance
     */
    INSTANCE;
    private final ConcurrentHashMap<String, RedisCache> cacheMap = new ConcurrentHashMap<>(64);

    /**
     * register redis cache obj
     *
     * @param redisCache
     * @return
     */
    public RedisCache register(RedisCache redisCache) {
        return this.cacheMap.put(redisCache.getId(), redisCache);
    }
}
public class RedisCache implements Cache {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisCache.class);
    /**
     * 读写锁
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 缓存实例id
     */
    private final String id;
    private RedisTemplate redisTemplate;

    /**
     * 缓存过期时间,目前为6个小时
     */
    private static final long EXPIRE_TIME_IN_MINUTES = 60 * 6;

    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        LOGGER.debug("Redis Cache id " + id);
        this.id = id;
        CacheManager.INSTANCE.register(this);
    }

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

    /**
     * Put query result to redis
     *
     * @param key
     * @param value
     */
    @Override
    public void putObject(Object key, Object value) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            ValueOperations opsForValue = redisTemplate.opsForValue();
            opsForValue.set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
            LOGGER.debug("Put query result to redis");
        } catch (Throwable t) {
            LOGGER.error("Redis put failed", t);
        }
    }

    /**
     * Get cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        try {

            RedisTemplate redisTemplate = getRedisTemplate();
            ValueOperations opsForValue = redisTemplate.opsForValue();
            Object o = opsForValue.get(key.toString());
            if (o == null) {
                LOGGER.debug("Redis get failed, fail over to db");
            } else {
                LOGGER.debug("Get cached query result from redis,key:" + key + " value:" + o.toString());
            }
            return o;
        } catch (Throwable t) {
            LOGGER.error("Redis get failed, fail over to db", t);
            return null;
        }
    }

    /**
     * Remove cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object removeObject(Object key) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            redisTemplate.delete(key.toString());
            LOGGER.debug("Remove cached query result from redis");
        } catch (Throwable t) {
            LOGGER.error("Redis remove failed", t);
        }
        return null;
    }

    /**
     * Clears this cache instance
     */
    @Override
    public void clear() {
        try {
            String pattern = "*:" + this.id + "*";
            delete(pattern);
        } catch (Exception e) {
            LOGGER.error("clear:{}", e.getMessage(), e);

        }
        LOGGER.info("Clear all the cached query result from redis");
    }

    public boolean delete(String pattern) {
        RedisCallback<Boolean> redisCallback = connection -> {
            try {
                ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions();
                scanOptionsBuilder.match(pattern);
                ScanOptions scanOptions = scanOptionsBuilder.build();
                Cursor<byte[]> cursor = connection.scan(scanOptions);
                while (cursor.hasNext()) {
                    connection.del(cursor.next());
                }
                return true;
            } catch (Exception e) {
                return false;
            }
        };
        Object result = getRedisTemplate().execute(redisCallback);
        if (result != null) {
            return (boolean) result;
        }
        return false;
    }


    /**
     * This method is not used
     *
     * @return
     */
    @Override
    public int getSize() {
        return 0;
    }

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

    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            synchronized (this) {
                if (redisTemplate == null) {
                    redisTemplate = (RedisTemplate) SpringUtil.getBean("dbRedisTemplate");
                }
            }

        }
        return redisTemplate;
    }

}
/**
 * 重写redis序列化,支持数据库二级缓存,配置ObjectMapper,
 *
 * @author zilong
 **/
@Configuration(value = "DbMapperRedisConfig")
@EnableCaching // 开启缓存支持
public class DbCacheConfig extends CachingConfigurerSupport {

	/**
	 * RedisTemplate配置(数据库版)
	 *
	 * @param lettuceConnectionFactory
	 * @return
	 */
	@Bean
	public RedisTemplate<String, Object> dbRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
		// 序列化配置
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
		om.activateDefaultTyping(om.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(om);
		// 配置redisTemplate
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
		redisTemplate.setConnectionFactory(lettuceConnectionFactory);
		RedisSerializer<?> stringSerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(stringSerializer);
		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
		redisTemplate.setHashKeySerializer(stringSerializer);
		redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}

	/**
	 * 缓存配置管理器
	 *
	 * @param factory
	 * @return
	 */
	@Bean
	public CacheManager cacheManager(LettuceConnectionFactory factory) {
        // 配置序列化(缓存默认有效期 6小时)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(6));
        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                												.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
		// 以锁写入的方式创建RedisCacheWriter对象
		//RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
		// 创建默认缓存配置对象
		/* 默认配置,设置缓存有效期 1小时*/
		//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
		/* 自定义配置test:demo 的超时时间为 5分钟*/
		RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration)
				.withInitialCacheConfigurations(singletonMap(CacheConstant.CSYJ_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)).disableCachingNullValues()))
				.transactionAware().build();
		return cacheManager;
	}

}
2、Mapper 开启二级缓存配置

√ \color{#FF7D00}{√}  @CacheNamespace

● \color{#FF7D00}{●}   作用于 mapper 接口上面,是用来实现二级缓存的,如果不指定实现类的话,默认就是 PerpetualCache(实际上就是hashmap实现的)。

● \color{#FF7D00}{●}   只适用于注解或者 tk.mapper,对 xml 无效。并且不能和 xml 搭配用。

√ \color{#FF7D00}{√}  @CacheNamespaceRef

● \color{#FF7D00}{●}   可以和 xml 搭配使用。需要指明 name 或者 value 。

● \color{#FF7D00}{●}   Xml配置缓存

<cache type=”xxx.MybatisCache”></cache>

√ \color{#FF7D00}{√}  @Options(useCache = false)

● \color{#FF7D00}{●}   个别方法不使用缓存

代码地址: h t t p s : / / g i t e e . c o m / z i l o n g 123666 / c a b i n e t . g i t \color{red}{代码地址:https://gitee.com/zilong123666/cabinet.git} 代码地址:https://gitee.com/zilong123666/cabinet.git

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_tt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值