一次キャッシュと二次キャッシュ
MyBatis はデータ キャッシュを 2 レベル構造に設計し、一次キャッシュと二次キャッシュに分けます。
一次キャッシュはセッション レベル キャッシュであり、データベース セッションを表す SqlSession オブジェクト (ローカル キャッシュとも呼ばれます)。一次キャッシュは MyBatis によって内部的に実装される機能であり、ユーザーが設定することはできません。デフォルトでは自動的にサポートされており、ユーザーにはカスタマイズする権限がありません (ただし、これは絶対的なものではありません。プラグを開発することで変更できます)。 -ins);
レベル 2 キャッシュ キャッシュはアプリケーション レベルのキャッシュであり、そのライフ サイクルはアプリケーションの宣言サイクルと同じです。つまり、その範囲はアプリケーション アプリケーション全体です。
MyBatis における 1 レベル キャッシュと 2 レベル キャッシュの構成は以下のとおりです:
1 レベル キャッシュの動作メカニズム:
1 レベル キャッシュは通常、セッション レベルです。つまり、SqlSession オブジェクトは Executor オブジェクトを使用してセッション操作を完了し、Executor オブジェクトはクエリのパフォーマンスを向上させるために Cache キャッシュを維持します。
第 2 レベル キャッシュの動作メカニズム:
前述したように、SqlSession オブジェクトはセッション操作を完了するために Executor オブジェクトを使用します。MyBatis の第 2 レベル キャッシュ メカニズムの鍵は、この Executor オブジェクトを扱うことです。ユーザーが「cacheEnabled=true」を設定すると、MyBatis が SqlSession オブジェクトの Executor オブジェクトを作成するときに、Executor オブジェクトにデコレータ (CachingExecutor) が追加されます。このとき、SqlSession は CachingExecutor オブジェクトを使用して操作リクエストを完了します。クエリ リクエストの場合、CachingExecutor はまずクエリ リクエストがアプリケーション レベルの 2 次キャッシュにキャッシュされた結果があるかどうかを判断し、クエリ結果が存在する場合はキャッシュされた結果を直接返します。操作が完了すると、CachingExecutor は実際の Executor オブジェクトに渡されて、実際の Executor から返されたクエリ結果をキャッシュに格納し、ユーザーに返します。
MyBatis の 2 次キャッシュは、より柔軟になるように設計されています。MyBatis 独自の定義された 2 次キャッシュ実装を使用することもできます。また、org.apache.ibatis.cache.Cache インターフェイスを実装することによって、キャッシュをカスタマイズすることもできます。サードパーティのメモリ キャッシュ ライブラリ (Memcached など)
キャッシュ変換
問題:
最も一般的な問題は、キャッシュがオンになった後、ページング クエリ中にどのページがクエリされたとしても、データの最初のページが返されることです。また、SQL 自動生成プラグインを使用して get メソッドの SQL を生成する場合、渡されたパラメータは何個渡されても影響を受けず、最初のパラメータのクエリ結果が返されます。
なぜこれらの問題が発生するのか:
以前 Mybatis の実行プロセスを説明したときに、キャッシュを有効にするという前提の下で、Mybatis 実行プログラムはまずキャッシュからデータを読み取り、次にデータベースにアクセスしてクエリを実行すると述べました。読み取れない場合。問題は、SQL 自動生成プラグインとページング プラグインの実行タイミングがステートメント ハンドラー内にあり、ステートメント ハンドラーが SQL 自動生成プラグインとページング プラグインの両方の後に実行されることです。実行プログラムはSQLを書き換えて実装しています。キャッシュキー(キーはSQLとそれに対応するパラメータ値で構成されています)を読み取る際には、元のSQLが使用されるため、当然問題が発生します。
問題解決:
問題の原因が見つかると、解決が容易になります。インターセプタを介してエグゼキュータ内の鍵生成メソッドを書き換えて、自動生成されたSQL(SQL自動生成プラグインに相当)を使用するか、生成可能な場合はページング情報を追加(ページングプラグインに相当)するだけです。
インターセプターのシグネチャ:
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class CacheInterceptor implements Interceptor { ... }
シグネチャからわかるように、インターセプトされるターゲットのタイプは Executor (注: タイプはインターフェイス タイプとしてのみ設定できます) であり、インターセプト メソッドは query という名前のメソッドです。
インターセプトの実装:
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; }
プラグインの実装:
public Object plugin(Object target) { // 当目标类是CachingExecutor类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的 // 次数 if (target instanceof CachingExecutor) { return Plugin.wrap(target, this); } else { return target; } }
Java の MyBatis フレームワークにおけるキャッシュとキャッシュ使用量の改善の詳細な説明については、PHP の中国語 Web サイトに注目してください。