View Javadoc
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  
18  import static org.apache.ibatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;
19  
20  import java.sql.Connection;
21  import java.sql.SQLException;
22  import java.sql.Statement;
23  import java.util.List;
24  import java.util.concurrent.ConcurrentLinkedQueue;
25  
26  import org.apache.ibatis.cache.CacheKey;
27  import org.apache.ibatis.cache.impl.PerpetualCache;
28  import org.apache.ibatis.cursor.Cursor;
29  import org.apache.ibatis.executor.statement.StatementUtil;
30  import org.apache.ibatis.logging.Log;
31  import org.apache.ibatis.logging.LogFactory;
32  import org.apache.ibatis.logging.jdbc.ConnectionLogger;
33  import org.apache.ibatis.mapping.BoundSql;
34  import org.apache.ibatis.mapping.MappedStatement;
35  import org.apache.ibatis.mapping.ParameterMapping;
36  import org.apache.ibatis.mapping.ParameterMode;
37  import org.apache.ibatis.mapping.StatementType;
38  import org.apache.ibatis.reflection.MetaObject;
39  import org.apache.ibatis.reflection.factory.ObjectFactory;
40  import org.apache.ibatis.session.Configuration;
41  import org.apache.ibatis.session.LocalCacheScope;
42  import org.apache.ibatis.session.ResultHandler;
43  import org.apache.ibatis.session.RowBounds;
44  import org.apache.ibatis.transaction.Transaction;
45  import org.apache.ibatis.type.TypeHandlerRegistry;
46  
47  /**
48   * @author Clinton Begin
49   */
50  public abstract class BaseExecutor implements Executor {
51  
52    private static final Log log = LogFactory.getLog(BaseExecutor.class);
53  
54    protected Transaction transaction;
55    protected Executor wrapper;
56  
57    protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
58    protected PerpetualCache localCache;
59    protected PerpetualCache localOutputParameterCache;
60    protected Configuration configuration;
61  
62    protected int queryStack;
63    private boolean closed;
64  
65    protected BaseExecutor(Configuration configuration, Transaction transaction) {
66      this.transaction = transaction;
67      this.deferredLoads = new ConcurrentLinkedQueue<>();
68      this.localCache = new PerpetualCache("LocalCache");
69      this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
70      this.closed = false;
71      this.configuration = configuration;
72      this.wrapper = this;
73    }
74  
75    @Override
76    public Transaction getTransaction() {
77      if (closed) {
78        throw new ExecutorException("Executor was closed.");
79      }
80      return transaction;
81    }
82  
83    @Override
84    public void close(boolean forceRollback) {
85      try {
86        try {
87          rollback(forceRollback);
88        } finally {
89          if (transaction != null) {
90            transaction.close();
91          }
92        }
93      } catch (SQLException e) {
94        // Ignore. There's nothing that can be done at this point.
95        log.warn("Unexpected exception on closing transaction.  Cause: " + e);
96      } finally {
97        transaction = null;
98        deferredLoads = null;
99        localCache = null;
100       localOutputParameterCache = null;
101       closed = true;
102     }
103   }
104 
105   @Override
106   public boolean isClosed() {
107     return closed;
108   }
109 
110   @Override
111   public int update(MappedStatement ms, Object parameter) throws SQLException {
112     ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
113     if (closed) {
114       throw new ExecutorException("Executor was closed.");
115     }
116     clearLocalCache();
117     return doUpdate(ms, parameter);
118   }
119 
120   @Override
121   public List<BatchResult> flushStatements() throws SQLException {
122     return flushStatements(false);
123   }
124 
125   public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
126     if (closed) {
127       throw new ExecutorException("Executor was closed.");
128     }
129     return doFlushStatements(isRollBack);
130   }
131 
132   @Override
133   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
134     BoundSql boundSql = ms.getBoundSql(parameter);
135     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
136     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
137   }
138 
139   @SuppressWarnings("unchecked")
140   @Override
141   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
142     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
143     if (closed) {
144       throw new ExecutorException("Executor was closed.");
145     }
146     if (queryStack == 0 && ms.isFlushCacheRequired()) {
147       clearLocalCache();
148     }
149     List<E> list;
150     try {
151       queryStack++;
152       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
153       if (list != null) {
154         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
155       } else {
156         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
157       }
158     } finally {
159       queryStack--;
160     }
161     if (queryStack == 0) {
162       for (DeferredLoad deferredLoad : deferredLoads) {
163         deferredLoad.load();
164       }
165       // issue #601
166       deferredLoads.clear();
167       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
168         // issue #482
169         clearLocalCache();
170       }
171     }
172     return list;
173   }
174 
175   @Override
176   public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
177     BoundSql boundSql = ms.getBoundSql(parameter);
178     return doQueryCursor(ms, parameter, rowBounds, boundSql);
179   }
180 
181   @Override
182   public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
183     if (closed) {
184       throw new ExecutorException("Executor was closed.");
185     }
186     DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
187     if (deferredLoad.canLoad()) {
188       deferredLoad.load();
189     } else {
190       deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
191     }
192   }
193 
194   @Override
195   public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
196     if (closed) {
197       throw new ExecutorException("Executor was closed.");
198     }
199     CacheKey cacheKey = new CacheKey();
200     cacheKey.update(ms.getId());
201     cacheKey.update(rowBounds.getOffset());
202     cacheKey.update(rowBounds.getLimit());
203     cacheKey.update(boundSql.getSql());
204     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
205     TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
206     // mimic DefaultParameterHandler logic
207     for (ParameterMapping parameterMapping : parameterMappings) {
208       if (parameterMapping.getMode() != ParameterMode.OUT) {
209         Object value;
210         String propertyName = parameterMapping.getProperty();
211         if (boundSql.hasAdditionalParameter(propertyName)) {
212           value = boundSql.getAdditionalParameter(propertyName);
213         } else if (parameterObject == null) {
214           value = null;
215         } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
216           value = parameterObject;
217         } else {
218           MetaObject metaObject = configuration.newMetaObject(parameterObject);
219           value = metaObject.getValue(propertyName);
220         }
221         cacheKey.update(value);
222       }
223     }
224     if (configuration.getEnvironment() != null) {
225       // issue #176
226       cacheKey.update(configuration.getEnvironment().getId());
227     }
228     return cacheKey;
229   }
230 
231   @Override
232   public boolean isCached(MappedStatement ms, CacheKey key) {
233     return localCache.getObject(key) != null;
234   }
235 
236   @Override
237   public void commit(boolean required) throws SQLException {
238     if (closed) {
239       throw new ExecutorException("Cannot commit, transaction is already closed");
240     }
241     clearLocalCache();
242     flushStatements();
243     if (required) {
244       transaction.commit();
245     }
246   }
247 
248   @Override
249   public void rollback(boolean required) throws SQLException {
250     if (!closed) {
251       try {
252         clearLocalCache();
253         flushStatements(true);
254       } finally {
255         if (required) {
256           transaction.rollback();
257         }
258       }
259     }
260   }
261 
262   @Override
263   public void clearLocalCache() {
264     if (!closed) {
265       localCache.clear();
266       localOutputParameterCache.clear();
267     }
268   }
269 
270   protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
271 
272   protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
273 
274   protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
275       throws SQLException;
276 
277   protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
278       throws SQLException;
279 
280   protected void closeStatement(Statement statement) {
281     if (statement != null) {
282       try {
283         statement.close();
284       } catch (SQLException e) {
285         // ignore
286       }
287     }
288   }
289 
290   /**
291    * Apply a transaction timeout.
292    *
293    * @param statement
294    *          a current statement
295    * @throws SQLException
296    *           if a database access error occurs, this method is called on a closed <code>Statement</code>
297    * @since 3.4.0
298    * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
299    */
300   protected void applyTransactionTimeout(Statement statement) throws SQLException {
301     StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
302   }
303 
304   private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
305     if (ms.getStatementType() == StatementType.CALLABLE) {
306       final Object cachedParameter = localOutputParameterCache.getObject(key);
307       if (cachedParameter != null && parameter != null) {
308         final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
309         final MetaObject metaParameter = configuration.newMetaObject(parameter);
310         for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
311           if (parameterMapping.getMode() != ParameterMode.IN) {
312             final String parameterName = parameterMapping.getProperty();
313             final Object cachedValue = metaCachedParameter.getValue(parameterName);
314             metaParameter.setValue(parameterName, cachedValue);
315           }
316         }
317       }
318     }
319   }
320 
321   private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
322     List<E> list;
323     localCache.putObject(key, EXECUTION_PLACEHOLDER);
324     try {
325       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
326     } finally {
327       localCache.removeObject(key);
328     }
329     localCache.putObject(key, list);
330     if (ms.getStatementType() == StatementType.CALLABLE) {
331       localOutputParameterCache.putObject(key, parameter);
332     }
333     return list;
334   }
335 
336   protected Connection getConnection(Log statementLog) throws SQLException {
337     Connection connection = transaction.getConnection();
338     if (statementLog.isDebugEnabled()) {
339       return ConnectionLogger.newInstance(connection, statementLog, queryStack);
340     } else {
341       return connection;
342     }
343   }
344 
345   @Override
346   public void setExecutorWrapper(Executor wrapper) {
347     this.wrapper = wrapper;
348   }
349 
350   private static class DeferredLoad {
351 
352     private final MetaObject resultObject;
353     private final String property;
354     private final Class<?> targetType;
355     private final CacheKey key;
356     private final PerpetualCache localCache;
357     private final ObjectFactory objectFactory;
358     private final ResultExtractor resultExtractor;
359 
360     // issue #781
361     public DeferredLoad(MetaObject resultObject,
362                         String property,
363                         CacheKey key,
364                         PerpetualCache localCache,
365                         Configuration configuration,
366                         Class<?> targetType) {
367       this.resultObject = resultObject;
368       this.property = property;
369       this.key = key;
370       this.localCache = localCache;
371       this.objectFactory = configuration.getObjectFactory();
372       this.resultExtractor = new ResultExtractor(configuration, objectFactory);
373       this.targetType = targetType;
374     }
375 
376     public boolean canLoad() {
377       return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
378     }
379 
380     public void load() {
381       @SuppressWarnings("unchecked")
382       // we suppose we get back a List
383       List<Object> list = (List<Object>) localCache.getObject(key);
384       Object value = resultExtractor.extractObjectFromList(list, targetType);
385       resultObject.setValue(property, value);
386     }
387 
388   }
389 
390 }