View Javadoc
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  
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)
134       throws SQLException {
135     BoundSql boundSql = ms.getBoundSql(parameter);
136     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
137     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
138   }
139 
140   @SuppressWarnings("unchecked")
141   @Override
142   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
143       CacheKey key, BoundSql boundSql) throws SQLException {
144     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
145     if (closed) {
146       throw new ExecutorException("Executor was closed.");
147     }
148     if (queryStack == 0 && ms.isFlushCacheRequired()) {
149       clearLocalCache();
150     }
151     List<E> list;
152     try {
153       queryStack++;
154       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
155       if (list != null) {
156         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
157       } else {
158         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
159       }
160     } finally {
161       queryStack--;
162     }
163     if (queryStack == 0) {
164       for (DeferredLoad deferredLoad : deferredLoads) {
165         deferredLoad.load();
166       }
167       // issue #601
168       deferredLoads.clear();
169       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
170         // issue #482
171         clearLocalCache();
172       }
173     }
174     return list;
175   }
176 
177   @Override
178   public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
179     BoundSql boundSql = ms.getBoundSql(parameter);
180     return doQueryCursor(ms, parameter, rowBounds, boundSql);
181   }
182 
183   @Override
184   public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
185       Class<?> targetType) {
186     if (closed) {
187       throw new ExecutorException("Executor was closed.");
188     }
189     DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
190     if (deferredLoad.canLoad()) {
191       deferredLoad.load();
192     } else {
193       deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
194     }
195   }
196 
197   @Override
198   public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
199     if (closed) {
200       throw new ExecutorException("Executor was closed.");
201     }
202     CacheKey cacheKey = new CacheKey();
203     cacheKey.update(ms.getId());
204     cacheKey.update(rowBounds.getOffset());
205     cacheKey.update(rowBounds.getLimit());
206     cacheKey.update(boundSql.getSql());
207     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
208     TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
209     // mimic DefaultParameterHandler logic
210     MetaObject metaObject = null;
211     for (ParameterMapping parameterMapping : parameterMappings) {
212       if (parameterMapping.getMode() != ParameterMode.OUT) {
213         Object value;
214         String propertyName = parameterMapping.getProperty();
215         if (boundSql.hasAdditionalParameter(propertyName)) {
216           value = boundSql.getAdditionalParameter(propertyName);
217         } else if (parameterObject == null) {
218           value = null;
219         } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
220           value = parameterObject;
221         } else {
222           if (metaObject == null) {
223             metaObject = configuration.newMetaObject(parameterObject);
224           }
225           value = metaObject.getValue(propertyName);
226         }
227         cacheKey.update(value);
228       }
229     }
230     if (configuration.getEnvironment() != null) {
231       // issue #176
232       cacheKey.update(configuration.getEnvironment().getId());
233     }
234     return cacheKey;
235   }
236 
237   @Override
238   public boolean isCached(MappedStatement ms, CacheKey key) {
239     return localCache.getObject(key) != null;
240   }
241 
242   @Override
243   public void commit(boolean required) throws SQLException {
244     if (closed) {
245       throw new ExecutorException("Cannot commit, transaction is already closed");
246     }
247     clearLocalCache();
248     flushStatements();
249     if (required) {
250       transaction.commit();
251     }
252   }
253 
254   @Override
255   public void rollback(boolean required) throws SQLException {
256     if (!closed) {
257       try {
258         clearLocalCache();
259         flushStatements(true);
260       } finally {
261         if (required) {
262           transaction.rollback();
263         }
264       }
265     }
266   }
267 
268   @Override
269   public void clearLocalCache() {
270     if (!closed) {
271       localCache.clear();
272       localOutputParameterCache.clear();
273     }
274   }
275 
276   protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
277 
278   protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
279 
280   protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
281       ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
282 
283   protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds,
284       BoundSql boundSql) throws SQLException;
285 
286   protected void closeStatement(Statement statement) {
287     if (statement != null) {
288       try {
289         statement.close();
290       } catch (SQLException e) {
291         // ignore
292       }
293     }
294   }
295 
296   /**
297    * Apply a transaction timeout.
298    *
299    * @param statement
300    *          a current statement
301    *
302    * @throws SQLException
303    *           if a database access error occurs, this method is called on a closed <code>Statement</code>
304    *
305    * @since 3.4.0
306    *
307    * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
308    */
309   protected void applyTransactionTimeout(Statement statement) throws SQLException {
310     StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
311   }
312 
313   private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter,
314       BoundSql boundSql) {
315     if (ms.getStatementType() == StatementType.CALLABLE) {
316       final Object cachedParameter = localOutputParameterCache.getObject(key);
317       if (cachedParameter != null && parameter != null) {
318         final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
319         final MetaObject metaParameter = configuration.newMetaObject(parameter);
320         for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
321           if (parameterMapping.getMode() != ParameterMode.IN) {
322             final String parameterName = parameterMapping.getProperty();
323             final Object cachedValue = metaCachedParameter.getValue(parameterName);
324             metaParameter.setValue(parameterName, cachedValue);
325           }
326         }
327       }
328     }
329   }
330 
331   private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
332       ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
333     List<E> list;
334     localCache.putObject(key, EXECUTION_PLACEHOLDER);
335     try {
336       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
337     } finally {
338       localCache.removeObject(key);
339     }
340     localCache.putObject(key, list);
341     if (ms.getStatementType() == StatementType.CALLABLE) {
342       localOutputParameterCache.putObject(key, parameter);
343     }
344     return list;
345   }
346 
347   protected Connection getConnection(Log statementLog) throws SQLException {
348     Connection connection = transaction.getConnection();
349     if (statementLog.isDebugEnabled()) {
350       return ConnectionLogger.newInstance(connection, statementLog, queryStack);
351     }
352     return connection;
353   }
354 
355   @Override
356   public void setExecutorWrapper(Executor wrapper) {
357     this.wrapper = wrapper;
358   }
359 
360   private static class DeferredLoad {
361 
362     private final MetaObject resultObject;
363     private final String property;
364     private final Class<?> targetType;
365     private final CacheKey key;
366     private final PerpetualCache localCache;
367     private final ObjectFactory objectFactory;
368     private final ResultExtractor resultExtractor;
369 
370     // issue #781
371     public DeferredLoad(MetaObject resultObject, String property, CacheKey key, PerpetualCache localCache,
372         Configuration configuration, Class<?> targetType) {
373       this.resultObject = resultObject;
374       this.property = property;
375       this.key = key;
376       this.localCache = localCache;
377       this.objectFactory = configuration.getObjectFactory();
378       this.resultExtractor = new ResultExtractor(configuration, objectFactory);
379       this.targetType = targetType;
380     }
381 
382     public boolean canLoad() {
383       return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
384     }
385 
386     public void load() {
387       @SuppressWarnings("unchecked")
388       // we suppose we get back a List
389       List<Object> list = (List<Object>) localCache.getObject(key);
390       Object value = resultExtractor.extractObjectFromList(list, targetType);
391       resultObject.setValue(property, value);
392     }
393 
394   }
395 
396 }