Cache de nível 1 e cache de nível 2
Mybatis projeta o cache de dados em uma estrutura de dois níveis, dividida em cache de primeiro nível e cache de segundo nível:
O Cache de Nível 1 é um cache no nível da sessão, localizado no objeto SQLSession que representa uma sessão de banco de dados e também é chamado de cache local. O cache de nível 1 é um recurso implementado internamente pela Mybatis. Os usuários não podem configurá -lo e apoiá -lo automaticamente por padrão. Os usuários não têm o direito de personalizá-lo (mas isso não é absoluto e podem ser modificados por meio de plug-ins de desenvolvimento);
O cache de segundo nível é um cache no nível do aplicativo, que possui um ciclo de vida longo, o mesmo que o ciclo de aplicação da declaração, o que significa que seu escopo de função é o aplicativo inteiro.
A organização de cache de primeiro nível e cache de segundo nível em Mybatis é mostrada na figura abaixo:
Mecanismo de trabalho de cache de primeiro nível:
O cache de nível 1 é o nível da sessão. De um modo geral, um objeto SQLSession usará um objeto Executor para concluir as operações da sessão. O objeto Executor manterá um cache para melhorar o desempenho da consulta.
Mecanismo de trabalho do cache secundário:
Como mencionado acima, um objeto SQLSession usará um objeto de executor para concluir a operação da sessão. A chave para o mecanismo de armazenamento secundário de Mybatis é fazer uma confusão sobre esse objeto de executor. Se o usuário configurou "Cacheenabled = true", quando o mybatis cria um objeto de executor para o objeto SQLSession, ele adicionará um decorador ao objeto Executor: CachingExecutor. No momento, o SQLSession usa o objeto CachingExector para concluir a solicitação de operação. Para solicitações de consulta, o CachingExector determinará primeiro se a solicitação de consulta em cache em cache no cache secundário no nível do aplicativo. Se houver um resultado de consulta, ele retornará diretamente os resultados em cache; Se não houver cache, ele será entregue ao objeto Real Executor para concluir a operação de consulta. Depois disso, o CachingExecutor colocará o resultado da consulta retornado pelo executor real no cache e depois o devolverá ao usuário.
O cache secundário da Mybatis foi projetado para ser mais flexível. Você pode usar a implementação secundária de cache definida pelo Mybatis; Você também pode personalizar o cache implementando a interface org.apache.ibatis.cache.cache; Você também pode usar bibliotecas de cache de memória de terceiros, como memcached, etc.
Transformação do cache
pergunta:
O problema mais comum é que, após a abertura do cache, os dados na primeira página serão devolvidos à página ao consultar a paginação. Além disso, ao usar o plug-in de geração automática SQL para gerar SQL para o método GET, os parâmetros passados não funcionam. Independentemente dos parâmetros passados, o resultado da consulta do primeiro parâmetro é retornado.
Por que esses problemas ocorrem:
Ao explicar o processo de execução de Mybatis antes, foi mencionado que, sob a premissa de ativar o cache, o Executor da Mybatis primeiro lerá dados do cache e irá apenas para o banco de dados para consultar, se não puder ser lido. O problema está aqui. O tempo de execução do plug-in de geração automática SQL e plug-in de paginação está no DeclarationHandler, e o DeclarationHandler é executado após o executor. Se o plug-in de geração automática do SQL e o plug-in de paginação são implementados pela reescrita do SQL, o executor usa o SQL original ao gerar e ler a chave do cache (a chave consiste no SQL e nos valores de parâmetros correspondentes), é claro que há um problema.
Resolva o problema:
Depois que a causa do problema for encontrada, será conveniente resolvê -lo. Basta substituir o método de geração de chaves no executor através do interceptador e usar o SQL gerado automaticamente (correspondente ao plug-in de geração automática SQL) ou adicionar informações de paginação (correspondentes ao plug-in de paginação) ao gerá-lo.
Assinatura interceptadora:
@Intercepts ({@Signature (type = executor.class, method = "query", args = {mapedstatement.class, object.class, rowbounds.class, resultHandler.class})}) Classe public CacheIntercept implementa Intecept {...}Como pode ser visto na assinatura, o tipo de destino a ser interceptado é executor (Nota: o tipo só pode ser configurado como tipo de interface) e o método de interceptação é um método chamado consulta.
Implementação da interceptação:
Public Object Intercept (Invocation Invocation) lança o Throwable {Executor ExecororProxy = (Executor) Invocation.getTarget (); MetaObject metaxecutor = metaobject.foroBject (executorproxy, default_object_factory, default_object_wrapper_factory); // separa a cadeia de objetos proxy while (metaxecutor.hasgetter ("h")) {objeto objeto = metaxecutor.getValue ("h"); metaxecutor = metaobject.foroBject (objeto, default_object_factory, default_object_wrapper_factory); } // A classe de destino que separa o último objeto de proxy while (metaxecutor.hasgetter ("Target")) {objeto objeto = metaxecutor.getValue ("Target"); metaxecutor = metaobject.foroBject (objeto, default_object_factory, default_object_wrapper_factory); } Objeto [] args = invocation.getargs (); retornar this.Query (Metaxecutor, args); } public <e> list <e> Query (metaObject metaxecutor, objeto [] args) lança sqLexception {mapedstatement ms = (mapedstatement) args [0]; Objeto parameterObject = args [1]; Rowbounds rowbounds = (slowbounds) args [2]; Resulthandler Resulthandler = (Resulthandler) args [3]; Boundsql Boundsql = Ms.getBoundSQL (ParameterObject); // Reescreva a geração de chaves cachekey cachekey = createCacheKey (ms, parameterObject, rowbounds, boundsql); Executor executor = (executor) metaexecutor.getoriginalObject (); Return Execoror.Query (MS, ParameterObject, RowBounds, ResultHandler, Cachekey, Boundsql); } private CacheKey CreateCacheKey (MappEdStatement MS, Objeto ParameterObject, RowBounds RowBounds, Boundsql Boundsql) {Configuração da configuração = ms.getConfiguration (); PAGESQLID = Configuration.getVariables (). getProperty ("Pagesqlid"); if (null == PAGESQLID || "" .Equals (PAGESQLID)) {LOGGER.WARN ("Propriedade PAGESQLID não é definido, use padrão '.*Page $'"); PAGESQLID = defaultPagesqlid; } Cachekey cachekey = new cachekey (); CacheKey.Update (ms.getId ()); CacheKey.Update (rowbounds.getOffSet ()); CacheKey.Update (rowbounds.getlimit ()); Lista <MeameTerMapping> parameTerMAppings = boundSql.getParameterMAppings (); // Resolva o bug que gera automaticamente o SQL, e a instrução SQL está vazia, fazendo com que a chave gerar erros se (null == boundsql.getSql () || "" .equals (boundsql.getSql ()) {string id = ms.getId (); id = id.substring (id.lastIndexof (".") + 1); String newsql = null; tente {if ("select" .equals (id)) {newsql = sqlbuilder.buildSelectSql (parameterObject); } Sqlsource sqlsource = buildSqlsource (configuração, newsql, parameterObject.getClass ()); ParameTerMAppings = sqlsource.getBoundSQL (parameterObject) .getParameTerMAppings (); CacheKey.Update (NewsQL); } catch (Exceção e) {Logger.error ("Atualize o erro do cacheKey.", 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 (propriedadeName)); } else if (bountsql.hasadditionparameter (propriedadeName)) {cachekey.update (boundsql.getAdditionParameter (PropertyName)); }}}} // Quando uma consulta de paginação for necessária, adicione a página atual e o número de páginas por página no parâmetro da página ao cacheKey if (ms.getId (). Matches (Pagesqlid) && metaObject.hasgetter ("Page")) {PageParameter = (PageParameter) metabject.getter ("Page") {PageParameter = (PageParameter); if (null! = página) {cachekey.UpDate (página.getCurrentPage ()); CacheKey.Update (Page.getPagesize ()); }} retornar cacheKey; } Implementação do plugin:
Plug -in de objeto público (destino do objeto) {// Quando a classe de destino é do tipo CachingExecutor, a classe de destino será embrulhada, caso contrário, retornará diretamente ao próprio alvo, reduzindo o número de vezes que o alvo é proxado se (Instância de destino do CachingExecutor) {Return Plugin.Wrap (Target, this); } else {return Target; }}