Mybaitsソースコード分析(5)第1レベルのキャッシュと第2レベルのキャッシュの詳細な説明
はじめに:前回の記事ではmybaitsデータソースについて説明しましたが、この記事ではmybaitsの第1レベルのキャッシュ、第2レベルのキャッシュ、および主な実装の基本的な使用法について説明しました。
この記事は主に次の部分に分かれています。
第1レベルのキャッシュと第2レベルのキャッシュの使用とテスト
mybaitsキャッシュ関連クラスの概要
詳細なレベル1キャッシュ
詳細な第2レベルのキャッシュ
1.第1レベルのキャッシュと第2レベルのキャッシュの使用とテスト
mybaitsでは、第1レベルのキャッシュがデフォルトで有効になり、キャッシュのライフサイクルがsqlsessionレベルになり、第2レベルのキャッシュのグローバル構成がデフォルトで有効になりますが、使用するには名前空間でも有効にする必要があります。 2番目のレベルのキャッシュ、2番目のレベルのキャッシュライフサイクルはsqlsessionFactoryであり、キャッシュ操作の範囲は、各マッパーがキャッシュに対応することです(これが、マッパーによって構成された名前空間で有効にする必要がある理由です)
1. SqlMapperに次の構成を追加します。cacheEnabledは第2レベルのキャッシュをオンにする役割を果たし、logImplはSQLを出力する役割を果たします(実際のSQLを印刷するかどうかに応じてキャッシュがなくなったかどうかをテストできます)
<settings>
<!--打印执行sql用
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 默认为true -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.テスト
/**
* 一级缓存测试 :
* 测试前,需要加下面logImpl这个打印sql语句的配置
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/> // 是为了打印sql语句用途
<setting name="cacheEnabled" value="true"/> // 二级缓存默认就是开启的
</settings>
测试结果,会发出一次sql语句,一级缓存默认开启,缓存生命周期是SqlSession级别
*/
@Test
public void test2() throws Exception {
InputStream in = Resources.getResourceAsStream("custom/sqlMapConfig3.xml");
SqlSessionFactory factory2 = new SqlSessionFactoryBuilder().build(in);
SqlSession openSession = factory2.openSession();
UserMapper mapper = openSession.getMapper(UserMapper.class);
User user1 = mapper.findUserById(40); // 会发出sql语句
System.out.println(user1);
User user2 = mapper.findUserById(40); // 会发出sql语句
System.out.println(user2);
openSession.close();
}
/**
* 二级缓存测试
* 二级缓存全局配置默认开启,但是需要每个名称空间配置<cache></cache>,
* 即需要全局和局部同时配置,缓存生命周期是SqlSessionFactory级别。
*/
@Test
public void test3() throws Exception {
InputStream in = Resources.getResourceAsStream("custom/sqlMapConfig3.xml");
SqlSessionFactory factory2 = new SqlSessionFactoryBuilder().build(in);
SqlSession openSession = factory2.openSession();
UserMapper mapper = openSession.getMapper(UserMapper.class);
User user1 = mapper.findUserById(40);
System.out.println(user1);
openSession.close(); // 关闭session
openSession = factory2.openSession();
mapper = openSession.getMapper(UserMapper.class);
User user3 = mapper.findUserById(40); // 二级缓存全局和局部全部开启才会打印sql
System.out.println(user3);
}
テスト結果から、mybaitsが第2レベルのキャッシュを開かない場合、同じsqlsessionを持つクエリが再度実行されることがわかります。クローズがない場合は、第1レベルのキャッシュがチェックされます。レベルキャッシュがオンになっている、第2レベルのキャッシュがオンになっている場合、クエリが閉じている場合、キャッシュは引き続き使用できます(第2レベルのキャッシュが使用されます)、キャッシュメカニズムは次のとおりです。レベルキャッシュが最初にチェックされ、第1レベルのキャッシュが見つからず、データベースが再度チェックされることはありません。データベースは最初に第1レベルのキャッシュで検出され、次に第2レベルのキャッシュに配置されます。 2番目のクエリでは、最初に2番目のレベルのキャッシュがチェックされ、2番目のレベルのキャッシュに在庫がある場合は返されます。
2つ目は、mybaitsキャッシュ関連クラスの導入です。
mybaitsレベル1キャッシュとレベル2キャッシュのトップレベルのインターフェースは同じで、どちらもキャッシュクラスです。デフォルトでは、mybaitsのキャッシュ実装はhashMapパッケージであり、キャッシュクラスを実装するパッケージは多数あります。 。
1.トップレベルインターフェイスのCacheクラスとパッケージ構造を見てみましょう。
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
PerpetualCacheを除いて、他のキャッシュ実装はデコレータモードを使用します。基盤となるPerpetualCacheは実際のキャッシュを完成させ、これに基づいて他の関数を追加します。
SynchronizedCache:get / putメソッドをロックすることにより、1つのスレッドのみがキャッシュを操作することが保証されます
FifoCache、LruCache:キャッシュが上限に達したら、FIFOまたはLRU(メモリへの最も早いアクセス)戦略を介してキャッシュを削除します(名前空間が<cache>を開始するときにキャッシュ無効化戦略が構成されている場合、2つを使用できます)
ScheduledCache:get / put / remove / getSizeなどの操作を実行する前に、キャッシュ時間が設定された最大キャッシュ時間(デフォルトは1時間)を超えているかどうかを確認します。超えている場合は、キャッシュをクリアします。つまり、キャッシュをクリアします。たまに
SoftCache / WeakCache:キャッシュはJVMソフト参照と弱参照を介して実装されます。JVMメモリが不足している場合、これらのキャッシュは自動的にクリーンアップされます。
TranscationCache:トランザクションパッケージ、つまり、このキャッシュへの操作はデリゲートキャッシュにすぐには更新されません。操作は、マップコンテナの削除と追加のメンテナンスに分割されます。commitメソッドが呼び出された後、実際の操作がキャッシュされます。 。
2.キャッシュ実装クラスの主な実装ロジック
1) PerpetualCache
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
PerpetualCacheはマップキャッシュです。これはmybaitsのデフォルトのキャッシュ実装です。第1レベルのキャッシュはこのクラスを使用します。第2レベルのキャッシュがサードパーティのキャッシュを使用する場合は、使用できる既製のjarパッケージがあります。ここでは説明しません。
2)FifoCache
FifoCacheは、先入れ先出しキャッシュを実装するキャッシングパッケージであり、その実装原則は、LinkedList先入れ先出しメカニズムを使用することです。
private final Cache delegate;
private LinkedList<Object> keyList;
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<Object>();
this.size = 1024;
}
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
}
// 主要实现就是添加缓存的时候,顺便添加到list中,这样如果添加前,判断list的尺寸大于size
// 就移除list中的第一个,并且delegate缓存也移除。
private void cycleKeyList(Object key) {
keyList.addLast(key);
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
3) LruCache
LruCacheは、LRU除去メカニズムを実装するキャッシュパッケージです。その主な原則は、LinkListの3つのパラメーター構造を使用することです。
new LinkedHashMap <Object、View>(size、0.75f、true)、3番目のパラメーターaccessOrderの関数は、要素がアクセスされた場合に、リンクリストの最後に要素を追加するかどうかです。LinkedHashMapの保護されたremoveEldestEntryメソッドと組み合わせて、LRU(つまり、最も長い間アクセスされていない削除)を実装できます。
private MyCache delegate;
private Map<Object, Object> keyMap;
public LRUCache(MyCache delegate) {
super();
this.delegate = delegate;
setSize(1024);
}
private void setSize(int initialCapacity) {
keyMap = new LinkedHashMap<Object, Object>(initialCapacity, 0.75f, true) {
private static final long serialVersionUID = 4267176411845948333L;
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > initialCapacity; // 大于尺寸
if (tooBig) { // ture 移除delegate缓存
delegate.removeObject(eldest.getKey());
}
return tooBig; // 返回true会自动移除LinkHashMap的缓存
}
};
}
// 其他操作的时候KeyMap进行同步
4)、SynchronizedCache
SynchronizedCacheは同期ロックのパッケージです。これは単純です。つまり、キャッシュ状態の変更に依存するすべての実装メソッドがロックされます。
@Override
public synchronized void putObject(Object key, Object value) {
delegate.putObject(key, value);
}
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public synchronized Object removeObject(Object key) {
return delegate.removeObject(key);
}
5)、TranscationCache
TranscationCacheのパッケージ化は、2つのマップを内部的に維持することです。1つのマップはキャッシュの削除操作をインストールし、もう1つのマップはキャッシュの書き込み操作をインストールします。コミットすると、2つのマップをトラバースして、キャッシュされた操作を実行します。リセットすると、クリアされます。 。これらの2つの一時的なマップ。次のcahceのメンバー変数、および2つの内部クラス(これらの2つの内部クラスはキャッシュ操作をラップするためのものです)。
/**
* 对需要添加元素的包装,并且传入了delegate缓存,调用此commit就可以用delegate缓存put进添加元素
*/
private static class AddEntry {
private Object key;
private Object value;
private MyCache delegate;
public AddEntry(Object key, Object value, MyCache delegate) {
super();
this.key = key;
this.value = value;
this.delegate = delegate;
}
public void commit() {
this.delegate.putObject(key, value);
}
}
/**
* 对需要移除的元素的包装,并且传入了delegate缓存,调用此commit就可以用delegate移除元素。
*/
private static class RemoveEntry {
private Object key;
private MyCache delegate;
public RemoveEntry(Object key, MyCache delegate) {
super();
this.key = key;
this.delegate = delegate;
}
public void commit() {
this.delegate.removeObject(key);
}
}
// 包装的缓存
private MyCache delegate;
// 待添加元素的map
private Map<Object, AddEntry> entriesToAddOnCommit;
// 待移除元素的map
private Map<Object, RemoveEntry> entriesToRemoveOnCommit;
以下はコア実装です
// 添加的时候,操作的是二个map,既然添加,那么临时remove的map需要remove这个key
@Override
public void putObject(Object key, Object value) {
this.entriesToRemoveOnCommit.remove(key);
this.entriesToAddOnCommit.put(key, new AddEntry(key, value, delegate));
}
@Override
public Object getObject(Object key) {
return this.delegate.getObject(key);
}
// 移除的时候,操作的是二个map,既然移除,那么临时add的map需要remove这个可以。
@Override
public Object removeObject(Object key) {
this.entriesToAddOnCommit.remove(key);
this.entriesToRemoveOnCommit.put(key, new RemoveEntry(key, delegate));
return this.delegate.getObject(key); // 这里是为了获得返回值,注意不能用removeObject
}
@Override
public void clear() {
this.delegate.clear();
reset();
}
// 提交
public void commit() {
delegate.clear(); // delegate移除
for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
entry.commit(); // 移除remove里的元素
}
for (AddEntry entry : entriesToAddOnCommit.values()) {
entry.commit(); // 添加add里的元素
}
reset();
}
public void rollback() {
reset();
}
3.キャッシュキーの実装
mybaitsによってキャッシュされるキーは、CacheKeyクラスに基づいて実装され、そのコアメカニズムは次のとおりです。
*キャッシュキーの実装メカニズム:updateメソッドを使用して、ハッシュコードを計算し、それを内部リストコレクションに追加して、内部リスト要素もすべて同じであるかどうかを判断します。
* mybaitsのCacheKeyを作成するためのメカニズム:同じステートメント、内部ページパラメーターオフセット、内部ページパラメーター制限、プリコンパイルされたsqlステートメント、パラメーターマッピング。
/**
* CacheKey的组装
*/
public static void main(String[] args) {
String mappedStatementId = "MappedStatementId"; // 用字符串描述mybaits的cachekey的元素。
String rowBounds_getOffset = "rowBounds_getOffset";
String rowBounds_getLimit = "rowBounds_getLimit";
String buondSql_getSql = "buondSql_getSql";
List<String> parameterMappings = new ArrayList<>();
parameterMappings.add("param1");
parameterMappings.add("param2");
CacheKey cacheKey = new CacheKey(); // 创建CacheKey
cacheKey.update(mappedStatementId); // 添加元素到CacheKey
cacheKey.update(rowBounds_getOffset);
cacheKey.update(rowBounds_getLimit);
cacheKey.update(buondSql_getSql);
cacheKey.update(parameterMappings);
System.out.println(cacheKey);
}
3、第1レベルのキャッシュの詳細な説明
上記のキャッシュクラスインターフェイスとその実装についてはすでに理解しているので、mybaitsで次のレベルのキャッシュがどのように使用されるかを調べることができます。
第1レベルのキャッシュについて説明する前に、sqlsessionが実際にExecutorを呼び出して操作し、BaseExecutorがExecutorの基本的な実装であることを確認しましょう。他にもいくつかの実装があります。SimpleExecutorと別の実装を使用します。
CachingExecutorは、第2レベルのキャッシュを実装します。セッションを作成するときにキャッシュの作成を確認することから始めましょう。次に、BaseExecutorが第1レベルのキャッシュを確認し始めます。
1.キャッシュ作成プロセス(キャッシュ作成はSqlSessionを作成するときに行われ、SqlSsessionのopenSessionFromDataSourceを直接確認します)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) { // 根据Executor类型创建不同的Executor
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction); // 默认的Executor
}
if (cacheEnabled) { // 如果开启全局二级缓存
executor = new CachingExecutor(executor); // 就创建CachingExecutor
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
上記はエグゼキュータの作成プロセスですが、実際には第1レベルのキャッシュの作成プロセスです。第1レベルのキャッシュは、エグゼキュータ内のキャッシュのメンバー変数です。第2レベルのキャッシュは、CachingExecutorのExecutorのパッケージ化によって実現されます。これについては、後で詳しく分析します。
2.エントリBaseExecutor.queryをクエリして、分析を開始します
SqlSessionのすべてのクエリは、呼び出されたExecutorのqueryメソッドによって実装されます。実装クラスBaseExecutorのコードは次のとおりです。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // 缓存key创建
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
このクエリは、主にキャッシュキーを作成し(作成ロジックについては上記で説明しました)、SQLを取得するためのものです。queryによって呼び出されたオーバーロードされたqueryメソッドを見てください。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache(); // 如果配置需要清除就清除本地缓存
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // 从缓存没有取到,查数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load(); //处理循环引用?
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // 如果是statement的本地缓存,就直接清除!
}
}
return list; // 取到返回
}
このクエリメソッドでは、最初にローカルキャッシュlocalCache(これはPerpetualCache)を取り出し、見つかった場合は戻り、データベースメソッドqueryFromDatabaseが見つからない場合はチェックします。さらに、第1レベルのキャッシュはステートメント範囲として構成できます。つまり、ローカルキャッシュはクエリごとにクリアされます。queryFromDatabaseメソッドをもう一度見てみましょう。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try { // 查询语句
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list); // 查询出来放入一级缓存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
また、更新などの操作を行うと、ローカルキャッシュが削除されます。
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
clearLocalCache();
return doUpdate(ms, parameter);
}
第四に、第2レベルのキャッシュの実現
前述のように、第2レベルのキャッシュは主にCachingExecutorのパッケージ化に依存しているため、このクラスを直接分析することで第2レベルのキャッシュを理解できます。
1.CachingExecutorメンバーとTransactionalCacheManagerの詳細な説明
public class CachingExecutor implements Executor {
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager(); // TransactionalCache的管理类
TransactionalCacheManager:CachingExecutorによって使用される第2レベルのキャッシュオブジェクトを管理するために使用されます。1つのtransactionalCachesフィールドのみが定義されます。
private final Map <Cache、TransactionalCache > transactionalCaches = new HashMap <Cache、TransactionalCache>();
そのキーはCachingExecutorによって使用される第2レベルのキャッシュオブジェクトであり、値は対応するTransactionalCacheオブジェクトです。以下でその実装を見てみましょう。
public class TransactionalCacheManager {
// 装未包装缓存和包装成Transaction缓存的map映射
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
// 操作缓存多了一个Cache参数,实际上是调用Transaction的对应方法
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
// 全部缓存commit
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
// 全部缓存rollback
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
// 创建一个TransactionalCache,并把原cache为key放入map维护
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
2.レベル2キャッシュキャッシュロジック
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache(); // 获得二级缓存
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) { // 开启了缓存
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) { // 没有就查询
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
}
return list; // 有就返回
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
上記は、最初に第2レベルのキャッシュの単純なクエリであり、ない場合は戻り、次にBaseExcuteのdoqueryを確認します(最初に第1レベルのキャッシュを確認し、戻り値を見つけ、データベースを確認せずに、データベースがチェックされると、第1レベルのキャッシュに戻ります)。
更新やコミットなどの他の操作は、第2レベルのキャッシュをクリアします(これは単純な実装と同等です。つまり、クエリしかない場合は第2レベルのキャッシュが常に有効であり、更新がある場合はすべて第2レベルのキャッシュをクリアする必要があります)。
加えて:
デフォルトでは、第2レベルのキャッシュのキャッシュは、各名前空間がキャッシュを共有します。このキャッシュの実装も、LRUCacheなどおよびSynchronizedCacheと一緒にパッケージ化する必要があります。
これは、Cache cache = ms.getCache();のブレークポイントでチェックされます。パッケージの構成方法は、Mapper.xml解析の関連プロセスで分析できます。
5:プライマリキャッシュとセカンダリキャッシュの使用の概要
コードからわかるように、第1レベルのキャッシュはデフォルトでオンになっており、第1レベルのキャッシュクエリを実行するか追加操作を実行するかを制御する設定または判断ステートメントはありません。したがって、第1レベルのキャッシュをオフにすることはできません。
セカンダリキャッシュの構成には、次の3つの場所があります。
a、グローバルキャッシュスイッチ、mybatis-config.xml
<settings> <setting name = "cacheEnabled" value = "true" /> </ settings>
b。各名前空間の下の第2レベルのキャッシュインスタンスmapper.xml
<cache />または他の名前空間のキャッシュを参照<cache-refnamespace = "com.someone.application.data.SomeMapper" />
c。<select>ノードでuseCache属性を構成します
デフォルトはtrueです。falseに設定すると、セカンダリキャッシュはこのselectステートメントに対して有効になりません。
a、第1レベルのキャッシュのスコープ
第1レベルのキャッシュのスコープは構成可能です。
<settings> <setting name = "localCacheScope" value = "STATEMENT" /> </ settings>
範囲オプションは次のとおりです。SESSIONおよびSTATEMENT、デフォルト値はSESSIONです
セッション:この場合、セッション(SqlSession)で実行されたすべてのクエリがキャッシュされます
ステートメント:ローカルセッションはステートメントの実行にのみ使用され、クエリの完了後に第1レベルのキャッシュがクリアされます。
b、セカンダリキャッシュのスコープ
セカンダリキャッシュのスコープは名前空間です。つまり、同じ名前空間の下にある複数のSqlSessionが同じキャッシュを共有します。
4.使用に関する提案
2次キャッシュの使用はお勧めしません。2次キャッシュは名前空間のスコープで共有されます。ライフサイクルはsqlsesisonFacotryですが、更新するとすべての2次キャッシュがクリアされます。 -レベルキャッシュには、テーブルのクエリにも問題があります(たとえば、この名前空間にないテーブルに接続した、そのテーブルのデータが変更された、この名前空間の2次キャッシュが不明である)など。問題があるため、使用することはお勧めしません。
極端な場合、第1レベルのキャッシュにダーティデータが含まれている可能性があります.1つの提案はそれをSTATEMENT範囲に変更することであり、もう1つはsqlsessionを使用してビジネスロジックで同じステートメントを繰り返しクエリしないことです。
終わり!