Maison  >  Article  >  Java  >  Explication détaillée des améliorations de la mise en cache et de l'utilisation du cache dans le framework MyBatis de Java

Explication détaillée des améliorations de la mise en cache et de l'utilisation du cache dans le framework MyBatis de Java

高洛峰
高洛峰original
2017-01-23 09:29:261604parcourir

Cache de niveau 1 et cache de niveau 2
MyBatis conçoit le cache de données dans une structure à deux niveaux, divisée en cache de niveau 1 et cache de niveau 2 :
Le cache de niveau 1 est un cache de niveau session, situé dans la base de données principale. L'objet SqlSession de la session est également appelé cache local. Le cache de premier niveau est une fonctionnalité implémentée en interne par MyBatis. Il ne peut pas être configuré par les utilisateurs. Il est automatiquement pris en charge par défaut et les utilisateurs n'ont pas le droit de le personnaliser (mais ce n'est pas absolu. Il peut être modifié en développant le plug. -ins);
Le cache de deuxième niveau est un cache au niveau de l'application. Il a un long cycle de vie, qui est le même que le cycle de déclaration de l'application.
L'organisation du cache de premier niveau et du cache de deuxième niveau dans MyBatis est la suivante :

Explication détaillée des améliorations de la mise en cache et de lutilisation du cache dans le framework MyBatis de Java

Le mécanisme de fonctionnement du cache de premier niveau :
Le cache de premier niveau est le niveau de session Session. De manière générale, un objet SqlSession utilisera un objet Executor pour terminer les opérations de session et l'objet Executor maintiendra un cache Cache pour améliorer les performances des requêtes.
Le mécanisme de fonctionnement du cache de deuxième niveau :
Comme mentionné ci-dessus, un objet SqlSession utilisera un objet Executor pour terminer l'opération de session. La clé du mécanisme de cache de deuxième niveau de MyBatis est de faire des histoires. à propos de cet objet Executor. Si l'utilisateur configure "cacheEnabled=true", alors lorsque MyBatis crée un objet Executor pour l'objet SqlSession, il ajoutera un décorateur à l'objet Executor : CachingExecutor. À ce stade, SqlSession utilise l'objet CachingExecutor pour terminer la demande d'opération. Pour une requête de requête, CachingExecutor déterminera d'abord si la requête de requête a un résultat mis en cache dans le cache secondaire au niveau de l'application. S'il y a un résultat de requête, le résultat mis en cache sera renvoyé directement s'il n'y a pas de résultat mis en cache, il le sera. remis au véritable objet Executor pour terminer la requête. Après l'opération, CachingExecutor placera les résultats de la requête renvoyés par le véritable Executor dans le cache, puis les renverra à l'utilisateur.
Le cache de deuxième niveau de MyBatis est conçu pour être plus flexible. Vous pouvez utiliser la propre implémentation de cache de deuxième niveau définie par MyBatis, vous pouvez également personnaliser le cache en implémentant l'interface org.apache.ibatis.cache.Cache ; utilisez des bibliothèques de mise en cache de mémoire tierces telles que Memcached, etc.

Explication détaillée des améliorations de la mise en cache et de lutilisation du cache dans le framework MyBatis de Java

Explication détaillée des améliorations de la mise en cache et de lutilisation du cache dans le framework MyBatis de Java

Transformation du cache
Problème :
Le problème le plus courant est qu'après l'activation du cache, peu importe si la requête se fait en pagination Quelle que soit la page qui renvoie les données de la première page. De plus, lors de l'utilisation du plug-in de génération automatique SQL pour générer le SQL de la méthode get, les paramètres transmis n'ont aucun effet. Quel que soit le nombre de paramètres transmis, le résultat de la requête du premier paramètre est renvoyé.

Pourquoi ces problèmes surviennent :
Lorsque j'ai expliqué le processus d'exécution de Mybatis auparavant, j'ai mentionné que lorsque le cache est activé, l'exécuteur de Mybatis lira d'abord les données du cache. la base de données à interroger. Le problème réside ici. Le timing d'exécution du plug-in de génération automatique SQL et du plug-in de pagination se trouve dans le gestionnaire d'instructions, et le gestionnaire d'instructions est exécuté après l'exécuteur. in sont implémentés en réécrivant SQL. L'exécuteur génère Lors de la lecture de la clé de cache (la clé est constituée de SQL et des valeurs de paramètres correspondantes), le SQL d'origine est utilisé, donc bien sûr il y aura un problème.

Résolution de problèmes :
Une fois que vous avez trouvé la cause du problème, il devient plus facile de le résoudre. Réécrivez simplement la méthode de génération de clé dans l'exécuteur via l'intercepteur et utilisez le SQL généré automatiquement (correspondant au plug-in de génération automatique SQL) ou ajoutez des informations de pagination (correspondant au plug-in de pagination) lorsque la génération est possible.

Signature de l'intercepteur :

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

Comme le montre la signature, le type de cible à intercepter est Executor (remarque : le type ne peut être configuré que comme un type d'interface), la méthode interceptée est la méthode nommée query.

Implémentation de l'interception :

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;
}

Implémentation du plugin :

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

Explication plus détaillée de MyBatis de Java Pour les articles concernant les améliorations du cache et de l'utilisation du cache dans le framework, veuillez faire attention au site Web PHP chinois !


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn