1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
168 deferredLoads.clear();
169 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
170
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
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
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
292 }
293 }
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
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
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
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 }