View Javadoc
1   /**
2    * Copyright 2010-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.mybatis.spring;
17  
18  import static java.lang.reflect.Proxy.newProxyInstance;
19  import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
20  import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;
21  import static org.mybatis.spring.SqlSessionUtils.getSqlSession;
22  import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;
23  import static org.springframework.util.Assert.notNull;
24  
25  import java.lang.reflect.InvocationHandler;
26  import java.lang.reflect.Method;
27  import java.sql.Connection;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.ibatis.cursor.Cursor;
32  import org.apache.ibatis.exceptions.PersistenceException;
33  import org.apache.ibatis.executor.BatchResult;
34  import org.apache.ibatis.session.Configuration;
35  import org.apache.ibatis.session.ExecutorType;
36  import org.apache.ibatis.session.ResultHandler;
37  import org.apache.ibatis.session.RowBounds;
38  import org.apache.ibatis.session.SqlSession;
39  import org.apache.ibatis.session.SqlSessionFactory;
40  import org.springframework.beans.factory.DisposableBean;
41  import org.springframework.dao.support.PersistenceExceptionTranslator;
42  
43  /**
44   * Thread safe, Spring managed, {@code SqlSession} that works with Spring transaction management to ensure that that the
45   * actual SqlSession used is the one associated with the current Spring transaction. In addition, it manages the session
46   * life-cycle, including closing, committing or rolling back the session as necessary based on the Spring transaction
47   * configuration.
48   * <p>
49   * The template needs a SqlSessionFactory to create SqlSessions, passed as a constructor argument. It also can be
50   * constructed indicating the executor type to be used, if not, the default executor type, defined in the session
51   * factory will be used.
52   * <p>
53   * This template converts MyBatis PersistenceExceptions into unchecked DataAccessExceptions, using, by default, a
54   * {@code MyBatisExceptionTranslator}.
55   * <p>
56   * Because SqlSessionTemplate is thread safe, a single instance can be shared by all DAOs; there should also be a small
57   * memory savings by doing this. This pattern can be used in Spring configuration files as follows:
58   *
59   * <pre class="code">
60   * {@code
61   * <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
62   *   <constructor-arg ref="sqlSessionFactory" />
63   * </bean>
64   * }
65   * </pre>
66   *
67   * @author Putthiphong Boonphong
68   * @author Hunter Presnall
69   * @author Eduardo Macarron
70   *
71   * @see SqlSessionFactory
72   * @see MyBatisExceptionTranslator
73   */
74  public class SqlSessionTemplate implements SqlSession, DisposableBean {
75  
76    private final SqlSessionFactory sqlSessionFactory;
77  
78    private final ExecutorType executorType;
79  
80    private final SqlSession sqlSessionProxy;
81  
82    private final PersistenceExceptionTranslator exceptionTranslator;
83  
84    /**
85     * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} provided as an argument.
86     *
87     * @param sqlSessionFactory
88     *          a factory of SqlSession
89     */
90    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
91      this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
92    }
93  
94    /**
95     * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} provided as an argument and the given
96     * {@code ExecutorType} {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate} is constructed.
97     *
98     * @param sqlSessionFactory
99     *          a factory of SqlSession
100    * @param executorType
101    *          an executor type on session
102    */
103   public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
104     this(sqlSessionFactory, executorType,
105         new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
106   }
107 
108   /**
109    * Constructs a Spring managed {@code SqlSession} with the given {@code SqlSessionFactory} and {@code ExecutorType}. A
110    * custom {@code SQLExceptionTranslator} can be provided as an argument so any {@code PersistenceException} thrown by
111    * MyBatis can be custom translated to a {@code RuntimeException} The {@code SQLExceptionTranslator} can also be null
112    * and thus no exception translation will be done and MyBatis exceptions will be thrown
113    *
114    * @param sqlSessionFactory
115    *          a factory of SqlSession
116    * @param executorType
117    *          an executor type on session
118    * @param exceptionTranslator
119    *          a translator of exception
120    */
121   public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
122       PersistenceExceptionTranslator exceptionTranslator) {
123 
124     notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
125     notNull(executorType, "Property 'executorType' is required");
126 
127     this.sqlSessionFactory = sqlSessionFactory;
128     this.executorType = executorType;
129     this.exceptionTranslator = exceptionTranslator;
130     this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
131         new Class[] { SqlSession.class }, new SqlSessionInterceptor());
132   }
133 
134   public SqlSessionFactory getSqlSessionFactory() {
135     return this.sqlSessionFactory;
136   }
137 
138   public ExecutorType getExecutorType() {
139     return this.executorType;
140   }
141 
142   public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
143     return this.exceptionTranslator;
144   }
145 
146   /**
147    * {@inheritDoc}
148    */
149   @Override
150   public <T> T selectOne(String statement) {
151     return this.sqlSessionProxy.selectOne(statement);
152   }
153 
154   /**
155    * {@inheritDoc}
156    */
157   @Override
158   public <T> T selectOne(String statement, Object parameter) {
159     return this.sqlSessionProxy.selectOne(statement, parameter);
160   }
161 
162   /**
163    * {@inheritDoc}
164    */
165   @Override
166   public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
167     return this.sqlSessionProxy.selectMap(statement, mapKey);
168   }
169 
170   /**
171    * {@inheritDoc}
172    */
173   @Override
174   public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
175     return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);
176   }
177 
178   /**
179    * {@inheritDoc}
180    */
181   @Override
182   public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
183     return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
184   }
185 
186   /**
187    * {@inheritDoc}
188    */
189   @Override
190   public <T> Cursor<T> selectCursor(String statement) {
191     return this.sqlSessionProxy.selectCursor(statement);
192   }
193 
194   /**
195    * {@inheritDoc}
196    */
197   @Override
198   public <T> Cursor<T> selectCursor(String statement, Object parameter) {
199     return this.sqlSessionProxy.selectCursor(statement, parameter);
200   }
201 
202   /**
203    * {@inheritDoc}
204    */
205   @Override
206   public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
207     return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
208   }
209 
210   /**
211    * {@inheritDoc}
212    */
213   @Override
214   public <E> List<E> selectList(String statement) {
215     return this.sqlSessionProxy.selectList(statement);
216   }
217 
218   /**
219    * {@inheritDoc}
220    */
221   @Override
222   public <E> List<E> selectList(String statement, Object parameter) {
223     return this.sqlSessionProxy.selectList(statement, parameter);
224   }
225 
226   /**
227    * {@inheritDoc}
228    */
229   @Override
230   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
231     return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
232   }
233 
234   /**
235    * {@inheritDoc}
236    */
237   @Override
238   public void select(String statement, ResultHandler handler) {
239     this.sqlSessionProxy.select(statement, handler);
240   }
241 
242   /**
243    * {@inheritDoc}
244    */
245   @Override
246   public void select(String statement, Object parameter, ResultHandler handler) {
247     this.sqlSessionProxy.select(statement, parameter, handler);
248   }
249 
250   /**
251    * {@inheritDoc}
252    */
253   @Override
254   public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
255     this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
256   }
257 
258   /**
259    * {@inheritDoc}
260    */
261   @Override
262   public int insert(String statement) {
263     return this.sqlSessionProxy.insert(statement);
264   }
265 
266   /**
267    * {@inheritDoc}
268    */
269   @Override
270   public int insert(String statement, Object parameter) {
271     return this.sqlSessionProxy.insert(statement, parameter);
272   }
273 
274   /**
275    * {@inheritDoc}
276    */
277   @Override
278   public int update(String statement) {
279     return this.sqlSessionProxy.update(statement);
280   }
281 
282   /**
283    * {@inheritDoc}
284    */
285   @Override
286   public int update(String statement, Object parameter) {
287     return this.sqlSessionProxy.update(statement, parameter);
288   }
289 
290   /**
291    * {@inheritDoc}
292    */
293   @Override
294   public int delete(String statement) {
295     return this.sqlSessionProxy.delete(statement);
296   }
297 
298   /**
299    * {@inheritDoc}
300    */
301   @Override
302   public int delete(String statement, Object parameter) {
303     return this.sqlSessionProxy.delete(statement, parameter);
304   }
305 
306   /**
307    * {@inheritDoc}
308    */
309   @Override
310   public <T> T getMapper(Class<T> type) {
311     return getConfiguration().getMapper(type, this);
312   }
313 
314   /**
315    * {@inheritDoc}
316    */
317   @Override
318   public void commit() {
319     throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
320   }
321 
322   /**
323    * {@inheritDoc}
324    */
325   @Override
326   public void commit(boolean force) {
327     throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
328   }
329 
330   /**
331    * {@inheritDoc}
332    */
333   @Override
334   public void rollback() {
335     throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
336   }
337 
338   /**
339    * {@inheritDoc}
340    */
341   @Override
342   public void rollback(boolean force) {
343     throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
344   }
345 
346   /**
347    * {@inheritDoc}
348    */
349   @Override
350   public void close() {
351     throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
352   }
353 
354   /**
355    * {@inheritDoc}
356    */
357   @Override
358   public void clearCache() {
359     this.sqlSessionProxy.clearCache();
360   }
361 
362   /**
363    * {@inheritDoc}
364    *
365    */
366   @Override
367   public Configuration getConfiguration() {
368     return this.sqlSessionFactory.getConfiguration();
369   }
370 
371   /**
372    * {@inheritDoc}
373    */
374   @Override
375   public Connection getConnection() {
376     return this.sqlSessionProxy.getConnection();
377   }
378 
379   /**
380    * {@inheritDoc}
381    *
382    * @since 1.0.2
383    *
384    */
385   @Override
386   public List<BatchResult> flushStatements() {
387     return this.sqlSessionProxy.flushStatements();
388   }
389 
390   /**
391    * Allow gently dispose bean:
392    *
393    * <pre>
394    * {@code
395    *
396    * <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
397    *  <constructor-arg index="0" ref="sqlSessionFactory" />
398    * </bean>
399    * }
400    * </pre>
401    *
402    * The implementation of {@link DisposableBean} forces spring context to use {@link DisposableBean#destroy()} method
403    * instead of {@link SqlSessionTemplate#close()} to shutdown gently.
404    *
405    * @see SqlSessionTemplate#close()
406    * @see "org.springframework.beans.factory.support.DisposableBeanAdapter#inferDestroyMethodIfNecessary(Object, RootBeanDefinition)"
407    * @see "org.springframework.beans.factory.support.DisposableBeanAdapter#CLOSE_METHOD_NAME"
408    */
409   @Override
410   public void destroy() throws Exception {
411     // This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives
412     // UnsupportedOperationException
413   }
414 
415   /**
416    * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
417    * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
418    * {@code PersistenceExceptionTranslator}.
419    */
420   private class SqlSessionInterceptor implements InvocationHandler {
421     @Override
422     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
423       SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
424           SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
425       try {
426         Object result = method.invoke(sqlSession, args);
427         if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
428           // force commit even on non-dirty sessions because some databases require
429           // a commit/rollback before calling close()
430           sqlSession.commit(true);
431         }
432         return result;
433       } catch (Throwable t) {
434         Throwable unwrapped = unwrapThrowable(t);
435         if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
436           // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
437           closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
438           sqlSession = null;
439           Throwable translated = SqlSessionTemplate.this.exceptionTranslator
440               .translateExceptionIfPossible((PersistenceException) unwrapped);
441           if (translated != null) {
442             unwrapped = translated;
443           }
444         }
445         throw unwrapped;
446       } finally {
447         if (sqlSession != null) {
448           closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
449         }
450       }
451     }
452   }
453 
454 }