Cache de nivel 1 y caché de nivel 2
MyBatis diseña caché de datos en una estructura de dos niveles, dividida en caché de primer nivel y caché de segundo nivel:
El caché de nivel 1 es un caché de nivel de sesión de sesión, ubicado en el objeto SQLSession que representa una sesión de base de datos, y también se llama caché local. El caché de nivel 1 es una característica implementada internamente por MyBatis. Los usuarios no pueden configurarlo y lo admiten automáticamente de forma predeterminada. Los usuarios no tienen derecho a personalizarlo (pero esto no es absoluto, y pueden modificarse a través de complementos de desarrollo);
El caché de segundo nivel es un caché de nivel de aplicación, que tiene un ciclo de vida largo, lo mismo que el ciclo de aplicación de la aplicación, lo que significa que su alcance de función es toda la aplicación de la aplicación.
La organización del caché de primer nivel y el caché de segundo nivel en MyBatis se muestra en la figura a continuación:
Mecanismo de trabajo del almacenamiento en caché del nivel uno:
El caché de nivel 1 es nivel de sesión de sesión. En términos generales, un objeto SQLSession utilizará un objeto de ejecutor para completar las operaciones de sesión. El objeto del ejecutor mantendrá un caché de caché para mejorar el rendimiento de la consulta.
Mecanismo de trabajo del almacenamiento en caché secundario:
Como se mencionó anteriormente, un objeto SQLSession utilizará un objeto de ejecutor para completar la operación de sesión. La clave del mecanismo de almacenamiento secundario de MyBatis es hacer un escándalo sobre este objeto de ejecutor. Si el usuario ha configurado "Cacheenabled = true", cuando MyBatis crea un objeto de ejecutor para el objeto SQLSession, agregará un decorador al objeto del ejecutor: CachingExecutor. En este momento, SQLSession utiliza el objeto CachingExecutor para completar la solicitud de operación. Para las solicitudes de consulta, CachingExecutor primero determinará si la solicitud de consulta ha almacenado en caché los resultados en el caché secundario de nivel de aplicación. Si hay un resultado de la consulta, devolverá directamente los resultados en caché; Si no hay caché, se entregará al objeto del ejecutor real para completar la operación de consulta. Después de eso, CachingExecutor colocará el resultado de la consulta devuelto por el ejecutor real en el caché y luego lo devolverá al usuario.
El caché secundario de MyBatis está diseñado para ser más flexible. Puede usar la implementación secundaria de caché definida por MyBatis; También puede personalizar el caché implementando la interfaz org.apache.ibatis.cache.cache; También puede usar bibliotecas de caché de memoria de terceros, como Memcached, etc.
Transformación de caché
pregunta:
El problema más común es que después de abrir caché, los datos en la primera página se devolverán a la página al consultar la paginación. Además, cuando se usa el complemento de generación automática SQL para generar SQL para el método GET, los parámetros aprobados no funcionan. Independientemente de los parámetros aprobados, se devuelve el resultado de la consulta del primer parámetro.
Por qué ocurren estos problemas:
Al explicar el proceso de ejecución de MyBatis antes, se mencionó que bajo la premisa de habilitar el caché, el ejecutor de MyBatis leerá primero los datos del caché, y solo irá a la base de datos para consultar si no se puede leer. El problema se encuentra aquí. El tiempo de ejecución del complemento de generación automática SQL y el complemento de paginación se encuentra en DeclaryHandler, y el DeclaryHandler se ejecuta después del Ejecutor. Si el complemento de generación automática de SQL y el complemento de paginación se implementan reescribiendo SQL, el ejecutor usa SQL original al generar y leer la clave de caché (la clave consta de SQL y valores de parámetros correspondientes), por lo que, por supuesto, hay un problema.
Resolver el problema:
Una vez que se encuentra la causa del problema, será conveniente resolverlo. Simplemente anule el método de generación de claves en el ejecutor a través del interceptor, y use el SQL generado automáticamente (correspondiente al complemento de generación automática SQL) o agregue información de paginación (correspondiente al complemento de Paging) al generarlo.
Firma del Interceptor:
@Intercepts ({@firma (type = ejecutor.class, método = "query", args = {mappedStatement.class, object.class, rowbounds.class, resulthandler.class})}) clase pública Cacheinterceptor implementa el interceptor {...}Como se puede ver desde la firma, el tipo de destino que se interceptará es el ejecutor (nota: el tipo solo se puede configurar como tipo de interfaz), y el método de intercepción es un método llamado consulta.
Implementación de Intercept:
Public Object Intercept (Invocation Invocation) lanza lanzable {Ejecutor EjecutorProxy = (Ejecutor) Invocation.GetTarget (); MetaObject metaExecutor = metaObject.forObject (EjecutorProxy, default_object_factory, default_object_wrapper_factory); // separa la cadena de objeto proxy mientras (metaExecutor.hasgetter ("h")) {objeto objeto = metaExecutor.getValue ("h"); metaExecutor = metaObject.forObject (objeto, default_object_factory, default_object_wrapper_factory); } // La clase de destino que separa el último objeto proxy mientras (metaExecutor.hasgetter ("target")) {objeto objeto = metaExecutor.getValue ("target"); metaExecutor = metaObject.forObject (objeto, default_object_factory, default_object_wrapper_factory); } Objeto [] args = invocation.getArgs (); devolver esto.Query (metaexecutor, args); } public <e> list <E> QUERY (metaObject MetaExecutor, Object [] args) lanza SQLException {MappedStatement MS = (MappedStatement) args [0]; Objeto ParametREAnt = args [1]; RowBounds RowBounds = (RowBounds) args [2]; Resulthandler resulthandler = (resulthandler) args [3]; BoundSQL Boundsql = ms.getBoundSql (parametreObject); // Reescribe la generación de clave cachekey cachekey = createCachekey (MS, ParameteterObject, RowBounds, BoundSQL); Ejecutor Ejecutor = (Ejecutor) MetaExecutor.getOriginalObject (); return ESCUCELOR.Query (MS, ParametERObject, RowBounds, Resulthandler, CacheKey, BoundSQL); } private cachekey createCachekey (MappedStatement MS, Object ParameterObject, RowBounds RowBounds, BoundSQL BoundSql) {Configuración Configuración = Ms.getConfiguration (); pagesqlid = configuration.getVariables (). GetProperty ("PageSqlid"); if (null == Pagesqlid || "" .equals (pagesqlid)) {logger.warn ("PROPIEDAD PAGESQLID NO ESTÁ LA pageSqlid = DefaultPagesqLid; } Cachekey cachekey = new CacheKey (); cachekey.update (ms.getid ()); cachekey.update (rowBounds.getOffset ()); cachekey.update (rowBounds.getLimit ()); Lista <amametermapping> parametermappings = boundsql.getParametermappings (); // Resuelve el error que genera automáticamente SQL, y la instrucción SQL está vacía, causando la clave para generar errores if (null == Boundsql.getSql () || "" .Equals (BoundSql.getSql ())) {String id = Ms.getID (); id = id.substring (id.lastindexof (".") + 1); String NewsQl = null; Pruebe {if ("select" .equals (id)) {NewsQl = SqlBuilder.BuildSelectSQL (ParametRangject); } SqlSource sqlSource = buildSQLSource (Configuración, NewsQL, ParametRangject.getClass ()); parametermappings = sqlSource.getBoundSQL (parametreObject) .getParametermappings (); cachekey.update (NewsQL); } Catch (Exception e) {logger.error ("actualizar el error de caché", e); }} else {cachekey.update (boundsql.getsql ()); } MetaObject metaObject = metaObject.forObject (parametREAncject, default_object_factory, default_object_wrapper_factory); if (parametermappings.size ()> 0 && parametREAnt! = null) {typeHandlerRegistry typeHandlerRegistry = ms.getConfiguration (). getTypeEnHandlerRegistry (); if (typeHandlerRegistry.hastypeHandler (parametREAncject.getClass ())) {cacheKey.Update (parametREAnt); } 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)); }}}} // Cuando se requiere una consulta de paginación, agregue la página actual y el número de páginas por página en el parámetro de página al CacheKey if (ms.getid (). Matches (Pagesqlid) && metaObject.hasgetter ("página")) {PageParameter Page = (PageParameter) MetaObject.getValue ("Page");; if (null! = page) {cachekey.update (page.getCurrentPage ()); cachekey.update (page.getPageSize ()); }} return cachekey; } Implementación del complemento:
Public Object Plugin (Object Target) {// Cuando la clase de destino es de tipo CachingExecutor, la clase de destino está envuelta, de lo contrario volverá directamente al objetivo en sí, reduciendo el número de veces que el objetivo se proxyan si (el objetivo instanceo de CachingExecutor) {return Plugin.wrap (Target, este); } else {Target de retorno; }}