Level 1 Cache und Level 2 Cache
MyBatis gestaltet den Datencache in einer zweistufigen Struktur, unterteilt in Level 1 Cache und Level 2 Cache:
Level 1 Cache ist ein Cache auf Sitzungsebene, der sich in befindet die primäre Datenbank. Das SqlSession-Objekt der Sitzung wird auch als lokaler Cache bezeichnet. Der First-Level-Cache ist eine intern von MyBatis implementierte Funktion. Er kann nicht von Benutzern konfiguriert werden. Er wird standardmäßig automatisch unterstützt und Benutzer haben nicht das Recht, ihn anzupassen (dies gilt jedoch nicht unbedingt). Er kann durch die Entwicklung eines Plug-Ins geändert werden -ins);
Der Cache der zweiten Ebene ist ein Cache auf Anwendungsebene. Er hat einen langen Lebenszyklus, der dem Deklarationszyklus der Anwendung entspricht.
Die Organisation des Caches der ersten Ebene und des Caches der zweiten Ebene in MyBatis ist wie folgt:
Der Arbeitsmechanismus des Caches der ersten Ebene:
Der Cache der ersten Ebene ist die Sitzungsebene. Im Allgemeinen verwendet ein SqlSession-Objekt ein Executor-Objekt, um Sitzungsvorgänge abzuschließen, und das Executor-Objekt verwaltet einen Cache-Cache, um die Abfrageleistung zu verbessern.
Der Arbeitsmechanismus des Caches der zweiten Ebene:
Wie oben erwähnt, verwendet ein SqlSession-Objekt ein Executor-Objekt, um den Sitzungsvorgang abzuschließen. Der Schlüssel zum Cache-Mechanismus der zweiten Ebene von MyBatis besteht darin, viel Aufhebens zu machen über dieses Executor-Objekt. Wenn der Benutzer „cacheEnabled=true“ konfiguriert, fügt MyBatis beim Erstellen eines Executor-Objekts für das SqlSession-Objekt einen Dekorator zum Executor-Objekt hinzu: CachingExecutor. Zu diesem Zeitpunkt verwendet SqlSession das CachingExecutor-Objekt, um die Vorgangsanforderung abzuschließen. Bei einer Abfrageanforderung ermittelt CachingExecutor zunächst, ob die Abfrageanforderung ein zwischengespeichertes Ergebnis im sekundären Cache auf Anwendungsebene hat. Wenn ein Abfrageergebnis vorhanden ist, wird das zwischengespeicherte Ergebnis direkt zurückgegeben Wird an das reale Executor-Objekt übergeben, um die Abfrage abzuschließen. Nach dem Vorgang legt CachingExecutor die vom realen Executor zurückgegebenen Abfrageergebnisse im Cache ab und gibt sie dann an den Benutzer zurück.
Der Cache der zweiten Ebene von MyBatis ist flexibler gestaltet. Sie können den Cache auch anpassen, indem Sie die Schnittstelle org.apache.ibatis.cache.Cache implementieren Verwenden Sie Speicher-Caching-Bibliotheken von Drittanbietern wie Memcached usw.
Cache-Transformation
Problem:
Das häufigste Problem ist, dass nach dem Einschalten des Caches, egal ob Die Abfrage erfolgt im Paging. Welche Seite auch immer die Daten der ersten Seite zurückgibt. Darüber hinaus haben die übergebenen Parameter keine Auswirkung, wenn das SQL-Plugin zur automatischen Generierung zum Generieren der SQL der get-Methode verwendet wird. Unabhängig davon, wie viele Parameter übergeben werden, wird das Abfrageergebnis des ersten Parameters zurückgegeben.
Warum treten diese Probleme auf:
Als ich zuvor den Ausführungsprozess von Mybatis erläuterte, erwähnte ich, dass der Executor von Mybatis beim Einschalten des Caches zuerst die Daten aus dem Cache liest die abzufragende Datenbank. Das Problem liegt hier. Der Ausführungszeitpunkt des Plug-Ins für die automatische SQL-Generierung und des Paging-Plugins liegt im Anweisungshandler, und der Anweisungshandler wird nach dem Executor ausgeführt. in werden durch Umschreiben von SQL implementiert. Beim Lesen des Cache-Schlüssels (der Schlüssel besteht aus SQL und entsprechenden Parameterwerten) wird das ursprüngliche SQL verwendet, daher wird es natürlich ein Problem geben.
Lösen Sie das Problem:
Sobald Sie die Ursache des Problems gefunden haben, wird es einfacher, es zu lösen. Schreiben Sie einfach die Schlüsselgenerierungsmethode im Executor über den Interceptor neu und verwenden Sie automatisch generiertes SQL (entsprechend dem SQL-Plug-In für die automatische Generierung) oder fügen Sie Paging-Informationen (entsprechend dem Paging-Plug-In) hinzu, wenn die Generierung möglich ist.
Interceptor-Signatur:
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class CacheInterceptor implements Interceptor { ... }
Wie aus der Signatur ersichtlich ist, ist der abzufangende Zieltyp Executor (Hinweis: Der Typ kann nur als konfiguriert werden Schnittstellentyp), die abgefangene Methode ist die Methode namens query.
Intercept-Implementierung:
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 '.*Page$' "); 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-Implementierung:
public Object plugin(Object target) { // 当目标类是CachingExecutor类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的 // 次数 if (target instanceof CachingExecutor) { return Plugin.wrap(target, this); } else { return target; } }
Ausführlichere Erklärung der MyBatis For-Artikel von Java Im Zusammenhang mit Cache- und Cache-Nutzungsverbesserungen im Framework achten Sie bitte auf die chinesische PHP-Website!