BaseExecutor.java

  1. /*
  2.  *    Copyright 2009-2023 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 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)
  118.       throws SQLException {
  119.     BoundSql boundSql = ms.getBoundSql(parameter);
  120.     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  121.     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  122.   }

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

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

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

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

  216.   @Override
  217.   public boolean isCached(MappedStatement ms, CacheKey key) {
  218.     return localCache.getObject(key) != null;
  219.   }

  220.   @Override
  221.   public void commit(boolean required) throws SQLException {
  222.     if (closed) {
  223.       throw new ExecutorException("Cannot commit, transaction is already closed");
  224.     }
  225.     clearLocalCache();
  226.     flushStatements();
  227.     if (required) {
  228.       transaction.commit();
  229.     }
  230.   }

  231.   @Override
  232.   public void rollback(boolean required) throws SQLException {
  233.     if (!closed) {
  234.       try {
  235.         clearLocalCache();
  236.         flushStatements(true);
  237.       } finally {
  238.         if (required) {
  239.           transaction.rollback();
  240.         }
  241.       }
  242.     }
  243.   }

  244.   @Override
  245.   public void clearLocalCache() {
  246.     if (!closed) {
  247.       localCache.clear();
  248.       localOutputParameterCache.clear();
  249.     }
  250.   }

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

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

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

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

  257.   protected void closeStatement(Statement statement) {
  258.     if (statement != null) {
  259.       try {
  260.         statement.close();
  261.       } catch (SQLException e) {
  262.         // ignore
  263.       }
  264.     }
  265.   }

  266.   /**
  267.    * Apply a transaction timeout.
  268.    *
  269.    * @param statement
  270.    *          a current statement
  271.    *
  272.    * @throws SQLException
  273.    *           if a database access error occurs, this method is called on a closed <code>Statement</code>
  274.    *
  275.    * @since 3.4.0
  276.    *
  277.    * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
  278.    */
  279.   protected void applyTransactionTimeout(Statement statement) throws SQLException {
  280.     StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  281.   }

  282.   private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter,
  283.       BoundSql boundSql) {
  284.     if (ms.getStatementType() == StatementType.CALLABLE) {
  285.       final Object cachedParameter = localOutputParameterCache.getObject(key);
  286.       if (cachedParameter != null && parameter != null) {
  287.         final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
  288.         final MetaObject metaParameter = configuration.newMetaObject(parameter);
  289.         for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
  290.           if (parameterMapping.getMode() != ParameterMode.IN) {
  291.             final String parameterName = parameterMapping.getProperty();
  292.             final Object cachedValue = metaCachedParameter.getValue(parameterName);
  293.             metaParameter.setValue(parameterName, cachedValue);
  294.           }
  295.         }
  296.       }
  297.     }
  298.   }

  299.   private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
  300.       ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  301.     List<E> list;
  302.     localCache.putObject(key, EXECUTION_PLACEHOLDER);
  303.     try {
  304.       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  305.     } finally {
  306.       localCache.removeObject(key);
  307.     }
  308.     localCache.putObject(key, list);
  309.     if (ms.getStatementType() == StatementType.CALLABLE) {
  310.       localOutputParameterCache.putObject(key, parameter);
  311.     }
  312.     return list;
  313.   }

  314.   protected Connection getConnection(Log statementLog) throws SQLException {
  315.     Connection connection = transaction.getConnection();
  316.     if (statementLog.isDebugEnabled()) {
  317.       return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  318.     }
  319.     return connection;
  320.   }

  321.   @Override
  322.   public void setExecutorWrapper(Executor wrapper) {
  323.     this.wrapper = wrapper;
  324.   }

  325.   private static class DeferredLoad {

  326.     private final MetaObject resultObject;
  327.     private final String property;
  328.     private final Class<?> targetType;
  329.     private final CacheKey key;
  330.     private final PerpetualCache localCache;
  331.     private final ObjectFactory objectFactory;
  332.     private final ResultExtractor resultExtractor;

  333.     // issue #781
  334.     public DeferredLoad(MetaObject resultObject, String property, CacheKey key, PerpetualCache localCache,
  335.         Configuration configuration, Class<?> targetType) {
  336.       this.resultObject = resultObject;
  337.       this.property = property;
  338.       this.key = key;
  339.       this.localCache = localCache;
  340.       this.objectFactory = configuration.getObjectFactory();
  341.       this.resultExtractor = new ResultExtractor(configuration, objectFactory);
  342.       this.targetType = targetType;
  343.     }

  344.     public boolean canLoad() {
  345.       return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
  346.     }

  347.     public void load() {
  348.       @SuppressWarnings("unchecked")
  349.       // we suppose we get back a List
  350.       List<Object> list = (List<Object>) localCache.getObject(key);
  351.       Object value = resultExtractor.extractObjectFromList(list, targetType);
  352.       resultObject.setValue(property, value);
  353.     }

  354.   }

  355. }