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 premier niveau et cache de deuxième niveau:
Le cache de niveau 1 est un cache au niveau de session de session, situé dans l'objet SQLSession représentant une session de base de données, et est également appelé cache local. Le cache de niveau 1 est une fonctionnalité implémentée en interne par MyBatis. Les utilisateurs ne peuvent pas le configurer et le prendre en charge automatiquement par défaut. Les utilisateurs n'ont pas le droit de le personnaliser (mais ce n'est pas absolu, et ils peuvent être modifiés via des plug-ins de développement);
Le cache de deuxième niveau est un cache au niveau de l'application, qui a un long cycle de vie, le même que le cycle d'application de déclaration, ce qui signifie que sa portée de fonction est l'ensemble de l'application d'application.
L'organisation du cache de premier niveau et du cache de deuxième niveau dans Mybatis est illustrée dans la figure ci-dessous:
Mécanisme de travail de la mise en cache de premier niveau:
Le cache de niveau 1 est le niveau de session de session. D'une manière générale, un objet SQLSession utilisera un objet exécuteur pour effectuer les opérations de session. L'objet exécuteur entretiendra un cache de cache pour améliorer les performances de la requête.
Mécanisme de travail de la mise en cache secondaire:
Comme mentionné ci-dessus, un objet SQLSession utilisera un objet exécuteur pour terminer l'opération de session. La clé du mécanisme de mise en cache secondaire de Mybatis est de faire des histoires sur cet objet exécuteur. Si l'utilisateur a configuré "Cacheenabled = true", lorsque MyBatis crée un objet exécuteur pour l'objet SQLSession, il ajoutera un décorateur à l'objet exécuteur: CachingExecutor. À l'heure actuelle, SQLSession utilise l'objet CachingExecutor pour compléter la demande d'opération. Pour les demandes de requête, CachingExecutor déterminera d'abord si la demande de requête a mis en cache les résultats dans le cache secondaire au niveau de l'application. S'il y a un résultat de requête, il renverra directement les résultats mis en cache; S'il n'y a pas de cache, il sera remis à l'objet exécuteur réel pour terminer l'opération de requête. Après cela, CachingExecutor placera le résultat de la requête renvoyé par l'exécuteur réel dans le cache, puis le renverra à l'utilisateur.
Le cache secondaire de Mybatis est conçu pour être plus flexible. Vous pouvez utiliser l'implémentation de cache secondaire définie par MyBatis; Vous pouvez également personnaliser le cache en implémentant l'interface org.apache.ibatis.cache.cache; Vous pouvez également utiliser des bibliothèques de cache de mémoire tierces, telles que Memcached, etc.
Transformation du cache
question:
Le problème le plus courant est qu'après l'ouverture du cache, les données de la première page seront renvoyées à la page lors de l'interrogation. De plus, lorsque vous utilisez le plug-in SQL Automatic Generation pour générer SQL pour la méthode GET, les paramètres passés ne fonctionnent pas. Quels que soient les paramètres passés, le résultat de la requête du premier paramètre est retourné.
Pourquoi ces problèmes se produisent:
Lors de l'explication du processus d'exécution de MyBatis auparavant, il a été mentionné que sous la prémisse d'activer le cache, l'exécuteur de MyBatis lira d'abord les données du cache et n'acceptera que la base de données pour interroger si elle ne peut pas être lue. Le problème réside ici. Le temps d'exécution du plug-in de génération automatique SQL et le plug-in Paging se trouve dans le statement Handleur, et le statement Handleur est exécuté après l'exécuteur testamentaire. Que le plug-in de génération automatique SQL et le plug-in Paging soient implémentés en réécrivant SQL, l'exécuteur exécuteur utilise SQL d'origine lors de la génération et de la lecture de la touche de cache (la clé se compose de SQL et de valeurs de paramètres correspondantes), donc bien sûr, il y a un problème.
Résolvez le problème:
Une fois la cause du problème trouvé, il sera pratique de le résoudre. Il suffit de remplacer la méthode de génération de clés dans l'exécuteur 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) lors de la génération.
Signature d'interceptor:
@Intercepts ({@ signature (type = exécutor.class, méthode = "query", args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class})}) classe publique CacheInterceptor implémente l'intercepteur {...}Comme on peut le voir à partir de la signature, le type cible à intercepter est exécuteur (note: le type ne peut être configuré que comme type d'interface), et la méthode d'interception est une méthode nommée Query.
Mise en œuvre de l'interception:
Public Object Intercept (Invocation Invocation) lève le throwable {exécutor ExecutorProxy = (exécuteur) invocation.getTarget (); MetaObject metaexecUtor = metaObject.ForObject (EMIRGETORPROXY, default_object_factory, default_object_wrapper_factory); // sépare la chaîne d'objets proxy while (metaexecutor.hasgetter ("h")) {objet objet = metaexecUtor.getValue ("h"); metaexecUtor = metaObject.forObject (objet, default_object_factory, default_object_wrapper_factory); } // La classe cible qui sépare le dernier objet proxy while (metaexecutor.hasgetter ("cible")) {objet objet = metaexecUtor.getValue ("Target"); metaexecUtor = metaObject.forObject (objet, default_object_factory, default_object_wrapper_factory); } Objet [] args = invocation.getArgs (); Renvoyez ce.Query (MetaexecUtor, Args); } public <e> list <e> Query (metaObject MetaexecUtor, Object [] args) lève Sqlexception {maptedStatement ms = (maptedstatement) args [0]; Object ParameterObject = Args [1]; RowBounds RowBounds = (Rowbounds) args [2]; Resulthandler Resulthandler = (Resulthandler) args [3]; BOUNDSQL BOURNSQL = MS.GetBoundSQL (ParameterObject); // réécrivez la génération de cache cachekekey cachekey = createCacheKey (ms, parameterObject, rowbounds, boundsql); Executor Executor = (Execupor) metaexecUtor.getoriginalObject (); return Executor.Query (MS, ParameterObject, RowBounds, Resulthandler, CacheKey, BoundsQL); } Private CacheKey CreateCacheKey (maptedStatement MS, Object ParameterObject, RowBounds Rowbounds, BoundsQL BoundsQL) {Configuration Configuration = Ms.GetConfiguration (); pagesqlid = configuration.getVariables (). getProperty ("pagesqlid"); if (null == pagesqlid || "" .equals (pagesqlid)) {logger.warn ("La propriété pagesqlid n'est pas définie, utilise default '. * page $'"); pagesqlid = defaultPagesQLid; } CacheKey cacheKey = new cacheKey (); cacheKey.update (Ms.GetId ()); cacheKey.update (rowbounds.getoffset ()); cachekey.update (rowbounds.getLIMIT ()); List <paramètres de paramètres> Paramètres de paramètres = boundsql.getParameTermAppings (); // résolvez le bug qui génère automatiquement SQL, et l'instruction SQL est vide, ce qui fait que la clé génére des erreurs 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 (paramètreObject); } Sqlsource sqlsource = buildSQLSource (configuration, newsql, paramètreObject.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 (ParametMapping Parametermapping: ParameTermAppings) {String PropertyName = ParameTermapping.getProperty (); if (metaObject.HasGetter (propriétéName)) {cacheKey.update (metaObject.getValue (propriétéName)); } else if (boundsql.hasadditionalParameter (propriétéName)) {cacheKey.update (boundsql.getAdditionalParameter (PropertyName)); }}}} // Lorsqu'une requête de pagination est requise, ajoutez la page actuelle et le nombre de pages par page dans le paramètre de la page au cachekey if (Ms.getId (). Matchs (pagesqlid) && metaObject.Hasgetter ("page")) {pageParamètre page = (pageParamètre) MetaObject.GetValue ("page"); if (null! = page) {cacheKey.update (page.getCurrentPage ()); cacheKey.update (page.getPageSize ()); }} return cacheKey; } Implémentation du plugin:
Plugin d'objet public (cible d'objet) {// Lorsque la classe cible est de type cachingExECUTOR, la classe cible est enveloppée, sinon il reviendra directement à la cible elle-même, réduisant le nombre de fois que la cible est procassée if (instance cible de cachingExecutor) {return plugin.wrap (cible, this); } else {return Target; }}