mybatis 细粒度控制二级缓存
本文要解决的问题:细粒度控制mybatis的二级缓存。mybatis的二级缓存的问题:当更新SQL执行时只清除当前SQL所在命名空间(namespace)的缓存。如果存在2个命名空间namespaceA和namespaceB,当namespaceA下执行更新操作时,namespaceB的缓存并不会清除。正常的情况下,这种策略是不会有问题。但是,如果存在关联查询时就有可能出现问题了,例如namespaceA关联namespaceB,当namespaceB执行更新时清除namespaceB的缓存,这是查询namespaceA时,会导致查询出来的结果是缓存的,即数据不是最新的。步骤如下:
1. 开启mybatis二级缓存
<settings>
<setting name="cacheEnabled" value="true" />
</settings>2. 加入SQL分析的jar依赖
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.4</version>
</dependency>3. 复制如下拦截器代码
package com.xyz.core.inctercepter; import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties; import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.util.TablesNamesFinder; /**
* 缓存控制拦截器
*
* @author lee
* @since 2016年3月17日
*/
@Intercepts({
@Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
@Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class }) })
public class CachingInterceptor implements Interceptor {
private final static Logger logger = LoggerFactory.getLogger(CachingInterceptor.class);
/**
* 表名关联的命名空间<br/>
* 一个table下关联的namespace
*/
private final static Map<String, Map<String, String>> tableLinks = new HashMap<String, Map<String, String>>(); /**
* 记录已经解析过的SQL,确保一条SQL只被解析一次,提高效率
*/
private final static Map<String, String> dealedMap = new HashMap<String, String>(); public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
BoundSql boundSql = ms.getBoundSql(args[1]);
if (!ms.getConfiguration().isCacheEnabled() || ms.getCache() == null)
return invocation.proceed();
if (dealedMap.containsKey(ms.getId()) && dealedMap.get(ms.getId()).equals(boundSql.getSql())) {
return invocation.proceed();
}
dealedMap.put(ms.getId(), boundSql.getSql()); final String operate = invocation.getMethod().getName();
final List<String> tableNames = getTableList(boundSql.getSql());
final String namespace = ms.getCache().getId();
logger.debug("当前操作SQL中包含的namespace:" + namespace);
if ("query".equals(operate)) {
deal(namespace, tableNames);
} else {
Configuration configuration = ms.getConfiguration();
clearCache(tableNames, configuration.getMappedStatements());
} return invocation.proceed();
} public Object plugin(Object target) {
return Plugin.wrap(target, this);
} public void setProperties(Properties properties) {
} /**
* 一个namespace包含多个SQL<br/>
* 一条SQL语句包含多个table
*
* @param namespace
* @param tableNames
*/
private void deal(final String namespace, final List<String> tableNames) {
if (tableNames == null || tableNames.size() == 0)
return; for (String tableName : tableNames) {
Map<String, String> namespaces = tableLinks.get(tableName);
if (namespaces == null) {
namespaces = new HashMap<String, String>();
namespaces.put(namespace, namespace);
tableLinks.put(tableName, namespaces);
} else if (!namespaces.containsKey(namespace)) {
namespaces.put(namespace, namespace);
}
}
} /**
* 清除缓存
*
* @param mappedStatments
*/
@SuppressWarnings("rawtypes")
private void clearCache(List<String> tableNames, Collection mappedStatments) {
if (tableNames == null || tableNames.isEmpty())
return;
for (String tableName : tableNames) {
Map<String, String> namespaces = tableLinks.get(tableName);
if (namespaces == null)
continue;
for (String namespaceNeedClearKey : namespaces.keySet()) {
for (Object o : mappedStatments) {
if (o instanceof MappedStatement) {
MappedStatement sta = ((MappedStatement) o);
final String namespace = sta.getCache().getId();
if (namespaceNeedClearKey.equals(namespace)) {
logger.debug("命名空间[{}]的缓存被清除", namespace);
sta.getCache().clear();
break;
}
}
}
}
}
} /**
* 解析SQL中包含的表名
*
* @param sql
* @return
*/
public List<String> getTableList(final String sql) {
try {
Statement statement = CCJSqlParserUtil.parse(sql);
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> tableList = null;
if (statement instanceof Select) {
Select selectStatement = (Select) statement;
tableList = tablesNamesFinder.getTableList(selectStatement);
} else if (statement instanceof Update) {
Update updateStatement = (Update) statement;
tableList = tablesNamesFinder.getTableList(updateStatement);
} else if (statement instanceof Insert) {
Insert insertStatement = (Insert) statement;
tableList = tablesNamesFinder.getTableList(insertStatement);
} else if (statement instanceof Delete) {
Delete deleteStatement = (Delete) statement;
tableList = tablesNamesFinder.getTableList(deleteStatement);
}
logger.debug("SQL:{}中包含的表名:" + tableList, sql);
return tableList;
} catch (JSQLParserException e) {
logger.error("解析sql异常:" + sql, e);
}
return null;
}
}4. 配置mybatis拦截器
<plugins>
<plugin interceptor="com.xyz.core.inctercepter.CachingInterceptor" />
</plugins>
mybatis 细粒度控制二级缓存的更多相关文章
- Mybatis一级、二级缓存
Mybatis一级.二级缓存 一级缓存 首先做一个测试,创建一个mapper配置文件和mapper接口,我这里用了最简单的查询来演示. <mapper namespace="c ...
- Spring + MySQL + Mybatis + Redis【二级缓存】
一.Redis环境 Redis 官网 :http://redis.io/ windows下载:https://github.com/dmajkic/redis/downloads 1.文件解压缩 2. ...
- 【MyBatis学习13】MyBatis中的二级缓存
1. 二级缓存的原理 前面介绍了,mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的.为了更加 ...
- Mybatis使用Redis二级缓存
在Mybatis中允许开发者自定义自己的缓存,本文将使用Redis作为Mybatis的二级缓存.在Mybatis中定义二级缓存,需要如下配置: 1. MyBatis支持二级缓存的总开关:全局配置变量参 ...
- mybatis整合redis二级缓存
mybatis默认开启了二级缓存功能,在mybatis主配置文件中,将cacheEnabled设置成false,则会关闭二级缓存功能 <settings> <!--二级缓存默认开启, ...
- mybatis 学习五 二级缓存不推荐使用
mybatis 二级缓存不推荐使用 一 mybatis的缓存使用. 大体就是首先根据你的sqlid,参数的信息自己算出一个key值,然后你查询的时候,会先把这个key值去缓存中找看有没有value,如 ...
- Spring + MySQL + Mybatis + Redis【二级缓存】执行流程分析
一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就 ...
- mybatis(4)_二级缓存深入_使用第三方ehcache配置二级缓存
增删改对二级缓存的影响 1.增删改也会清空二级缓存 2.对于二级缓存的清空实质上是对value清空为null,key依然存在,并非将Entry<k,v>删除 3.从DB中进行select查 ...
- MyBatis 一、二级缓存和自定义缓存
1.一级缓存 MyBatis 默认开启了一级缓存,一级缓存是在SqlSession 层面进行缓存的.即,同一个SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一 ...
随机推荐
- hive --service metastore 出现的问题
Could not create ServerSocket on address 0.0.0.0/0.0.0.0:9083 执行命令jps root@hadoopm:/usr# jps1763 Res ...
- Win10 锁屏图片 路径
Win10锁屏图片非常漂亮,下面是获得这些图片的方法: 一. 找到这个路径 C:\Users\UserName\AppData\Local\Packages\Microsoft.Windows.Con ...
- 怎样使java程序减少内存占用(转载)
本文收集网上关于减少java程序占用的一些小知识点 (1)别用new Boolean(). 在很多场景中Boolean类型是必须的,比如JDBC中boolean类型的set与get都是通过Boolea ...
- sqlite3 命令
然后使用下列操作打开并进入数据库 1 2 3 $./adb shell $cd sdcard/path/subdir $sqlite3 dsxniubility.db 终端内进入数据库一般操作也就是 ...
- sed 技巧
八.流编辑器sed sed ':a;N;$!ba;s/0.01/0.0001/g' file:a 创建一个labelaN 将下一行读入到模式空间$! 如果不是最后一行,ba跳转到label a处s/0 ...
- .NET 串口通信中断接收,包含0X1A(作为EOF)
.NET串口通信中将`0X1A`当做EOF处理,.NET接收到EOF会触发一次接收中断,此时事件形参`SerialDataReceivedEventArgs`值为枚举 `Eof`,其他为`Chars` ...
- composer [ReflectionException] Class Fxp\Composer\AssetPlugin\Repository\NpmRepository does not exist
在执行composer update时报错 [ReflectionException]Class Fxp\Composer\AssetPlugin\Repository\NpmRepository d ...
- mongo3.2
arbiter配置文件 processManagement: fork: true net: bindIp: 172.16.10.1,127.0.0.1 port: storage: dbPath: ...
- vulcan测试记录
感觉这个游戏很赞,是六个里面最喜欢的一个了 1.有时候挖坑对于位置要求比较大? 2.感觉难度比较大,尤其是玩到第三关很考验啊(不过从另一个方面来说也是优点?) 3.玩到现在对于怪物吃金子的原理没有很懂 ...
- sql存储过程异常捕获并输出例子还有不输出过程里面判断异常 例子
编程的异常处理很重要,当然Sql语句中存储过程的异常处理也很重要,明确的异常提示能够快速的找到问题的根源,节省很多时间. 下面,我就以一个插入数据为例来说明Sql Server中的存储过程怎么捕获异常 ...