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) 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
166 deferredLoads.clear();
167 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
168
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
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
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
286 }
287 }
288 }
289
290
291
292
293
294
295
296
297
298
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
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
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 }