BaseExecutor.java

  1. /*
  2.  *    Copyright 2009-2021 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.  *       http://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 static org.apache.ibatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;

  18. import java.sql.Connection;
  19. import java.sql.SQLException;
  20. import java.sql.Statement;
  21. import java.util.List;
  22. import java.util.concurrent.ConcurrentLinkedQueue;

  23. import org.apache.ibatis.cache.CacheKey;
  24. import org.apache.ibatis.cache.impl.PerpetualCache;
  25. import org.apache.ibatis.cursor.Cursor;
  26. import org.apache.ibatis.executor.statement.StatementUtil;
  27. import org.apache.ibatis.logging.Log;
  28. import org.apache.ibatis.logging.LogFactory;
  29. import org.apache.ibatis.logging.jdbc.ConnectionLogger;
  30. import org.apache.ibatis.mapping.BoundSql;
  31. import org.apache.ibatis.mapping.MappedStatement;
  32. import org.apache.ibatis.mapping.ParameterMapping;
  33. import org.apache.ibatis.mapping.ParameterMode;
  34. import org.apache.ibatis.mapping.StatementType;
  35. import org.apache.ibatis.reflection.MetaObject;
  36. import org.apache.ibatis.reflection.factory.ObjectFactory;
  37. import org.apache.ibatis.session.Configuration;
  38. import org.apache.ibatis.session.LocalCacheScope;
  39. import org.apache.ibatis.session.ResultHandler;
  40. import org.apache.ibatis.session.RowBounds;
  41. import org.apache.ibatis.transaction.Transaction;
  42. import org.apache.ibatis.type.TypeHandlerRegistry;

  43. /**
  44.  * @author Clinton Begin
  45.  */
  46. public abstract class BaseExecutor implements Executor {

  47.   private static final Log log = LogFactory.getLog(BaseExecutor.class);

  48.   protected Transaction transaction;
  49.   protected Executor wrapper;

  50.   protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  51.   protected PerpetualCache localCache;
  52.   protected PerpetualCache localOutputParameterCache;
  53.   protected Configuration configuration;

  54.   protected int queryStack;
  55.   private boolean closed;

  56.   protected BaseExecutor(Configuration configuration, Transaction transaction) {
  57.     this.transaction = transaction;
  58.     this.deferredLoads = new ConcurrentLinkedQueue<>();
  59.     this.localCache = new PerpetualCache("LocalCache");
  60.     this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  61.     this.closed = false;
  62.     this.configuration = configuration;
  63.     this.wrapper = this;
  64.   }

  65.   @Override
  66.   public Transaction getTransaction() {
  67.     if (closed) {
  68.       throw new ExecutorException("Executor was closed.");
  69.     }
  70.     return transaction;
  71.   }

  72.   @Override
  73.   public void close(boolean forceRollback) {
  74.     try {
  75.       try {
  76.         rollback(forceRollback);
  77.       } finally {
  78.         if (transaction != null) {
  79.           transaction.close();
  80.         }
  81.       }
  82.     } catch (SQLException e) {
  83.       // Ignore. There's nothing that can be done at this point.
  84.       log.warn("Unexpected exception on closing transaction.  Cause: " + e);
  85.     } finally {
  86.       transaction = null;
  87.       deferredLoads = null;
  88.       localCache = null;
  89.       localOutputParameterCache = null;
  90.       closed = true;
  91.     }
  92.   }

  93.   @Override
  94.   public boolean isClosed() {
  95.     return closed;
  96.   }

  97.   @Override
  98.   public int update(MappedStatement ms, Object parameter) throws SQLException {
  99.     ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  100.     if (closed) {
  101.       throw new ExecutorException("Executor was closed.");
  102.     }
  103.     clearLocalCache();
  104.     return doUpdate(ms, parameter);
  105.   }

  106.   @Override
  107.   public List<BatchResult> flushStatements() throws SQLException {
  108.     return flushStatements(false);
  109.   }

  110.   public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
  111.     if (closed) {
  112.       throw new ExecutorException("Executor was closed.");
  113.     }
  114.     return doFlushStatements(isRollBack);
  115.   }

  116.   @Override
  117.   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  118.     BoundSql boundSql = ms.getBoundSql(parameter);
  119.     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  120.     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  121.   }

  122.   @SuppressWarnings("unchecked")
  123.   @Override
  124.   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  125.     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  126.     if (closed) {
  127.       throw new ExecutorException("Executor was closed.");
  128.     }
  129.     if (queryStack == 0 && ms.isFlushCacheRequired()) {
  130.       clearLocalCache();
  131.     }
  132.     List<E> list;
  133.     try {
  134.       queryStack++;
  135.       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  136.       if (list != null) {
  137.         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  138.       } else {
  139.         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  140.       }
  141.     } finally {
  142.       queryStack--;
  143.     }
  144.     if (queryStack == 0) {
  145.       for (DeferredLoad deferredLoad : deferredLoads) {
  146.         deferredLoad.load();
  147.       }
  148.       // issue #601
  149.       deferredLoads.clear();
  150.       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  151.         // issue #482
  152.         clearLocalCache();
  153.       }
  154.     }
  155.     return list;
  156.   }

  157.   @Override
  158.   public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
  159.     BoundSql boundSql = ms.getBoundSql(parameter);
  160.     return doQueryCursor(ms, parameter, rowBounds, boundSql);
  161.   }

  162.   @Override
  163.   public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
  164.     if (closed) {
  165.       throw new ExecutorException("Executor was closed.");
  166.     }
  167.     DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
  168.     if (deferredLoad.canLoad()) {
  169.       deferredLoad.load();
  170.     } else {
  171.       deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
  172.     }
  173.   }

  174.   @Override
  175.   public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  176.     if (closed) {
  177.       throw new ExecutorException("Executor was closed.");
  178.     }
  179.     CacheKey cacheKey = new CacheKey();
  180.     cacheKey.update(ms.getId());
  181.     cacheKey.update(rowBounds.getOffset());
  182.     cacheKey.update(rowBounds.getLimit());
  183.     cacheKey.update(boundSql.getSql());
  184.     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  185.     TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
  186.     // mimic DefaultParameterHandler logic
  187.     for (ParameterMapping parameterMapping : parameterMappings) {
  188.       if (parameterMapping.getMode() != ParameterMode.OUT) {
  189.         Object value;
  190.         String propertyName = parameterMapping.getProperty();
  191.         if (boundSql.hasAdditionalParameter(propertyName)) {
  192.           value = boundSql.getAdditionalParameter(propertyName);
  193.         } else if (parameterObject == null) {
  194.           value = null;
  195.         } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
  196.           value = parameterObject;
  197.         } else {
  198.           MetaObject metaObject = configuration.newMetaObject(parameterObject);
  199.           value = metaObject.getValue(propertyName);
  200.         }
  201.         cacheKey.update(value);
  202.       }
  203.     }
  204.     if (configuration.getEnvironment() != null) {
  205.       // issue #176
  206.       cacheKey.update(configuration.getEnvironment().getId());
  207.     }
  208.     return cacheKey;
  209.   }

  210.   @Override
  211.   public boolean isCached(MappedStatement ms, CacheKey key) {
  212.     return localCache.getObject(key) != null;
  213.   }

  214.   @Override
  215.   public void commit(boolean required) throws SQLException {
  216.     if (closed) {
  217.       throw new ExecutorException("Cannot commit, transaction is already closed");
  218.     }
  219.     clearLocalCache();
  220.     flushStatements();
  221.     if (required) {
  222.       transaction.commit();
  223.     }
  224.   }

  225.   @Override
  226.   public void rollback(boolean required) throws SQLException {
  227.     if (!closed) {
  228.       try {
  229.         clearLocalCache();
  230.         flushStatements(true);
  231.       } finally {
  232.         if (required) {
  233.           transaction.rollback();
  234.         }
  235.       }
  236.     }
  237.   }

  238.   @Override
  239.   public void clearLocalCache() {
  240.     if (!closed) {
  241.       localCache.clear();
  242.       localOutputParameterCache.clear();
  243.     }
  244.   }

  245.   protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  246.   protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

  247.   protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
  248.       throws SQLException;

  249.   protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
  250.       throws SQLException;

  251.   protected void closeStatement(Statement statement) {
  252.     if (statement != null) {
  253.       try {
  254.         statement.close();
  255.       } catch (SQLException e) {
  256.         // ignore
  257.       }
  258.     }
  259.   }

  260.   /**
  261.    * Apply a transaction timeout.
  262.    *
  263.    * @param statement
  264.    *          a current statement
  265.    * @throws SQLException
  266.    *           if a database access error occurs, this method is called on a closed <code>Statement</code>
  267.    * @since 3.4.0
  268.    * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
  269.    */
  270.   protected void applyTransactionTimeout(Statement statement) throws SQLException {
  271.     StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  272.   }

  273.   private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
  274.     if (ms.getStatementType() == StatementType.CALLABLE) {
  275.       final Object cachedParameter = localOutputParameterCache.getObject(key);
  276.       if (cachedParameter != null && parameter != null) {
  277.         final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
  278.         final MetaObject metaParameter = configuration.newMetaObject(parameter);
  279.         for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
  280.           if (parameterMapping.getMode() != ParameterMode.IN) {
  281.             final String parameterName = parameterMapping.getProperty();
  282.             final Object cachedValue = metaCachedParameter.getValue(parameterName);
  283.             metaParameter.setValue(parameterName, cachedValue);
  284.           }
  285.         }
  286.       }
  287.     }
  288.   }

  289.   private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  290.     List<E> list;
  291.     localCache.putObject(key, EXECUTION_PLACEHOLDER);
  292.     try {
  293.       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  294.     } finally {
  295.       localCache.removeObject(key);
  296.     }
  297.     localCache.putObject(key, list);
  298.     if (ms.getStatementType() == StatementType.CALLABLE) {
  299.       localOutputParameterCache.putObject(key, parameter);
  300.     }
  301.     return list;
  302.   }

  303.   protected Connection getConnection(Log statementLog) throws SQLException {
  304.     Connection connection = transaction.getConnection();
  305.     if (statementLog.isDebugEnabled()) {
  306.       return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  307.     } else {
  308.       return connection;
  309.     }
  310.   }

  311.   @Override
  312.   public void setExecutorWrapper(Executor wrapper) {
  313.     this.wrapper = wrapper;
  314.   }

  315.   private static class DeferredLoad {

  316.     private final MetaObject resultObject;
  317.     private final String property;
  318.     private final Class<?> targetType;
  319.     private final CacheKey key;
  320.     private final PerpetualCache localCache;
  321.     private final ObjectFactory objectFactory;
  322.     private final ResultExtractor resultExtractor;

  323.     // issue #781
  324.     public DeferredLoad(MetaObject resultObject,
  325.                         String property,
  326.                         CacheKey key,
  327.                         PerpetualCache localCache,
  328.                         Configuration configuration,
  329.                         Class<?> targetType) {
  330.       this.resultObject = resultObject;
  331.       this.property = property;
  332.       this.key = key;
  333.       this.localCache = localCache;
  334.       this.objectFactory = configuration.getObjectFactory();
  335.       this.resultExtractor = new ResultExtractor(configuration, objectFactory);
  336.       this.targetType = targetType;
  337.     }

  338.     public boolean canLoad() {
  339.       return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
  340.     }

  341.     public void load() {
  342.       @SuppressWarnings("unchecked")
  343.       // we suppose we get back a List
  344.       List<Object> list = (List<Object>) localCache.getObject(key);
  345.       Object value = resultExtractor.extractObjectFromList(list, targetType);
  346.       resultObject.setValue(property, value);
  347.     }

  348.   }

  349. }