MyBatis: BE CAREFUL WHEN USING BATCH MODE FOR ACCESSING DATA BASES.
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 10 Apr 2016 12:06:34 +0000 (14:06 +0200)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 10 Apr 2016 12:06:34 +0000 (14:06 +0200)
YOUR CODE MUST BE IMPLEMENTED FOR BATCH MODE. MANY TIMES SOME
CODE THAT WORKS WITH SIMPLE MODE WILL NOT WORK WITH BATCH MODE.

Abstractions (MyBatis) are cool but never forget how stuff works underneath.

I guess the same conclusion applies for JPA, Hibernater, etc, etc. Code working without
batch mode will not work with batch mode if you did not write your code for accessing
data base in batch mode.

MyBatis/MyBatis-Spring-ReuseBatchExecutor/src/main/java/de/example/mybatis/executor/ReuseBatchExecutor.java
MyBatis/MyBatis-Spring-ReuseBatchExecutor/src/main/java/de/example/mybatis/interceptor/ReuseBatchExecutorInterceptor.java
MyBatis/MyBatis-Spring-ReuseBatchExecutor/src/main/java/de/example/mybatis/spring/service/impl/ExampleServiceImpl.java
MyBatis/MyBatis-Spring-ReuseBatchExecutor/src/main/resources/config/mybatis-config.xml
MyBatis/MyBatis-Spring-ReuseBatchExecutor/src/main/resources/spring-config.xml

index 2d6702a..c5d543c 100644 (file)
@@ -35,6 +35,7 @@ public class ReuseBatchExecutor extends BaseExecutor {
                super(configuration, transaction);
        }
 
+       @Override
        public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
                final Configuration configuration = ms.getConfiguration();
                final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT,
@@ -44,6 +45,7 @@ public class ReuseBatchExecutor extends BaseExecutor {
                return BATCH_UPDATE_RETURN_VALUE;
        }
 
+       @Override
        public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
                        ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
                Statement stmt = null;
@@ -59,6 +61,7 @@ public class ReuseBatchExecutor extends BaseExecutor {
                }
        }
 
