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.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
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
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
164 deferredLoads.clear();
165 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
166
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
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
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
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
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
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 }