• mybatis 细粒度控制二级缓存


      本文要解决的问题:细粒度控制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>


    一介书生:关注多线程、高并发、分布式、微服务和系统架构。
  • 相关阅读:
    Android如何防止apk程序被反编译
    Android的十六进制颜色值
    popupwindow使用之异常:unable to add window -- token null is not valid
    布局文件中fill_parent和match_parent有什么区别?
    Android:Layout_weight的深刻理解
    页面的五种布局以及嵌套『Android系列八』
    禁止危险函数
    表单令牌防止重复提交原理
    【ThinkPHP框架3.2版本学习总结】四、视图
    【ThinkPHP框架3.2版本学习总结】三、模型
  • 原文地址:https://www.cnblogs.com/leeyazhou/p/5333702.html
Copyright © 2020-2023  润新知