+       @Override
        public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
 
                try {
index 19124f8..9b5ffa8 100644 (file)
@@ -4,6 +4,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Properties;
 
+import org.apache.ibatis.executor.BatchExecutor;
 import org.apache.ibatis.executor.CachingExecutor;
 import org.apache.ibatis.executor.Executor;
 import org.apache.ibatis.plugin.Interceptor;
@@ -35,6 +36,11 @@ public class ReuseBatchExecutorInterceptor implements Interceptor {
        public Object plugin(Object target) {
                Object result = target;
                
+               // It avoids one branch when target is not Executor instance.
+               if (!(target instanceof Executor)) {
+                       return result;
+               }
+               
                if (target instanceof CachingExecutor) {
                        CachingExecutor cachingExecutor = (CachingExecutor) target;
                        try {
@@ -48,7 +54,10 @@ public class ReuseBatchExecutorInterceptor implements Interceptor {
                        } catch (NoSuchFieldException e) {
                                LOGGER.error("Error: ", e);
                        }
-               } else if (target instanceof Executor){
+                       // Do not override SimpleExecutor because it is used by SelectKeyGenerator (retrieves autoincremented value
+                       // from database after INSERT) ReuseBatchExecutor should also work but if MyBatis wants to use SimpleExecutor
+                       // why do not stick with it?
+               } else if (target instanceof BatchExecutor){
                        result = doReuseBatchExecutor((Executor) target);
                }
 
index aea867b..4bcc2c0 100644 (file)
@@ -75,11 +75,21 @@ public class ExampleServiceImpl implements ExampleService {
                LOGGER.info("Update two Ads");
 
                adTestOne.setAdMobileImage("updatedBildOne.jpg");
+               // WARNING!!! adTestOne.id keeps being NULL when using BATCH Executor of MyBatis!!!!
+               //            So, this code will do ANYTHING AT ALL!!!!
+               // BE CAREFUL WHEN USING BATCH MODE FOR ACCESSING DATA BASES!!!!
                adMapper.updateByPrimaryKey(adTestOne);
                
+               // WARNING!!! adTestTwo.id keeps being NULL when using BATCH Executor of MyBatis!!!!
+               //            So, this code will do ANYTHING AT ALL!!!!
+               // BE CAREFUL WHEN USING BATCH MODE FOR ACCESSING DATA BASES!!!!
                adTestTwo.setAdMobileImage("updatedBildTwo.jpg");
                adMapper.updateByPrimaryKey(adTestTwo);
                
+               // IF YOU WANT BATCH MODE FOR ACCESSING DATA BASES YOUR CODE MUST BE IMPLEMENTED FOR BATCH MODE.
+               // I MEAN, IN THIS EXAMPLE SIMPLE MODE WILL WORK BUT BATCH MODE WILL NOT WORK IN ANY WAY!!!!
+               // BATCH has some implications that must not be forgotten. You can not abstract your code
+               // from the access mode to your data base!!!!!
                
                
                
index e34b106..9b9e9fd 100644 (file)
     the values which are returned from the cache in the lifetime of the session.
     Therefore, as best practice, do not to modify the objects returned by MyBatis.
 
-       SI SE MODIFICAN, CUANDO HAGA UNA BÚSQUEDA EN LA CACHE POR ESE OBJETO, YA NO LO ENCONTRARÉ,
-       SERÁ COMO SI NUNCA HUBIERA SIDO CACHEADO, Y ENTONCES LA CACHE NO SIRVE PARA NADA.
+       A) Ver CachingExecutor, se habilita o deshabilita con la opción cacheEnabled:
+       1. Se hace una query tipo SELECT (no DMLs como UPDATE, INSERT, etc)
+       2. Se crea una clave basada en la query (el statement)
+       3. Si la clave no está en la cache, se hace la query (se delega al Executor que haya por debajo)
+       4. Se guarda el resultado en la cache para esa query.
+       5. MODIFICO LOS OBJETOS RETORNADOS.
+       6. Una nueva query igual a la anterior será encontrada en la cache porque la clave es la misma.
+       7. Yo espero que lo que devuelva la query sea lo que está en base de datos pero como modifiqué
+          los objetos ahora la cache me devuelve los objetos modificados!!!!! DESASTRE TOTAL.
+       
+       CONCLUSION: o deshabilitas la cache con cacheEnabled=false o tienes mucho cuidado de NUNCA modificar
+                   los objetos retornados.
+       
+    LA CACHE SOLO SE USA CUANDO NO HAY DMLs. SI LA QUERY ES UN DML TIPO update, insert, etc, etc,
+    LA CACHE NO SE USARÁ. SOLO SE USA CON selects (POR EJEMPLO).
+    
+    
+    B) Ver BaseExecutor localCache, no puede ser nunca deshabilitada completamente, solo ponerla como
+    STATEMENT o SESSION con la opción localCacheScope:
+    Ver en BaseExecutor la siguiente linea:
+       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
+    Pasa exactamente lo mismo que pasaba con CachingExecutor :(
+    
+    CONCLUSIÓN: o pones localCache a nivel de STATEMENT con localCacheScope=STATEMENT o tienes mucho
+                       cuidado de NUNCA modificar los objetos retornados.
+       
+       
+       ¿Para qué existe CachingExecutor si ya hay una localCache que puedo poner a nivel de SESSION?
        
-       VER CachingExecutor.
        -->
     <setting name="cacheEnabled" value="false"/>
     <setting name="localCacheScope" value="STATEMENT"/>
@@ -38,7 +63,7 @@
     <setting name="useColumnLabel" value="false"/>
     <setting name="useGeneratedKeys" value="false"/>
     <setting name="autoMappingBehavior" value="PARTIAL"/>
-    <setting name="defaultExecutorType" value="SIMPLE"/>
+    <setting name="defaultExecutorType" value="BATCH"/>
     <setting name="defaultStatementTimeout" value="5"/>
     <setting name="safeRowBoundsEnabled" value="false"/>
     <setting name="mapUnderscoreToCamelCase" value="false"/>
index db47ee8..a928c3d 100644 (file)
@@ -6,15 +6,17 @@
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
-                                                  http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
+                                                  http://www.springframework.org/schema/beans/spring-beans.xsd
                                                   http://www.springframework.org/schema/context 
-                                                  http://www.springframework.org/schema/context/spring-context-3.2.xsd
+                                                  http://www.springframework.org/schema/context/spring-context.xsd
                                                   http://www.springframework.org/schema/tx 
-                           http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
+                           http://www.springframework.org/schema/tx/spring-tx.xsd
                            http://www.springframework.org/schema/aop 
-                           http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
+                           http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
 
+       <!-- Searches for beans in packages (instead of XML configuration we can use in this way annotations like @Service, @Endpoint, etc, etc)  -->
+    <context:component-scan base-package="de.example.mybatis"/>
 
     <!--
     There is no need to register all your mappers one by one. Instead, you can let MyBatis-Spring scan your classpath for them.