Análise do código-fonte Mybaits (5) A explicação mais detalhada do cache de primeiro nível e do cache de segundo nível

Análise do código-fonte Mybaits (5) Explicação detalhada do cache de primeiro nível e do cache de segundo nível

Prefácio: o artigo anterior explicou a fonte de dados mybaits, este artigo explicou o uso básico do cache de primeiro nível mybaits, o cache de segundo nível e a implementação principal.

Este artigo está dividido principalmente nas seguintes partes:

O uso e teste do cache de primeiro nível e do cache de segundo nível

Introdução às classes relacionadas ao cache mybaits

Cache de nível 1 detalhado

Cache de segundo nível detalhado

1. O uso e teste do cache de primeiro nível e do cache de segundo nível

Em mybaits, o cache de primeiro nível é habilitado por padrão, o ciclo de vida do cache está no nível sqlsession e a configuração global do cache de segundo nível é habilitada por padrão, mas você também precisa habilitá-lo no namespace para usar o cache de segundo nível, o cache de segundo nível O ciclo de vida é sqlsessionFactory, e o escopo da operação de cache é que cada mapeador corresponde a um cache (é por isso que ele precisa ser habilitado no namespace configurado pelo mapeador para ter efeito)

1. Adicione a seguinte configuração no SqlMapper, cacheEnabled é responsável por ativar o cache de segundo nível, logImpl é responsável por imprimir sql (podemos testar se o cache acabou de acordo com a necessidade de imprimir o sql real)

	<settings>
        <!--打印执行sql用
	  	<setting name="logImpl" value="STDOUT_LOGGING"/>
	  	<!-- 默认为true -->
	  	<setting name="cacheEnabled" value="true"/>
	</settings>

