View Javadoc
1   /*
2    *    Copyright 2009-2023 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    *       https://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.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.ibatis.binding.BindingException;
27  import org.apache.ibatis.cursor.Cursor;
28  import org.apache.ibatis.exceptions.ExceptionFactory;
29  import org.apache.ibatis.exceptions.TooManyResultsException;
30  import org.apache.ibatis.executor.BatchResult;
31  import org.apache.ibatis.executor.ErrorContext;
32  import org.apache.ibatis.executor.Executor;
33  import org.apache.ibatis.executor.result.DefaultMapResultHandler;
34  import org.apache.ibatis.executor.result.DefaultResultContext;
35  import org.apache.ibatis.mapping.MappedStatement;
36  import org.apache.ibatis.reflection.ParamNameResolver;
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}. Note that this class is not Thread-Safe.
44   *
45   * @author Clinton Begin
46   */
47  public class DefaultSqlSession implements SqlSession {
48  
49    private final Configuration configuration;
50    private final Executor executor;
51  
52    private final boolean autoCommit;
53    private boolean dirty;
54    private List<Cursor<?>> cursorList;
55  
56    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
57      this.configuration = configuration;
58      this.executor = executor;
59      this.dirty = false;
60      this.autoCommit = autoCommit;
61    }
62  
63    public DefaultSqlSession(Configuration configuration, Executor executor) {
64      this(configuration, executor, false);
65    }
66  
67    @Override
68    public <T> T selectOne(String statement) {
69      return this.selectOne(statement, null);
70    }
71  
72    @Override
73    public <T> T selectOne(String statement, Object parameter) {
74      // Popular vote was to return null on 0 results and throw exception on too many.
75      List<T> list = this.selectList(statement, parameter);
76      if (list.size() == 1) {
77        return list.get(0);
78      }
79      if (list.size() > 1) {
80        throw new TooManyResultsException(
81            "Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
82      } else {
83        return null;
84      }
85    }
86  
87    @Override
88    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
89      return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
90    }
91  
92    @Override
93    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
94      return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
95    }
96  
97    @Override
98    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
99      final List<? extends V> list = selectList(statement, parameter, rowBounds);
100     final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
101         configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
102     final DefaultResultContext<V> context = new DefaultResultContext<>();
103     for (V o : list) {
104       context.nextResultObject(o);
105       mapResultHandler.handleResult(context);
106     }
107     return mapResultHandler.getMappedResults();
108   }
109 
110   @Override
111   public <T> Cursor<T> selectCursor(String statement) {
112     return selectCursor(statement, null);
113   }
114 
115   @Override
116   public <T> Cursor<T> selectCursor(String statement, Object parameter) {
117     return selectCursor(statement, parameter, RowBounds.DEFAULT);
118   }
119 
120   @Override
121   public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
122     try {
123       MappedStatement ms = configuration.getMappedStatement(statement);
124       dirty |= ms.isDirtySelect();
125       Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
126       registerCursor(cursor);
127       return cursor;
128     } catch (Exception e) {
129       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
130     } finally {
131       ErrorContext.instance().reset();
132     }
133   }
134 
135   @Override
136   public <E> List<E> selectList(String statement) {
137     return this.selectList(statement, null);
138   }
139 
140   @Override
141   public <E> List<E> selectList(String statement, Object parameter) {
142     return this.selectList(statement, parameter, RowBounds.DEFAULT);
143   }
144 
145   @Override
146   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
147     return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
148   }
149 
150   private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
151     try {
152       MappedStatement ms = configuration.getMappedStatement(statement);
153       dirty |= ms.isDirtySelect();
154       return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
155     } catch (Exception e) {
156       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
157     } finally {
158       ErrorContext.instance().reset();
159     }
160   }
161 
162   @Override
163   public void select(String statement, Object parameter, ResultHandler handler) {
164     select(statement, parameter, RowBounds.DEFAULT, handler);
165   }
166 
167   @Override
168   public void select(String statement, ResultHandler handler) {
169     select(statement, null, RowBounds.DEFAULT, handler);
170   }
171 
172   @Override
173   public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
174     selectList(statement, parameter, rowBounds, handler);
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     return ParamNameResolver.wrapToMapIfCollection(object, null);
321   }
322 
323   /**
324    * @deprecated Since 3.5.5
325    */
326   @Deprecated
327   public static class StrictMap<V> extends HashMap<String, V> {
328 
329     private static final long serialVersionUID = -5741767162221585340L;
330 
331     @Override
332     public V get(Object key) {
333       if (!super.containsKey(key)) {
334         throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
335       }
336       return super.get(key);
337     }
338 
339   }
340 
341 }