Browse Source

解决分页二级缓存问题.

https://github.com/baomidou/mybatis-plus/issues/1580
nieqiuqiu 5 years ago
parent
commit
8d5609ac67

+ 3 - 3
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisConfiguration.java

@@ -17,13 +17,13 @@ package com.baomidou.mybatisplus.core;
 
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.core.executor.MybatisBatchExecutor;
+import com.baomidou.mybatisplus.core.executor.MybatisCachingExecutor;
 import com.baomidou.mybatisplus.core.executor.MybatisReuseExecutor;
 import com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor;
 import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.ibatis.binding.MapperRegistry;
-import org.apache.ibatis.executor.CachingExecutor;
 import org.apache.ibatis.executor.Executor;
 import org.apache.ibatis.logging.Log;
 import org.apache.ibatis.logging.LogFactory;
@@ -149,7 +149,7 @@ public class MybatisConfiguration extends Configuration {
         }
         getLanguageRegistry().setDefaultDriverClass(driver);
     }
-    
+
     @Override
     public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
         executorType = executorType == null ? defaultExecutorType : executorType;
@@ -163,7 +163,7 @@ public class MybatisConfiguration extends Configuration {
             executor = new MybatisSimpleExecutor(this, transaction);
         }
         if (cacheEnabled) {
-            executor = new CachingExecutor(executor);
+            executor = new MybatisCachingExecutor(executor);
         }
         executor = (Executor) interceptorChain.pluginAll(executor);
         return executor;

+ 196 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/executor/MybatisCachingExecutor.java

@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2011-2020, baomidou (jobob@qq.com).
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * <p>
+ * https://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.baomidou.mybatisplus.core.executor;
+
+import com.baomidou.mybatisplus.core.metadata.CachePage;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.cache.Cache;
+import org.apache.ibatis.cache.CacheKey;
+import org.apache.ibatis.cache.TransactionalCacheManager;
+import org.apache.ibatis.cursor.Cursor;
+import org.apache.ibatis.executor.BatchResult;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.ExecutorException;
+import org.apache.ibatis.mapping.*;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.transaction.Transaction;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * copy org.apache.ibatis.executor.CachingExecutor 主要修改了分页缓存逻辑
+ *
+ * @author nieqiuqiu
+ */
+public class MybatisCachingExecutor implements Executor {
+
+    private final Executor delegate;
+    private final TransactionalCacheManager tcm = new TransactionalCacheManager();
+
+    public MybatisCachingExecutor(Executor delegate) {
+        this.delegate = delegate;
+        delegate.setExecutorWrapper(this);
+    }
+
+    @Override
+    public Transaction getTransaction() {
+        return delegate.getTransaction();
+    }
+
+    @Override
+    public void close(boolean forceRollback) {
+        try {
+            //issues #499, #524 and #573
+            if (forceRollback) {
+                tcm.rollback();
+            } else {
+                tcm.commit();
+            }
+        } finally {
+            delegate.close(forceRollback);
+        }
+    }
+
+    @Override
+    public boolean isClosed() {
+        return delegate.isClosed();
+    }
+
+    @Override
+    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
+        flushCacheIfRequired(ms);
+        return delegate.update(ms, parameterObject);
+    }
+
+    @Override
+    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
+        BoundSql boundSql = ms.getBoundSql(parameterObject);
+        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
+        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
+    }
+
+    @Override
+    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
+        flushCacheIfRequired(ms);
+        return delegate.queryCursor(ms, parameter, rowBounds);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
+        throws SQLException {
+        Cache cache = ms.getCache();
+        IPage<E> page = null;
+        if (parameterObject instanceof Map) {
+            Map<?, ?> parameterMap = (Map<?, ?>) parameterObject;
+            Optional<? extends Map.Entry<?, ?>> optional = parameterMap.entrySet().stream().filter(entry -> entry.getValue() instanceof IPage).findFirst();
+            if (optional.isPresent()) {
+                page = (IPage) optional.get().getValue();
+            }
+        } else if (parameterObject instanceof IPage) {
+            page = (IPage) parameterObject;
+        }
+        if (cache != null) {
+            flushCacheIfRequired(ms);
+            if (ms.isUseCache() && resultHandler == null) {
+                ensureNoOutParams(ms, boundSql);
+                Object object = tcm.getObject(cache, key);
+                if (object == null) {
+                    List<E> list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
+                    if (page != null) {
+                        page.setRecords(list);
+                        tcm.putObject(cache, key, page);
+                        return new CachePage<>(page);
+                    } else {
+                        tcm.putObject(cache, key, list); // issue #578 and #116
+                    }
+                }
+                return page != null ? new CachePage<E>((IPage<E>) object) : (List<E>) object;
+            }
+        }
+        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
+    }
+
+    @Override
+    public List<BatchResult> flushStatements() throws SQLException {
+        return delegate.flushStatements();
+    }
+
+    @Override
+    public void commit(boolean required) throws SQLException {
+        delegate.commit(required);
+        tcm.commit();
+    }
+
+    @Override
+    public void rollback(boolean required) throws SQLException {
+        try {
+            delegate.rollback(required);
+        } finally {
+            if (required) {
+                tcm.rollback();
+            }
+        }
+    }
+
+    private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
+        if (ms.getStatementType() == StatementType.CALLABLE) {
+            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
+                if (parameterMapping.getMode() != ParameterMode.IN) {
+                    throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
+                }
+            }
+        }
+    }
+
+    @Override
+    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
+        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
+    }
+
+    @Override
+    public boolean isCached(MappedStatement ms, CacheKey key) {
+        return delegate.isCached(ms, key);
+    }
+
+    @Override
+    public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
+        delegate.deferLoad(ms, resultObject, property, key, targetType);
+    }
+
+    @Override
+    public void clearLocalCache() {
+        delegate.clearLocalCache();
+    }
+
+    private void flushCacheIfRequired(MappedStatement ms) {
+        Cache cache = ms.getCache();
+        if (cache != null && ms.isFlushCacheRequired()) {
+            tcm.clear(cache);
+        }
+    }
+
+    @Override
+    public void setExecutorWrapper(Executor executor) {
+        throw new UnsupportedOperationException("This method should not be called");
+    }
+
+}

+ 35 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/CachePage.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011-2020, baomidou (jobob@qq.com).
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * <p>
+ * https://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.baomidou.mybatisplus.core.metadata;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.ArrayList;
+
+/**
+ * @param <T>
+ * @author nieqiuqiu
+ */
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class CachePage<T> extends ArrayList {
+
+    private IPage<T> page;
+
+}

+ 8 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/override/MybatisMapperMethod.java

@@ -15,6 +15,7 @@
  */
 package com.baomidou.mybatisplus.core.override;
 
+import com.baomidou.mybatisplus.core.metadata.CachePage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.binding.BindingException;
 import org.apache.ibatis.binding.MapperMethod;
@@ -83,7 +84,13 @@ public class MybatisMapperMethod {
                     // TODO 这里下面改了
                     if (IPage.class.isAssignableFrom(method.getReturnType()) && args != null
                         && IPage.class.isAssignableFrom(args[0].getClass())) {
-                        result = ((IPage<?>) args[0]).setRecords(executeForIPage(sqlSession, args));
+                        result = executeForIPage(sqlSession, args);
+                        if (result instanceof CachePage) {
+                            CachePage cachePage = (CachePage) result;
+                            result = cachePage.getPage();
+                        } else {
+                            result = ((IPage<?>) args[0]).setRecords(executeForIPage(sqlSession, args));
+                        }
                         // TODO 这里上面改了
                     } else {
                         result = sqlSession.selectOne(command.getName(), param);