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