先说两句:

  我们都知道Mybatis缓存分两类: 一级缓存(同一个Session会话内) & 二级缓存(基于HashMap实现的以 namespace为范围的缓存)

  今天呢, 我们不谈一级缓存, 我们来谈一谈 二级缓存, 通过查看Mybatis源码发现, 他的二级缓存实现真的十分简单, 默认的实现类是 org.apache.ibatis.cache.impl.PerpetualCache 这里贴一下他的源码吧:

  

/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.cache.impl; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock; import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException; /**
* @author Clinton Begin
*/
public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) {
this.id = id;
} @Override
public String getId() {
return id;
} @Override
public int getSize() {
return cache.size();
} @Override
public void putObject(Object key, Object value) {
cache.put(key, value);
} @Override
public Object getObject(Object key) {
return cache.get(key);
} @Override
public Object removeObject(Object key) {
return cache.remove(key);
} @Override
public void clear() {
cache.clear();
} @Override
public ReadWriteLock getReadWriteLock() {
return null;
} @Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
} Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
} @Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
} }

PerpetualCache.java

  那么既然他都已经有一个实现了, 我们为什么还要自定义实现呢?

  原因很简单, 默认实现是(HashMap)本地缓存, 不支持分布式缓存, 而我们现在大多数项目都是以集群的方式部署, 这种情况下, 使用本地缓存会出现很严重的脏读问题, 特定情况下更新可直接导致数据不一致的问题.

  如果让缓存实现支持分布式呢? 方案有很多, 基本围绕着NoSQL数据库实现, 最常用的就是Redis了, 接下来我们就来用Redis实现自定义缓存类

  说干就干吧, 开始我们的自定义缓存实现之路

实现过程:

  首先: Mybatis自定义实现缓存类的配置方式十分简单, 我们只需要开启二级缓存, 并在 mapper.xml 中修改如下配置:

  

  其中 type 属性值就是我们自定义的缓存实现辣!

  接下来我们来看一看实现类内部是怎么写的:

  

package com.cardgame.demo.game.component.db;

import com.cardgame.demo.component.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import static com.cardgame.demo.game.component.core.config.Constants.REDIS_KEY_GAME; /**
* @author yjy
* 2018-08-06 15:46
*/
@Slf4j
public class MybatisRedisCache implements Cache { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private String id; public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("id can not be null");
}
log.debug("MyBatisRedisCache:id=" + id);
this.id = id;
} @Override
public String getId() {
return this.id;
} @Override
public int getSize() {
int size = (int) RedisUtils.getInstance().getHashSizeObj(getKey());
log.debug("MybatisCache getSize > {}", size);
return size;
} @Override
public void putObject(Object key, Object value) {
RedisUtils.getInstance().setHashObj(getKey(), key, value);
log.debug("MybatisCache put > key: {}, val: {}", key, value);
}
@Override
public Object getObject(Object key) {
Object val = RedisUtils.getInstance().getHashObj(getKey(), key);
log.debug("MybatisCache get > key: {}, val: {}", key, val);
return val;
} @Override
public Object removeObject(Object key) {
// 移除指定缓存
Object obj = RedisUtils.getInstance().getHashObj(getKey(), key);
RedisUtils.getInstance().delHashObj(getKey(), key);
log.debug("MybatisCache remove > key: {}, val: {}", key, obj);
return obj;
} @Override
public void clear() {
// 清除所有缓存
RedisUtils.getInstance().delObj(getKey());
log.debug("MybatisCache clear > hashKey : {}", getKey());
} @Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
} protected String getKey() {
return REDIS_KEY_GAME + "mybatis_cache_" + id;
} }

MybatisRedisCache

  自定义缓存实现的要求很低, 只需要 实现 org.apache.ibatis.cache.Cache 就可以了. 这里我们用到了 RedisUtils 工具类, 还请同学们 对应到自己的项目中的工具类.

  至此: 我们的Mybatis二级缓存就支持分布式啦!