2. Teste

	
	/**
	 * 一级缓存测试 :
	 * 	测试前,需要加下面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);
	}
	

A partir dos resultados do teste, pode-se ver que quando mybaits não abre o cache de segundo nível, uma consulta com a mesma sqlsession é executada novamente. Se não houver fechamento, o cache de primeiro nível será verificado. cache de nível é ativado, se o cache de segundo nível é ativado, se a consulta for fechada, O cache ainda pode ser usado (o cache de segundo nível é usado), o mecanismo de cache é: a primeira consulta, a segunda o cache de primeiro nível será verificado primeiro, o cache de primeiro nível não foi encontrado e o banco de dados não foi verificado novamente e o banco de dados foi primeiro encontrado no cache de primeiro nível e, em seguida, colocado no cache de segundo nível. segunda consulta, o cache de segundo nível será verificado primeiro e, se o cache de segundo nível estiver em estoque, ele será retornado.

Dois, a introdução de classes relacionadas ao cache mybaits

As interfaces de nível superior do cache de nível 1 do mybaits e do cache de nível 2 são iguais, ambos são classes de cache. Por padrão, a implementação do cache de mybaits é um pacote hashMap e há muitos pacotes que implementam a classe de cache .

1. Vamos dar uma olhada na classe Cache e na estrutura do pacote da interface de nível superior.

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();

}

Exceto para PerpetualCache , outras implementações de Cache usam o modo decorador. O PerpetualCache subjacente completa o cache real e adiciona outras funções nesta base.

SynchronizedCache : Bloqueando o método get / put, é garantido que apenas um thread opera o cache

FifoCache , LruCache : quando o cache atinge o limite superior, exclua o cache por meio da estratégia FIFO ou LRU (acesso mais antigo à memória) ( os dois podem ser usados ​​quando a estratégia de invalidação do cache é configurada quando o namespace inicia <cache> )

ScheduledCache: antes de executar operações como get / put / remove / getSize, determine se o tempo de cache excedeu o tempo máximo de cache definido (o padrão é uma hora), se for, limpe o cache - ou seja, limpe o cache de vez em quando

SoftCache / WeakCache: o cache é implementado por meio de referências suaves JVM e referências fracas. Quando a memória JVM é insuficiente, esses caches serão automaticamente limpos

TranscationCache: empacotamento transacional, ou seja, uma operação para este cache não será atualizada para o cache delegado imediatamente. A operação será dividida em remoção e manutenção de adição ao contêiner do mapa. Após o método de confirmação ser chamado, a operação real é armazenada em cache .

2. A principal lógica de implementação da classe de implementação de cache

1)  PerpetualCache 

public class PerpetualCache implements Cache {

  private String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

PerpetualCache é um cache de mapa. Esta é a implementação de cache padrão de mybaits. O cache de primeiro nível usa esta classe. Se o cache de segundo nível quiser usar um cache de terceiros, existem pacotes jar prontos que podem ser usados, que não é descrito aqui.

2) FifoCache  

FifoCache é um pacote de cache que implementa o cache first-in first-out. Seu princípio de implementação é usar o mecanismo LinkedList first-in first-out.

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 é um pacote de cache que implementa o mecanismo de eliminação de LRU.Seu princípio principal é usar a estrutura de três parâmetros de LinkList.

new LinkedHashMap <Object, View> (size, 0.75f, true), a função do terceiro parâmetro accessOrder é adicionar o elemento ao final da lista vinculada se o elemento for acessado. Combinado com um método removeEldestEntry protegido de LinkedHashMap, LRU (ou seja, a remoção que não foi acessada por muito tempo) pode ser implementada.

 	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 é um pacote de bloqueio síncrono.Isto é simples, ou seja, todos os métodos de implementação que dependem de alterações de estado do cache são bloqueados.

@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

O pacote de TranscationCache é manter dois mapas internamente. Um mapa instala a operação de remoção do cache e o outro mapa instala a operação de colocação do cache. Ao confirmar, ele atravessa os dois mapas e executa a operação em cache. Se redefinido, será apagado Estes dois mapas temporários. As seguintes variáveis ​​de membro de cahce e duas classes internas (essas duas classes internas são para agrupar a operação de cache).

	/**
	 * 对需要添加元素的包装,并且传入了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;

A seguir está a implementação principal

// 添加的时候,操作的是二个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. Implementação da chave de cache

A chave armazenada em cache por mybaits é implementada com base em uma classe CacheKey e seu mecanismo principal é o seguinte:

 * O mecanismo de implementação da chave de cache : através do método de atualização, calcule o hashcode e adicione-o à coleção da lista interna para determinar se é o mesmo, também com base nos elementos da lista interna são todos iguais.
 * Mecanismo para criar CacheKey de mybaits : mesma instrução, deslocamento de parâmetro de página interna, limite de parâmetro de página interna, instrução sql pré-compilada, mapeamento de parâmetro.

/**
	 * 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); 
	}

Três, a explicação detalhada do cache de primeiro nível

Já entendemos a interface da classe de cache e sua implementação acima, e agora podemos explorar como o próximo nível de cache é usado em mybaits.

Antes de falar sobre o cache de primeiro nível, vamos revisar que sqlsession realmente chama o Executor para operar, e BaseExecutor é a implementação básica do Executor. Ele tem várias outras implementações. Usamos SimpleExecutor e outro

CachingExecutor implementa o cache de segundo nível. Vamos começar observando a criação do cache ao criar uma sessão, e então o BaseExecutor começará a examinar o cache de primeiro nível.

1. Processo de criação de cache (a criação de cache ocorre ao criar SqlSession, olhamos diretamente para openSessionFromDataSource de SqlSsession)

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

 Embora o acima seja o processo de criação do Executor, na verdade é o processo de criação do cache de primeiro nível.O cache de primeiro nível é uma variável membro de um Cache no Executor. O segundo nível de cache é realizada por CachingExecutor embalagem de executor 's, o que será analisado em pormenor mais tarde.

2. Consulta a entrada BaseExecutor.query para iniciar a análise

Todas as consultas de SqlSession são implementadas pelo método de consulta do Executor chamado. O código da classe de implementação BaseExecutor é o seguinte:

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

Esta consulta é principalmente para criar uma chave de cache (a lógica de criação foi discutida acima) e para recuperar sql. Observe o método de consulta sobrecarregado chamado por consulta.

 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; // 取到返回
  }

Neste método de consulta, primeiro retire o cache local localCache (este é PerpetualCache), retorne se for encontrado e verifique o método de banco de dados queryFromDatabase se ele não for encontrado. Além disso, o cache de primeiro nível pode ser configurado como um intervalo de instruções, ou seja, o cache local será limpo para cada consulta. Vamos examinar o método queryFromDatabase novamente.

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

Além disso, se houver operações como atualização, o cache local será removido.

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

Quarto, a realização do cache de segundo nível

Como mencionado acima, o cache de segundo nível depende principalmente do empacotamento do CachingExecutor, então podemos entender o cache de segundo nível analisando diretamente esta classe.

1. Explicação detalhada dos membros CachingExecutor e TransactionalCacheManager

public class CachingExecutor implements Executor {

  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager(); // TransactionalCache的管理类

TransactionalCacheManager : usado para gerenciar o objeto de cache de segundo nível usado por CachingExecutor, apenas um campo transactionalCaches é definido

Mapa final privado <Cache, TransactionalCache > transactionalCaches = novo HashMap <Cache, TransactionalCache> ();

Sua chave é o objeto de cache de segundo nível usado por CachingExecutor, e o valor é o objeto TransactionalCache correspondente.Vamos ver sua implementação abaixo.

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. Lógica de cache de cache de nível 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);
  }

O acima é uma consulta simples do cache de segundo nível primeiro e retorna se não houver, então verifique o doquery de BaseExcute (que é primeiro verificar o cache de primeiro nível, encontrar o retorno, não verificar o banco de dados e colocar de volta ao cache de primeiro nível quando o banco de dados é verificado).

Outras operações, como atualização e confirmação, limparão o cache de segundo nível (isso é equivalente a uma implementação simples, o que significa que se houver apenas uma consulta, o cache de segundo nível é sempre válido, e se houver uma atualização, todos os caches de segundo nível devem ser limpos).

Além disso:

O Cache do cache de segundo nível, por padrão, cada namespace compartilha um Cache.A implementação deste Cache também deve ser empacotada com LRUCache, etc. e SynchronizedCache.

Isso é verificado no ponto de interrupção de Cache cache = ms.getCache () ;. Como configurar o pacote pode ser analisado no processo relevante de análise Mapper.xml.

 

Cinco: Resumo do uso do cache primário e secundário

1. Configuração de cache de nível 1:

Como pode ser visto no código, o cache de primeiro nível é ativado por padrão e não há nenhuma configuração ou declaração de julgamento para controlar se deve ser realizada a consulta de cache de primeiro nível ou operação de adição. Portanto, o cache de primeiro nível não pode ser desativado.

2. Configuração de cache de nível 2:

Existem três locais na configuração do cache secundário:

a, switch de cache global, mybatis-config.xml

<settings> <setting name = "cacheEnabled" value = "true" /> </settings>

b. A instância de cache de segundo nível mapper.xml em cada namespace

<cache /> ou consulte o cache de outros namespaces <cache-ref namespace = "com.someone.application.data.SomeMapper" />

c. Configure o atributo useCache no nó <select>

O padrão é verdadeiro, quando definido como falso, o cache secundário não terá efeito para esta instrução select

3. Intervalo de cache

a, o escopo do cache de primeiro nível

O escopo do cache de primeiro nível é configurável:

<settings> <setting name = "localCacheScope" value = "STATEMENT" /> </settings>

As opções de intervalo são: SESSION e STATEMENT, o valor padrão é SESSION

SESSÃO: Neste caso, todas as consultas executadas em uma sessão (SqlSession) serão armazenadas em cache

DECLARAÇÃO: A sessão local é usada apenas para execução de instruções e o cache de primeiro nível será limpo após a conclusão de uma consulta.

b, o escopo do cache secundário

O escopo do cache secundário é o namespace, ou seja, várias SqlSessions no mesmo namespace compartilham o mesmo cache

 

4. Sugestões de uso

Não é recomendado usar o cache de segundo nível. O cache de segundo nível é compartilhado no escopo do espaço de nomes. Embora o ciclo de vida seja sqlsesisonFacotry, qualquer atualização limpará todos os caches de segundo nível. Além disso, o segundo -level cache também tem problemas na consulta de tabelas (por exemplo, você se conectou a uma tabela que não está neste namespace, os dados dessa tabela foram alterados, o cache de segundo nível deste namespace não é conhecido) e alguns outros problemas, por isso não é recomendado usá-lo.

Em alguns casos extremos, o cache de primeiro nível pode ter dados sujos.Uma sugestão é alterá-lo para o intervalo STATEMENT e a outra é não usar uma sqlsession para consultar repetidamente a mesma instrução na lógica de negócios.

 

 

fim!

 

 

 

 

 

 

 

Acho que você gosta

Origin blog.csdn.net/shuixiou1/article/details/113664750
Recomendado
Clasificación