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