View Javadoc
1   /**
2    *    Copyright 2009-2019 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.html#CacheKey">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)
271       throws SQLException;
272 
273   protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
274       throws SQLException;
275 
276   protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
277       throws SQLException;
278 
279   protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
280       throws SQLException;
281 
282   protected void closeStatement(Statement statement) {
283     if (statement != null) {
284       try {
285         statement.close();
286       } catch (SQLException e) {
287         // ignore
288       }
289     }
290   }
291 
292   /**
293    * Apply a transaction timeout.
294    * @param statement a current statement
295    * @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
296    * @since 3.4.0
297    * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
298    */
299   protected void applyTransactionTimeout(Statement statement) throws SQLException {
300     StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
301   }
302 
303   private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
304     if (ms.getStatementType() == StatementType.CALLABLE) {
305       final Object cachedParameter = localOutputParameterCache.getObject(key);
306       if (cachedParameter != null && parameter != null) {
307         final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
308         final MetaObject metaParameter = configuration.newMetaObject(parameter);
309         for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
310           if (parameterMapping.getMode() != ParameterMode.IN) {
311             final String parameterName = parameterMapping.getProperty();
312             final Object cachedValue = metaCachedParameter.getValue(parameterName);
313             metaParameter.setValue(parameterName, cachedValue);
314           }
315         }
316       }
317     }
318   }
319 
320   private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
321     List<E> list;
322     localCache.putObject(key, EXECUTION_PLACEHOLDER);
323     try {
324       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
325     } finally {
326       localCache.removeObject(key);
327     }
328     localCache.putObject(key, list);
329     if (ms.getStatementType() == StatementType.CALLABLE) {
330       localOutputParameterCache.putObject(key, parameter);
331     }
332     return list;
333   }
334 
335   protected Connection getConnection(Log statementLog) throws SQLException {
336     Connection connection = transaction.getConnection();
337     if (statementLog.isDebugEnabled()) {
338       return ConnectionLogger.newInstance(connection, statementLog, queryStack);
339     } else {
340       return connection;
341     }
342   }
343 
344   @Override
345   public void setExecutorWrapper(Executor wrapper) {
346     this.wrapper = wrapper;
347   }
348 
349   private static class DeferredLoad {
350 
351     private final MetaObject resultObject;
352     private final String property;
353     private final Class<?> targetType;
354     private final CacheKey key;
355     private final PerpetualCache localCache;
356     private final ObjectFactory objectFactory;
357     private final ResultExtractor resultExtractor;
358 
359     // issue #781
360     public DeferredLoad(MetaObject resultObject,
361                         String property,
362                         CacheKey key,
363                         PerpetualCache localCache,
364                         Configuration configuration,
365                         Class<?> targetType) {
366       this.resultObject = resultObject;
367       this.property = property;
368       this.key = key;
369       this.localCache = localCache;
370       this.objectFactory = configuration.getObjectFactory();
371       this.resultExtractor = new ResultExtractor(configuration, objectFactory);
372       this.targetType = targetType;
373     }
374 
375     public boolean canLoad() {
376       return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
377     }
378 
379     public void load() {
380       @SuppressWarnings("unchecked")
381       // we suppose we get back a List
382       List<Object> list = (List<Object>) localCache.getObject(key);
383       Object value = resultExtractor.extractObjectFromList(list, targetType);
384       resultObject.setValue(property, value);
385     }
386 
387   }
388 
389 }