问题来临:

  今天我偶然想起, 是否需要给二级缓存 设置一个过期时间?

  我们来想一下不设置过期时间会有什么问题:

    当有一天我们不得不手动修改数据库的数据时(别问为啥要动数据库, 因为我在本地测试需要修改), 如果相应的 namespace 没有 插入和更新操作, 那么他的缓存将一直有有效, 然后查出来的数据一直是修改前的数据, 而且我们使用的Redis做的缓存, 即使重启了系统缓存依然还是在, 只能从redis中找到指定缓存并清除, 实在是头疼

  有没有办法解决呢?

  你可能已经想到了, cache标签不是支持 flushInterval 属性的吗? 设置一个 flushInterval = "10000", 这样不就行了吗?

  我开始也是这么认为的, 直到有一天我发现这个设置完全没有起作用, 缓存一直是有效的, 那问题就来了, 为什么呢?

  百思不得其解的我决定去瞄一瞄Mybatis的源码, 最终让我发现了其中的奥秘, 我们来看一下下面的代码:

  

  这是Mybatis初始化二级缓存中的一段代码, 我们可以看到, flushInterval 属性对于自定义实现类是不起作用的, 而 Mybatis 实现的缓存过期时间的原理则是利用 设计模式(装饰器模式) 在默认的 缓存实现类上封装了一层 ScheduleCache, 也正是此类实现了缓存的有效期设置.

  完了完了, 那不能设置咋办啊, 这不是坑爹吗?

  冷静下来, 先别忙. 既然 不能通过设置解决, 那我们就自己想办法解决吧

  既然你Mybatis不帮我封装 ScheduleCache, 那我就自己封装一个 ScheduleRedisCache, 原有的MybatisRedisCache不变. 我们看一下 ScheduleRedisCache的代码:

  

package com.cardgame.demo.game.component.db;

import com.cardgame.demo.component.redis.RedisUtils;
import org.apache.ibatis.cache.Cache; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import static com.cardgame.demo.game.component.core.config.Constants.REDIS_KEY_GAME; /**
*
* 二级缓存 过期时间 实现
*
* @author yjy
* 2018-08-13 13:21
*/
public class ScheduleRedisCache implements Cache { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private String id;
private long flushInterval = 10000; // 缓存刷新时间, 单位毫秒
private Cache delegate; // 委派缓存类 public ScheduleRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("id can not be null");
}
this.id = id;
this.delegate = new MybatisRedisCache(id);
} @Override
public String getId() {
return id;
} @Override
public void putObject(Object key, Object value) {
// 记录过期时间
long timeout = System.currentTimeMillis() + flushInterval;
RedisUtils.getInstance().setHashObj(getKey(), key, timeout);
delegate.putObject(key, value);
} @Override
public Object getObject(Object key) {
Object timeout = RedisUtils.getInstance().getHashObj(getKey(), key);
// if 未过期
if (timeout != null && (long) timeout > System.currentTimeMillis()) {
// 更新过期时间
RedisUtils.getInstance().setHashObj(getKey(), key, System.currentTimeMillis() + flushInterval);
return delegate.getObject(key);
}
return null;
} @Override
public Object removeObject(Object key) {
RedisUtils.getInstance().delHashObj(getKey(), key);
return delegate.removeObject(key);
} @Override
public void clear() {
RedisUtils.getInstance().delObj(getKey());
delegate.clear();
} @Override
public int getSize() {
return (int) RedisUtils.getInstance().getHashSizeObj(getKey());
} @Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
} protected String getKey() {
return REDIS_KEY_GAME + "schedule_cache_" + id;
} }

ScheduleRedisCache.java

  代码简单易懂, 说白了就是增加一个 缓存管理, 专门 缓存二级缓存的 过期时间. 在适当的时候 更新过期时间 / 清除过期时间

  需要注意的是, 既然我们封装了 MybatisRedisCache, 那么 mapper.xml 中就需要 改一改了, 如下:

  

  

  这样, 我们就实现了 二级缓存的超时自动过期功能了!!!

结论: 谢谢大家!666

  

  

