CachingExecutor.java

  1. /*
  2.  *    Copyright 2009-2024 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.executor;

  17. import java.sql.SQLException;
  18. import java.util.List;

  19. import org.apache.ibatis.cache.Cache;
  20. import org.apache.ibatis.cache.CacheKey;
  21. import org.apache.ibatis.cache.TransactionalCacheManager;
  22. import org.apache.ibatis.cursor.Cursor;
  23. import org.apache.ibatis.mapping.BoundSql;
  24. import org.apache.ibatis.mapping.MappedStatement;
  25. import org.apache.ibatis.mapping.ParameterMapping;
  26. import org.apache.ibatis.mapping.ParameterMode;
  27. import org.apache.ibatis.mapping.StatementType;
  28. import org.apache.ibatis.reflection.MetaObject;
  29. import org.apache.ibatis.session.ResultHandler;
  30. import org.apache.ibatis.session.RowBounds;
  31. import org.apache.ibatis.transaction.Transaction;

  32. /**
  33.  * @author Clinton Begin
  34.  * @author Eduardo Macarron
  35.  */
  36. public class CachingExecutor implements Executor {

  37.   private final Executor delegate;
  38.   private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  39.   public CachingExecutor(Executor delegate) {
  40.     this.delegate = delegate;
  41.     delegate.setExecutorWrapper(this);
  42.   }

  43.   @Override
  44.   public Transaction getTransaction() {
  45.     return delegate.getTransaction();
  46.   }

  47.   @Override
  48.   public void close(boolean forceRollback) {
  49.     try {
  50.       // issues #499, #524 and #573
  51.       if (forceRollback) {
  52.         tcm.rollback();
  53.       } else {
  54.         tcm.commit();
  55.       }
  56.     } finally {
  57.       delegate.close(forceRollback);
  58.     }
  59.   }

  60.   @Override
  61.   public boolean isClosed() {
  62.     return delegate.isClosed();
  63.   }

  64.   @Override
  65.   public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  66.     flushCacheIfRequired(ms);
  67.     return delegate.update(ms, parameterObject);
  68.   }

  69.   @Override
  70.   public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
  71.     flushCacheIfRequired(ms);
  72.     return delegate.queryCursor(ms, parameter, rowBounds);
  73.   }

  74.   @Override
  75.   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
  76.       throws SQLException {
  77.     BoundSql boundSql = ms.getBoundSql(parameterObject);
  78.     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  79.     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  80.   }

  81.   @Override
  82.   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,
  83.       CacheKey key, BoundSql boundSql) throws SQLException {
  84.     Cache cache = ms.getCache();
  85.     if (cache != null) {
  86.       flushCacheIfRequired(ms);
  87.       if (ms.isUseCache() && resultHandler == null) {
  88.         ensureNoOutParams(ms, boundSql);
  89.         @SuppressWarnings("unchecked")
  90.         List<E> list = (List<E>) tcm.getObject(cache, key);
  91.         if (list == null) {
  92.           list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  93.           tcm.putObject(cache, key, list); // issue #578 and #116
  94.         }
  95.         return list;
  96.       }
  97.     }
  98.     return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  99.   }

  100.   @Override
  101.   public List<BatchResult> flushStatements() throws SQLException {
  102.     return delegate.flushStatements();
  103.   }

  104.   @Override
  105.   public void commit(boolean required) throws SQLException {
  106.     delegate.commit(required);
  107.     tcm.commit();
  108.   }

  109.   @Override
  110.   public void rollback(boolean required) throws SQLException {
  111.     try {
  112.       delegate.rollback(required);
  113.     } finally {
  114.       if (required) {
  115.         tcm.rollback();
  116.       }
  117.     }
  118.   }

  119.   private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
  120.     if (ms.getStatementType() == StatementType.CALLABLE) {
  121.       for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
  122.         if (parameterMapping.getMode() != ParameterMode.IN) {
  123.           throw new ExecutorException(
  124.               "Caching stored procedures with OUT params is not supported.  Please configure useCache=false in "
  125.                   + ms.getId() + " statement.");
  126.         }
  127.       }
  128.     }
  129.   }

  130.   @Override
  131.   public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  132.     return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  133.   }

  134.   @Override
  135.   public boolean isCached(MappedStatement ms, CacheKey key) {
  136.     return delegate.isCached(ms, key);
  137.   }

  138.   @Override
  139.   public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
  140.       Class<?> targetType) {
  141.     delegate.deferLoad(ms, resultObject, property, key, targetType);
  142.   }

  143.   @Override
  144.   public void clearLocalCache() {
  145.     delegate.clearLocalCache();
  146.   }

  147.   private void flushCacheIfRequired(MappedStatement ms) {
  148.     Cache cache = ms.getCache();
  149.     if (cache != null && ms.isFlushCacheRequired()) {
  150.       tcm.clear(cache);
  151.     }
  152.   }

  153.   @Override
  154.   public void setExecutorWrapper(Executor executor) {
  155.     throw new UnsupportedOperationException("This method should not be called");
  156.   }

  157. }