Home  >  Article  >  Java  >  Detailed explanation of caching and cache usage improvements in Java's MyBatis framework

Detailed explanation of caching and cache usage improvements in Java's MyBatis framework

高洛峰
高洛峰Original
2017-01-23 09:29:261551browse

First-level cache and second-level cache
MyBatis designs the data cache into a two-level structure, divided into first-level cache and second-level cache:
The first-level cache is a session-level cache, located in the primary database. The SqlSession object of the session is also called the local cache. The first-level cache is a feature implemented internally by MyBatis. It cannot be configured by users. It is automatically supported by default and users do not have the right to customize it (but this is not absolute, and it can be modified by developing plug-ins);
The second-level cache is an Application-level cache. It has a long life cycle, which is the same as the Application's declaration cycle. That is to say, its scope is the entire Application application.
The organization of the first-level cache and the second-level cache in MyBatis is as shown below:

Detailed explanation of caching and cache usage improvements in Javas MyBatis framework

The working mechanism of the first-level cache:
The first-level cache is the Session session Level, generally speaking, a SqlSession object will use an Executor object to complete session operations, and the Executor object will maintain a Cache cache to improve query performance.
The working mechanism of the second-level cache:
As mentioned above, a SqlSession object will use an Executor object to complete the session operation. The key to MyBatis's second-level cache mechanism is to make a fuss about this Executor object. If the user configures "cacheEnabled=true", then when MyBatis creates an Executor object for the SqlSession object, it will add a decorator to the Executor object: CachingExecutor. At this time, SqlSession uses the CachingExecutor object to complete the operation request. For a query request, CachingExecutor will first determine whether the query request has a cached result in the application-level secondary cache. If there is a query result, it will directly return the cached result; if there is no cached result, it will then be handed over to the real Executor object to complete the query. After the operation, CachingExecutor will place the query results returned by the real Executor into the cache, and then return them to the user.
MyBatis's second-level cache is designed to be more flexible. You can use MyBatis's own defined second-level cache implementation; you can also customize the cache by implementing the org.apache.ibatis.cache.Cache interface; you can also use third-party memory Caching libraries such as Memcached, etc.

Detailed explanation of caching and cache usage improvements in Javas MyBatis framework

Detailed explanation of caching and cache usage improvements in Javas MyBatis framework

Cache transformation
Problem:
The most common problem is that after the cache is turned on, no matter whether the query is paging Whichever page returns the data of the first page. In addition, when using the SQL automatic generation plug-in to generate the SQL of the get method, the parameters passed in have no effect. No matter how many parameters are passed in, the query result of the first parameter is returned.

Why these problems occur:
When I explained the execution process of Mybatis before, I mentioned that under the premise of turning on the cache, the executor of Mybatis will first read the data from the cache. Go to the database to query. The problem lies here. The execution timing of sql automatic generation plug-in and paging plug-in is in statementhandler, and statementhandler is executed after executor. Both sql automatic generation plug-in and paging plug-in are realized by rewriting sql. The executor is generating When reading the cache key (the key consists of SQL and corresponding parameter values), the original SQL is used, so of course there will be a problem.

Solve the problem:
After you find the cause of the problem, it will be easier to solve it. Just rewrite the key generation method in the executor through the interceptor, and use automatically generated sql (corresponding to the sql automatic generation plug-in) or add paging information (corresponding to the paging plug-in) when the generation is possible.

Interceptor signature:

@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class CacheInterceptor implements Interceptor {
...
}

It can be seen from the signature that the target type to be intercepted is Executor (note: type can only be configured as an interface type), interception The method is the method named query.

Intercept implementation:

public Object intercept(Invocation invocation) throws Throwable {
    Executor executorProxy = (Executor) invocation.getTarget();
    MetaObject metaExecutor = MetaObject.forObject(executorProxy, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
    // 分离代理对象链
    while (metaExecutor.hasGetter("h")) {
      Object object = metaExecutor.getValue("h");
      metaExecutor = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
    }
    // 分离最后一个代理对象的目标类
    while (metaExecutor.hasGetter("target")) {
      Object object = metaExecutor.getValue("target");
      metaExecutor = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
    }
    Object[] args = invocation.getArgs();
    return this.query(metaExecutor, args);
  }
  
  public <E> List<E> query(MetaObject metaExecutor, Object[] args) throws SQLException {
    MappedStatement ms = (MappedStatement) args[0];
    Object parameterObject = args[1];
    RowBounds rowBounds = (RowBounds) args[2];
    ResultHandler resultHandler = (ResultHandler) args[3];
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 改写key的生成
    CacheKey cacheKey = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    Executor executor = (Executor) metaExecutor.getOriginalObject();
    return executor.query(ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql);
  }
  
  private CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    Configuration configuration = ms.getConfiguration();
    pageSqlId = configuration.getVariables().getProperty("pageSqlId");
    if (null == pageSqlId || "".equals(pageSqlId)) {
      logger.warn("Property pageSqlId is not setted,use default &#39;.*Page$&#39; ");
      pageSqlId = defaultPageSqlId;
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // 解决自动生成SQL,SQL语句为空导致key生成错误的bug
    if (null == boundSql.getSql() || "".equals(boundSql.getSql())) {
      String id = ms.getId();
      id = id.substring(id.lastIndexOf(".") + 1);
      String newSql = null;
      try {
        if ("select".equals(id)) {
          newSql = SqlBuilder.buildSelectSql(parameterObject);
        }
        SqlSource sqlSource = buildSqlSource(configuration, newSql, parameterObject.getClass());
        parameterMappings = sqlSource.getBoundSql(parameterObject).getParameterMappings();
        cacheKey.update(newSql);
      } catch (Exception e) {
        logger.error("Update cacheKey error.", e);
      }
    } else {
      cacheKey.update(boundSql.getSql());
    }
  
    MetaObject metaObject = MetaObject.forObject(parameterObject, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
  
    if (parameterMappings.size() > 0 && parameterObject != null) {
      TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
      if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
        cacheKey.update(parameterObject);
      } else {
        for (ParameterMapping parameterMapping : parameterMappings) {
          String propertyName = parameterMapping.getProperty();
          if (metaObject.hasGetter(propertyName)) {
            cacheKey.update(metaObject.getValue(propertyName));
          } else if (boundSql.hasAdditionalParameter(propertyName)) {
            cacheKey.update(boundSql.getAdditionalParameter(propertyName));
          }
        }
      }
    }
    // 当需要分页查询时,将page参数里的当前页和每页数加到cachekey里
    if (ms.getId().matches(pageSqlId) && metaObject.hasGetter("page")) {
      PageParameter page = (PageParameter) metaObject.getValue("page");
      if (null != page) {
        cacheKey.update(page.getCurrentPage());
        cacheKey.update(page.getPageSize());
      }
    }
    return cacheKey;
}

plugin implementation:

public Object plugin(Object target) {
  // 当目标类是CachingExecutor类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的
  // 次数
  if (target instanceof CachingExecutor) {
    return Plugin.wrap(target, this);
  } else {
    return target;
  }
}

More detailed explanation of caching in Java's MyBatis framework For articles related to improving the use of cache, please pay attention to the PHP Chinese website!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn