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