本文要解决的问题:细粒度控制mybatis的二级缓存。mybatis的二级缓存的问题:当更新SQL执行时只清除当前SQL所在命名空间(namespace)的缓存。如果存在2个命名空间namespaceA和namespaceB,当namespaceA下执行更新操作时,namespaceB的缓存并不会清除。正常的情况下,这种策略是不会有问题。但是,如果存在关联查询时就有可能出现问题了,例如namespaceA关联namespaceB,当namespaceB执行更新时清除namespaceB的缓存,这是查询namespaceA时,会导致查询出来的结果是缓存的,即数据不是最新的。步骤如下:

    1. 开启mybatis二级缓存

  1. <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 细粒度控制二级缓存的更多相关文章

  1. Mybatis一级、二级缓存

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

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

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

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

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

  4. Mybatis使用Redis二级缓存

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

  5. mybatis整合redis二级缓存

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

  6. mybatis 学习五 二级缓存不推荐使用

    mybatis 二级缓存不推荐使用 一 mybatis的缓存使用. 大体就是首先根据你的sqlid,参数的信息自己算出一个key值,然后你查询的时候,会先把这个key值去缓存中找看有没有value,如 ...

  7. Spring + MySQL + Mybatis + Redis【二级缓存】执行流程分析

    一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就 ...

  8. mybatis(4)_二级缓存深入_使用第三方ehcache配置二级缓存

    增删改对二级缓存的影响 1.增删改也会清空二级缓存 2.对于二级缓存的清空实质上是对value清空为null,key依然存在,并非将Entry<k,v>删除 3.从DB中进行select查 ...

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

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

随机推荐

  1. android必须要进行为不同分辨率设备切图

    以分辨率为1920×1080的android设备为例.在项目中加载资源的位置为xxhdpi文件夹: 例如将图片放入mdpi文件夹中就会出现,图片的横纵尺寸分别乘3被的后果,因为它认为在这个文件夹中是低 ...

  2. 使用天天模拟器开发Android应用

    自带的模拟器太慢,Genymotion配置过于复杂,天天模拟器旧版本直接可用于调试,由于新版本的天大模拟器端口号被修改为6555,要想用于开发,需要使用ADB命令进行连接. 下载天天模拟器 天天模拟器 ...

  3. 如何整合最新的Flex sdk和Air sdk。

    使用Flex来开发air应用,如果想使用最新sdk的话,配置起来还真是一个麻烦事儿. Flex捐给apache维护了,air还是adobe自己维护,那么就得分别到这两边下载最新的sdk然后自己整合起来 ...

  4. 面向过程 vs 面向对象

    从网上摘录了一些面向过程vs.面向对象的分析,先简单记录如下,稍后会继续整理. 为什么会出现面向对象分析方法? 因为现实世界太复杂多变,面向过程的分析方法无法实现. 面向过程 采用面向过程必须了解整个 ...

  5. (转)SVN服务器搭建和使用(二)

    上一篇介绍了VisualSVN Server和TortoiseSVN的下载,安装,汉化.这篇介绍一下如何使用VisualSVN Server建立版本库,以及TortoiseSVN的使用. 首先打开Vi ...

  6. AD中各层的说明

    PCB各层说明: 1.丝印层(OverLay,Silkscreen):有顶层丝印和底层丝印.用来画器件轮廓,器件编号和一些图案等. 2.信号层(SignalLayer):对于两层板,主要是TopLay ...

  7. JS学习之路

    前言 接触js也有四五年的时间了,对js的认识也逐渐加深,想把自己这几年学习js的经历记录一下. 总则-都是对象,都是引用 在接触js前用的比较多的是java,在刚开始接触js的时候,老实讲,我是有点 ...

  8. C#利用AxImp工具在WPF中使用OCX控件

    一.注册OCX并利用工具生成dll @echo off color a ::Failed REGSVR32 /S /I "MSCOMCTL.OCX" if exist %windi ...

  9. (转)学习使用Jmeter做压力测试(一)--压力测试基本概念

    一.性能测试的概念 性能测试是通过自动化的测试工具模拟多种正常峰值及异常负载条件来对系统的各项性能指标进行测试.负载测试和压力测试都属于性能测试,两者可以结合进行. 通过负载测试,确定在各种工作负载下 ...

  10. c#-SimHash匹配相似-算法

    使用场景:Google 的 simhash 算法 //通过大量测试,simhash用于比较大文本,比如500字以上效果都还蛮好,距离小于3的基本都是相似,误判率也比较低. //从我的经验,如果我们假定 ...