View Javadoc
1   /**
2    *    Copyright 2009-2019 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.session.defaults;
17  
18  import java.io.IOException;
19  import java.sql.Connection;
20  import java.sql.SQLException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.ibatis.binding.BindingException;
28  import org.apache.ibatis.cursor.Cursor;
29  import org.apache.ibatis.exceptions.ExceptionFactory;
30  import org.apache.ibatis.exceptions.TooManyResultsException;
31  import org.apache.ibatis.executor.BatchResult;
32  import org.apache.ibatis.executor.ErrorContext;
33  import org.apache.ibatis.executor.Executor;
34  import org.apache.ibatis.executor.result.DefaultMapResultHandler;
35  import org.apache.ibatis.executor.result.DefaultResultContext;
36  import org.apache.ibatis.mapping.MappedStatement;
37  import org.apache.ibatis.session.Configuration;
38  import org.apache.ibatis.session.ResultHandler;
39  import org.apache.ibatis.session.RowBounds;
40  import org.apache.ibatis.session.SqlSession;
41  
42  /**
43   * The default implementation for {@link SqlSession}.
44   * Note that this class is not Thread-Safe.
45   *
46   * @author Clinton Begin
47   */
48  public class DefaultSqlSession implements SqlSession {
49  
50    private final Configuration configuration;
51    private final Executor executor;
52  
53    private final boolean autoCommit;
54    private boolean dirty;
55    private List<Cursor<?>> cursorList;
56  
57    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
58      this.configuration = configuration;
59      this.executor = executor;
60      this.dirty = false;
61      this.autoCommit = autoCommit;
62    }
63  
64    public DefaultSqlSession(Configuration configuration, Executor executor) {
65      this(configuration, executor, false);
66    }
67  
68    @Override
69    public <T> T selectOne(String statement) {
70      return this.selectOne(statement, null);
71    }
72  
73    @Override
74    public <T> T selectOne(String statement, Object parameter) {
75      // Popular vote was to return null on 0 results and throw exception on too many.
76      List<T> list = this.selectList(statement, parameter);
77      if (list.size() == 1) {
78        return list.get(0);
79      } else if (list.size() > 1) {
80        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
81      } else {
82        return null;
83      }
84    }
85  
86    @Override
87    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
88      return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
89    }
90  
91    @Override
92    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
93      return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
94    }
95  
96    @Override
97    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
98      final List<? extends V> list = selectList(statement, parameter, rowBounds);
99      final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
100             configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
101     final DefaultResultContext<V> context = new DefaultResultContext<>();
102     for (V o : list) {
103       context.nextResultObject(o);
104       mapResultHandler.handleResult(context);
105     }
106     return mapResultHandler.getMappedResults();
107   }
108 
109   @Override
110   public <T> Cursor<T> selectCursor(String statement) {
111     return selectCursor(statement, null);
112   }
113 
114   @Override
115   public <T> Cursor<T> selectCursor(String statement, Object parameter) {
116     return selectCursor(statement, parameter, RowBounds.DEFAULT);
117   }
118 
119   @Override
120   public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
121     try {
122       MappedStatement ms = configuration.getMappedStatement(statement);
123       Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
124       registerCursor(cursor);
125       return cursor;
126     } catch (Exception e) {
127       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
128     } finally {
129       ErrorContext.instance().reset();
130     }
131   }
132 
133   @Override
134   public <E> List<E> selectList(String statement) {
135     return this.selectList(statement, null);
136   }
137 
138   @Override
139   public <E> List<E> selectList(String statement, Object parameter) {
140     return this.selectList(statement, parameter, RowBounds.DEFAULT);
141   }
142 
143   @Override
144   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
145     try {
146       MappedStatement ms = configuration.getMappedStatement(statement);
147       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
148     } catch (Exception e) {
149       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
150     } finally {
151       ErrorContext.instance().reset();
152     }
153   }
154 
155   @Override
156   public void select(String statement, Object parameter, ResultHandler handler) {
157     select(statement, parameter, RowBounds.DEFAULT, handler);
158   }
159 
160   @Override
161   public void select(String statement, ResultHandler handler) {
162     select(statement, null, RowBounds.DEFAULT, handler);
163   }
164 
165   @Override
166   public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
167     try {
168       MappedStatement ms = configuration.getMappedStatement(statement);
169       executor.query(ms, wrapCollection(parameter), rowBounds, handler);
170     } catch (Exception e) {
171       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
172     } finally {
173       ErrorContext.instance().reset();
174     }
175   }
176 
177   @Override
178   public int insert(String statement) {
179     return insert(statement, null);
180   }
181 
182   @Override
183   public int insert(String statement, Object parameter) {
184     return update(statement, parameter);
185   }
186 
187   @Override
188   public int update(String statement) {
189     return update(statement, null);
190   }
191 
192   @Override
193   public int update(String statement, Object parameter) {
194     try {
195       dirty = true;
196       MappedStatement ms = configuration.getMappedStatement(statement);
197       return executor.update(ms, wrapCollection(parameter));
198     } catch (Exception e) {
199       throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
200     } finally {
201       ErrorContext.instance().reset();
202     }
203   }
204 
205   @Override
206   public int delete(String statement) {
207     return update(statement, null);
208   }
209 
210   @Override
211   public int delete(String statement, Object parameter) {
212     return update(statement, parameter);
213   }
214 
215   @Override
216   public void commit() {
217     commit(false);
218   }
219 
220   @Override
221   public void commit(boolean force) {
222     try {
223       executor.commit(isCommitOrRollbackRequired(force));
224       dirty = false;
225     } catch (Exception e) {
226       throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
227     } finally {
228       ErrorContext.instance().reset();
229     }
230   }
231 
232   @Override
233   public void rollback() {
234     rollback(false);
235   }
236 
237   @Override
238   public void rollback(boolean force) {
239     try {
240       executor.rollback(isCommitOrRollbackRequired(force));
241       dirty = false;
242     } catch (Exception e) {
243       throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
244     } finally {
245       ErrorContext.instance().reset();
246     }
247   }
248 
249   @Override
250   public List<BatchResult> flushStatements() {
251     try {
252       return executor.flushStatements();
253     } catch (Exception e) {
254       throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
255     } finally {
256       ErrorContext.instance().reset();
257     }
258   }
259 
260   @Override
261   public void close() {
262     try {
263       executor.close(isCommitOrRollbackRequired(false));
264       closeCursors();
265       dirty = false;
266     } finally {
267       ErrorContext.instance().reset();
268     }
269   }
270 
271   private void closeCursors() {
272     if (cursorList != null && !cursorList.isEmpty()) {
273       for (Cursor<?> cursor : cursorList) {
274         try {
275           cursor.close();
276         } catch (IOException e) {
277           throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);
278         }
279       }
280       cursorList.clear();
281     }
282   }
283 
284   @Override
285   public Configuration getConfiguration() {
286     return configuration;
287   }
288 
289   @Override
290   public <T> T getMapper(Class<T> type) {
291     return configuration.getMapper(type, this);
292   }
293 
294   @Override
295   public Connection getConnection() {
296     try {
297       return executor.getTransaction().getConnection();
298     } catch (SQLException e) {
299       throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
300     }
301   }
302 
303   @Override
304   public void clearCache() {
305     executor.clearLocalCache();
306   }
307 
308   private <T> void registerCursor(Cursor<T> cursor) {
309     if (cursorList == null) {
310       cursorList = new ArrayList<>();
311     }
312     cursorList.add(cursor);
313   }
314 
315   private boolean isCommitOrRollbackRequired(boolean force) {
316     return (!autoCommit && dirty) || force;
317   }
318 
319   private Object wrapCollection(final Object object) {
320     if (object instanceof Collection) {
321       StrictMap<Object> map = new StrictMap<>();
322       map.put("collection", object);
323       if (object instanceof List) {
324         map.put("list", object);
325       }
326       return map;
327     } else if (object != null && object.getClass().isArray()) {
328       StrictMap<Object> map = new StrictMap<>();
329       map.put("array", object);
330       return map;
331     }
332     return object;
333   }
334 
335   public static class StrictMap<V> extends HashMap<String, V> {
336 
337     private static final long serialVersionUID = -5741767162221585340L;
338 
339     @Override
340     public V get(Object key) {
341       if (!super.containsKey(key)) {
342         throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
343       }
344       return super.get(key);
345     }
346 
347   }
348 
349 }