Mybatis自定义分布式二级缓存实现与遇到的一些问题解决方案!的更多相关文章

  1. Mybatis使用Redis二级缓存

    在Mybatis中允许开发者自定义自己的缓存,本文将使用Redis作为Mybatis的二级缓存.在Mybatis中定义二级缓存,需要如下配置: 1. MyBatis支持二级缓存的总开关:全局配置变量参 ...

  2. 【MyBatis学习13】MyBatis中的二级缓存

    1. 二级缓存的原理 前面介绍了,mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的.为了更加 ...

  3. 分布式二级缓存组件实战(Redis+Caffeine实现)

    前言 在生产中已有实践,本组件仅做个人学习交流分享使用.github:https://github.com/axinSoochow/redis-caffeine-cache-starter 个人水平有 ...

  4. Mybatis一级、二级缓存

      Mybatis一级.二级缓存   一级缓存 首先做一个测试,创建一个mapper配置文件和mapper接口,我这里用了最简单的查询来演示. <mapper namespace="c ...

  5. Spring + MySQL + Mybatis + Redis【二级缓存】

    一.Redis环境 Redis 官网 :http://redis.io/ windows下载:https://github.com/dmajkic/redis/downloads 1.文件解压缩 2. ...

  6. mybatis整合redis二级缓存

    mybatis默认开启了二级缓存功能,在mybatis主配置文件中,将cacheEnabled设置成false,则会关闭二级缓存功能 <settings> <!--二级缓存默认开启, ...

  7. MyBatis 一、二级缓存和自定义缓存

    1.一级缓存 ​ MyBatis 默认开启了一级缓存,一级缓存是在SqlSession 层面进行缓存的.即,同一个SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一 ...

  8. (转)MyBatis 一、二级缓存和自定义缓存

    1.一级缓存 MyBatis 默认开启了一级缓存,一级缓存是在SqlSession 层面进行缓存的.即,同一个SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数, 只会进行一次 ...

  9. MyBatis笔记——EhCache二级缓存

    介绍 ehcache是一个分布式缓存框架. 我们系统为了提高系统并发,性能.一般对系统进行分布式部署(集群部署方式)  不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统开发.所以要使用分布式缓 ...

随机推荐

  1. Git——开启区分大小写

    前言 默认情况下git是忽略区分大小写的,多人合作的情况下不规范很容易造成问题,所以开启区分大小写. 步骤 开启 全局开启 git config --global core.ignorecase fa ...

  2. 使用Python操作MongoDB

    MongoDB简介(摘自:http://www.runoob.com/mongodb/mongodb-intro.html) MongoDB 由C++语言编写,是一个基于分布式文件存储的开源数据库系统 ...

  3. HDU - 3247 Resource Archiver (AC自动机,状压dp)

    \(\quad\)Great! Your new software is almost finished! The only thing left to do is archiving all you ...

  4. LVS负载均衡DR模式实现

    LVS负载均衡之DR模式配置 DR 模式架构图: 操作步骤 实验环境准备:(centos7平台) 所有服务器上配置 # systemctl stop firewalld //关闭防火墙 # sed - ...

  5. Redisson实现分布式锁

    转: Redisson实现分布式锁 Redisson文档参考:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95 redis是实现 ...

  6. Java基础 -- 深入理解Java类型信息(Class对象)与反射机制

    一 RTTI概念 认识Claa对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RT ...

  7. day049--jQuery文档操作示例

    DOM操作(CRUD增改查删) 创建元素 $('span') // 创建一个span标签 后置插入操作   append(), appendTo() <!DOCTYPE html> < ...

  8. JS学习笔记Day20

    一. 1.服务器和客户端 客户端 程序: 通过浏览器直接运行 服务器 程序: 通过安装某种服务器软件   程序才可以运行              apache   php文件             ...

  9. 结构体addrinfo, sockaddr, sockaddr_in的区别

    struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址. 一.sockaddr sockaddr在头文件#include <sys/socket. ...

  10. P4180 严格次小生成树[BJWC2010] Kruskal,倍增

    题目链接\(Click\) \(Here\). 题意就是要求一个图的严格次小生成树.以前被题面吓到了没敢做,写了一下发现并不难. 既然要考虑次小我们就先考虑最小.可以感性理解到一定有一种次小生成树,可 ...