1 /*
2 * Copyright 2002-2009 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
17 package org.springframework.orm.hibernate3;
18
19 import java.io.Serializable;
20 import java.lang.reflect.InvocationHandler;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Proxy;
24 import java.sql.SQLException;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.List;
28
29 import org.hibernate.Criteria;
30 import org.hibernate.Filter;
31 import org.hibernate.FlushMode;
32 import org.hibernate.Hibernate;
33 import org.hibernate.HibernateException;
34 import org.hibernate.LockMode;
35 import org.hibernate.Query;
36 import org.hibernate.ReplicationMode;
37 import org.hibernate.Session;
38 import org.hibernate.SessionFactory;
39 import org.hibernate.criterion.DetachedCriteria;
40 import org.hibernate.criterion.Example;
41 import org.hibernate.engine.SessionImplementor;
42 import org.hibernate.event.EventSource;
43
44 import org.springframework.dao.DataAccessException;
45 import org.springframework.dao.DataAccessResourceFailureException;
46 import org.springframework.dao.InvalidDataAccessApiUsageException;
47 import org.springframework.util.Assert;
48
49 /**
50 * Helper class that simplifies Hibernate data access code. Automatically
51 * converts HibernateExceptions into DataAccessExceptions, following the
52 * <code>org.springframework.dao</code> exception hierarchy.
53 *
54 * <p>The central method is <code>execute</code>, supporting Hibernate access code
55 * implementing the {@link HibernateCallback} interface. It provides Hibernate Session
56 * handling such that neither the HibernateCallback implementation nor the calling
57 * code needs to explicitly care about retrieving/closing Hibernate Sessions,
58 * or handling Session lifecycle exceptions. For typical single step actions,
59 * there are various convenience methods (find, load, saveOrUpdate, delete).
60 *
61 * <p>Can be used within a service implementation via direct instantiation
62 * with a SessionFactory reference, or get prepared in an application context
63 * and given to services as bean reference. Note: The SessionFactory should
64 * always be configured as bean in the application context, in the first case
65 * given to the service directly, in the second case to the prepared template.
66 *
67 * <p><b>NOTE: As of Hibernate 3.0.1, transactional Hibernate access code can
68 * also be coded in plain Hibernate style. Hence, for newly started projects,
69 * consider adopting the standard Hibernate3 style of coding data access objects
70 * instead, based on {@link org.hibernate.SessionFactory#getCurrentSession()}.</b>
71 *
72 * <p>This class can be considered as direct alternative to working with the raw
73 * Hibernate3 Session API (through <code>SessionFactory.getCurrentSession()</code>).
74 * The major advantage is its automatic conversion to DataAccessExceptions as well
75 * as its capability to fall back to 'auto-commit' style behavior when used outside
76 * of transactions. <b>Note that HibernateTemplate will perform its own Session
77 * management, not participating in a custom Hibernate CurrentSessionContext
78 * unless you explicitly switch {@link #setAllowCreate "allowCreate"} to "false".</b>
79 *
80 * <p>{@link LocalSessionFactoryBean} is the preferred way of obtaining a reference
81 * to a specific Hibernate SessionFactory, at least in a non-EJB environment.
82 * The Spring application context will manage its lifecycle, initializing and
83 * shutting down the factory as part of the application.
84 *
85 * <p>Note that operations that return an Iterator (i.e. <code>iterate</code>)
86 * are supposed to be used within Spring-driven or JTA-driven transactions
87 * (with HibernateTransactionManager, JtaTransactionManager, or EJB CMT).
88 * Else, the Iterator won't be able to read results from its ResultSet anymore,
89 * as the underlying Hibernate Session will already have been closed.
90 *
91 * <p>Lazy loading will also just work with an open Hibernate Session,
92 * either within a transaction or within OpenSessionInViewFilter/Interceptor.
93 * Furthermore, some operations just make sense within transactions,
94 * for example: <code>contains</code>, <code>evict</code>, <code>lock</code>,
95 * <code>flush</code>, <code>clear</code>.
96 *
97 * @author Juergen Hoeller
98 * @since 1.2
99 * @see #setSessionFactory
100 * @see HibernateCallback
101 * @see org.hibernate.Session
102 * @see LocalSessionFactoryBean
103 * @see HibernateTransactionManager
104 * @see org.springframework.transaction.jta.JtaTransactionManager
105 * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
106 * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
107 */
108 public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {
109
110 private boolean allowCreate = true;
111
112 private boolean alwaysUseNewSession = false;
113
114 private boolean exposeNativeSession = false;
115
116 private boolean checkWriteOperations = true;
117
118 private boolean cacheQueries = false;
119
120 private String queryCacheRegion;
121
122 private int fetchSize = 0;
123
124 private int maxResults = 0;
125
126
127 /**
128 * Create a new HibernateTemplate instance.
129 */
130 public HibernateTemplate() {
131 }
132
133 /**
134 * Create a new HibernateTemplate instance.
135 * @param sessionFactory SessionFactory to create Sessions
136 */
137 public HibernateTemplate(SessionFactory sessionFactory) {
138 setSessionFactory(sessionFactory);
139 afterPropertiesSet();
140 }
141
142 /**
143 * Create a new HibernateTemplate instance.
144 * @param sessionFactory SessionFactory to create Sessions
145 * @param allowCreate if a non-transactional Session should be created when no
146 * transactional Session can be found for the current thread
147 */
148 public HibernateTemplate(SessionFactory sessionFactory, boolean allowCreate) {
149 setSessionFactory(sessionFactory);
150 setAllowCreate(allowCreate);
151 afterPropertiesSet();
152 }
153
154
155 /**
156 * Set if a new {@link Session} should be created when no transactional
157 * <code>Session</code> can be found for the current thread.
158 * The default value is <code>true</code>.
159 * <p><code>HibernateTemplate</code> is aware of a corresponding
160 * <code>Session</code> bound to the current thread, for example when using
161 * {@link HibernateTransactionManager}. If <code>allowCreate</code> is
162 * <code>true</code>, a new non-transactional <code>Session</code> will be
163 * created if none is found, which needs to be closed at the end of the operation.
164 * If <code>false</code>, an {@link IllegalStateException} will get thrown in
165 * this case.
166 * <p><b>NOTE: As of Spring 2.5, switching <code>allowCreate</code>
167 * to <code>false</code> will delegate to Hibernate's
168 * {@link org.hibernate.SessionFactory#getCurrentSession()} method,</b>
169 * which - with Spring-based setup - will by default delegate to Spring's
170 * <code>SessionFactoryUtils.getSession(sessionFactory, false)</code>.
171 * This mode also allows for custom Hibernate CurrentSessionContext strategies
172 * to be plugged in, whereas <code>allowCreate</code> set to <code>true</code>
173 * will always use a Spring-managed Hibernate Session.
174 * @see SessionFactoryUtils#getSession(SessionFactory, boolean)
175 */
176 public void setAllowCreate(boolean allowCreate) {
177 this.allowCreate = allowCreate;
178 }
179
180 /**
181 * Return if a new Session should be created if no thread-bound found.
182 */
183 public boolean isAllowCreate() {
184 return this.allowCreate;
185 }
186
187 /**
188 * Set whether to always use a new Hibernate Session for this template.
189 * Default is "false"; if activated, all operations on this template will
190 * work on a new Hibernate Session even in case of a pre-bound Session
191 * (for example, within a transaction or OpenSessionInViewFilter).
192 * <p>Within a transaction, a new Hibernate Session used by this template
193 * will participate in the transaction through using the same JDBC
194 * Connection. In such a scenario, multiple Sessions will participate
195 * in the same database transaction.
196 * <p>Turn this on for operations that are supposed to always execute
197 * independently, without side effects caused by a shared Hibernate Session.
198 */
199 public void setAlwaysUseNewSession(boolean alwaysUseNewSession) {
200 this.alwaysUseNewSession = alwaysUseNewSession;
201 }
202
203 /**
204 * Return whether to always use a new Hibernate Session for this template.
205 */
206 public boolean isAlwaysUseNewSession() {
207 return this.alwaysUseNewSession;
208 }
209
210 /**
211 * Set whether to expose the native Hibernate Session to
212 * HibernateCallback code.
213 * <p>Default is "false": a Session proxy will be returned, suppressing
214 * <code>close</code> calls and automatically applying query cache
215 * settings and transaction timeouts.
216 * @see HibernateCallback
217 * @see org.hibernate.Session
218 * @see #setCacheQueries
219 * @see #setQueryCacheRegion
220 * @see #prepareQuery
221 * @see #prepareCriteria
222 */
223 public void setExposeNativeSession(boolean exposeNativeSession) {
224 this.exposeNativeSession = exposeNativeSession;
225 }
226
227 /**
228 * Return whether to expose the native Hibernate Session to
229 * HibernateCallback code, or rather a Session proxy.
230 */
231 public boolean isExposeNativeSession() {
232 return this.exposeNativeSession;
233 }
234
235 /**
236 * Set whether to check that the Hibernate Session is not in read-only mode
237 * in case of write operations (save/update/delete).
238 * <p>Default is "true", for fail-fast behavior when attempting write operations
239 * within a read-only transaction. Turn this off to allow save/update/delete
240 * on a Session with flush mode NEVER.
241 * @see #setFlushMode
242 * @see #checkWriteOperationAllowed
243 * @see org.springframework.transaction.TransactionDefinition#isReadOnly
244 */
245 public void setCheckWriteOperations(boolean checkWriteOperations) {
246 this.checkWriteOperations = checkWriteOperations;
247 }
248
249 /**
250 * Return whether to check that the Hibernate Session is not in read-only
251 * mode in case of write operations (save/update/delete).
252 */
253 public boolean isCheckWriteOperations() {
254 return this.checkWriteOperations;
255 }
256
257 /**
258 * Set whether to cache all queries executed by this template.
259 * <p>If this is "true", all Query and Criteria objects created by
260 * this template will be marked as cacheable (including all
261 * queries through find methods).
262 * <p>To specify the query region to be used for queries cached
263 * by this template, set the "queryCacheRegion" property.
264 * @see #setQueryCacheRegion
265 * @see org.hibernate.Query#setCacheable
266 * @see org.hibernate.Criteria#setCacheable
267 */
268 public void setCacheQueries(boolean cacheQueries) {
269 this.cacheQueries = cacheQueries;
270 }
271
272 /**
273 * Return whether to cache all queries executed by this template.
274 */
275 public boolean isCacheQueries() {
276 return this.cacheQueries;
277 }
278
279 /**
280 * Set the name of the cache region for queries executed by this template.
281 * <p>If this is specified, it will be applied to all Query and Criteria objects
282 * created by this template (including all queries through find methods).
283 * <p>The cache region will not take effect unless queries created by this
284 * template are configured to be cached via the "cacheQueries" property.
285 * @see #setCacheQueries
286 * @see org.hibernate.Query#setCacheRegion
287 * @see org.hibernate.Criteria#setCacheRegion
288 */
289 public void setQueryCacheRegion(String queryCacheRegion) {
290 this.queryCacheRegion = queryCacheRegion;
291 }
292
293 /**
294 * Return the name of the cache region for queries executed by this template.
295 */
296 public String getQueryCacheRegion() {
297 return this.queryCacheRegion;
298 }
299
300 /**
301 * Set the fetch size for this HibernateTemplate. This is important for processing
302 * large result sets: Setting this higher than the default value will increase
303 * processing speed at the cost of memory consumption; setting this lower can
304 * avoid transferring row data that will never be read by the application.
305 * <p>Default is 0, indicating to use the JDBC driver's default.
306 */
307 public void setFetchSize(int fetchSize) {
308 this.fetchSize = fetchSize;
309 }
310
311 /**
312 * Return the fetch size specified for this HibernateTemplate.
313 */
314 public int getFetchSize() {
315 return this.fetchSize;
316 }
317
318 /**
319 * Set the maximum number of rows for this HibernateTemplate. This is important
320 * for processing subsets of large result sets, avoiding to read and hold
321 * the entire result set in the database or in the JDBC driver if we're
322 * never interested in the entire result in the first place (for example,
323 * when performing searches that might return a large number of matches).
324 * <p>Default is 0, indicating to use the JDBC driver's default.
325 */
326 public void setMaxResults(int maxResults) {
327 this.maxResults = maxResults;
328 }
329
330 /**
331 * Return the maximum number of rows specified for this HibernateTemplate.
332 */
333 public int getMaxResults() {
334 return this.maxResults;
335 }
336
337
338 public <T> T execute(HibernateCallback<T> action) throws DataAccessException {
339 return doExecute(action, false, false);
340 }
341
342 public List executeFind(HibernateCallback<?> action) throws DataAccessException {
343 Object result = doExecute(action, false, false);
344 if (result != null && !(result instanceof List)) {
345 throw new InvalidDataAccessApiUsageException(
346 "Result object returned from HibernateCallback isn't a List: [" + result + "]");
347 }
348 return (List) result;
349 }
350
351 /**
352 * Execute the action specified by the given action object within a
353 * new {@link org.hibernate.Session}.
354 * <p>This execute variant overrides the template-wide
355 * {@link #isAlwaysUseNewSession() "alwaysUseNewSession"} setting.
356 * @param action callback object that specifies the Hibernate action
357 * @return a result object returned by the action, or <code>null</code>
358 * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
359 */
360 public <T> T executeWithNewSession(HibernateCallback<T> action) {
361 return doExecute(action, true, false);
362 }
363
364 /**
365 * Execute the action specified by the given action object within a
366 * native {@link org.hibernate.Session}.
367 * <p>This execute variant overrides the template-wide
368 * {@link #isExposeNativeSession() "exposeNativeSession"} setting.
369 * @param action callback object that specifies the Hibernate action
370 * @return a result object returned by the action, or <code>null</code>
371 * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
372 */
373 public <T> T executeWithNativeSession(HibernateCallback<T> action) {
374 return doExecute(action, false, true);
375 }
376
377 /**
378 * Execute the action specified by the given action object within a Session.
379 * @param action callback object that specifies the Hibernate action
380 * @param enforceNewSession whether to enforce a new Session for this template
381 * even if there is a pre-bound transactional Session
382 * @param enforceNativeSession whether to enforce exposure of the native
383 * Hibernate Session to callback code
384 * @return a result object returned by the action, or <code>null</code>
385 * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
386 */
387 protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession)
388 throws DataAccessException {
389
390 Assert.notNull(action, "Callback object must not be null");
391
392 Session session = (enforceNewSession ?
393 SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession());
394 boolean existingTransaction = (!enforceNewSession &&
395 (!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
396 if (existingTransaction) {
397 logger.debug("Found thread-bound Session for HibernateTemplate");
398 }
399
400 FlushMode previousFlushMode = null;
401 try {
402 previousFlushMode = applyFlushMode(session, existingTransaction);
403 enableFilters(session);
404 Session sessionToExpose =
405 (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
406 T result = action.doInHibernate(sessionToExpose);
407 flushIfNecessary(session, existingTransaction);
408 return result;
409 }
410 catch (HibernateException ex) {
411 throw convertHibernateAccessException(ex);
412 }
413 catch (SQLException ex) {
414 throw convertJdbcAccessException(ex);
415 }
416 catch (RuntimeException ex) {
417 // Callback code threw application exception...
418 throw ex;
419 }
420 finally {
421 if (existingTransaction) {
422 logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate");
423 disableFilters(session);
424 if (previousFlushMode != null) {
425 session.setFlushMode(previousFlushMode);
426 }
427 }
428 else {
429 // Never use deferred close for an explicitly new Session.
430 if (isAlwaysUseNewSession()) {
431 SessionFactoryUtils.closeSession(session);
432 }
433 else {
434 SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
435 }
436 }
437 }
438 }
439
440 /**
441 * Return a Session for use by this template.
442 * <p>Returns a new Session in case of "alwaysUseNewSession" (using the same
443 * JDBC Connection as a transactional Session, if applicable), a pre-bound
444 * Session in case of "allowCreate" turned off, and a pre-bound or new Session
445 * otherwise (new only if no transactional or otherwise pre-bound Session exists).
446 * @return the Session to use (never <code>null</code>)
447 * @see SessionFactoryUtils#getSession
448 * @see SessionFactoryUtils#getNewSession
449 * @see #setAlwaysUseNewSession
450 * @see #setAllowCreate
451 */
452 protected Session getSession() {
453 if (isAlwaysUseNewSession()) {
454 return SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor());
455 }
456 else if (isAllowCreate()) {
457 return SessionFactoryUtils.getSession(
458 getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
459 }
460 else if (SessionFactoryUtils.hasTransactionalSession(getSessionFactory())) {
461 return SessionFactoryUtils.getSession(getSessionFactory(), false);
462 }
463 else {
464 try {
465 return getSessionFactory().getCurrentSession();
466 }
467 catch (HibernateException ex) {
468 throw new DataAccessResourceFailureException("Could not obtain current Hibernate Session", ex);
469 }
470 }
471 }
472
473 /**
474 * Create a close-suppressing proxy for the given Hibernate Session.
475 * The proxy also prepares returned Query and Criteria objects.
476 * @param session the Hibernate Session to create a proxy for
477 * @return the Session proxy
478 * @see org.hibernate.Session#close()
479 * @see #prepareQuery
480 * @see #prepareCriteria
481 */
482 protected Session createSessionProxy(Session session) {
483 Class[] sessionIfcs = null;
484 Class mainIfc = (session instanceof org.hibernate.classic.Session ?
485 org.hibernate.classic.Session.class : Session.class);
486 if (session instanceof EventSource) {
487 sessionIfcs = new Class[] {mainIfc, EventSource.class};
488 }
489 else if (session instanceof SessionImplementor) {
490 sessionIfcs = new Class[] {mainIfc, SessionImplementor.class};
491 }
492 else {
493 sessionIfcs = new Class[] {mainIfc};
494 }
495 return (Session) Proxy.newProxyInstance(
496 session.getClass().getClassLoader(), sessionIfcs,
497 new CloseSuppressingInvocationHandler(session));
498 }
499
500
501 //-------------------------------------------------------------------------
502 // Convenience methods for loading individual objects
503 //-------------------------------------------------------------------------
504
505 public <T> T get(Class<T> entityClass, Serializable id) throws DataAccessException {
506 return get(entityClass, id, null);
507 }
508
509 public <T> T get(final Class<T> entityClass, final Serializable id, final LockMode lockMode)
510 throws DataAccessException {
511
512 return executeWithNativeSession(new HibernateCallback<T>() {
513 @SuppressWarnings("unchecked")
514 public T doInHibernate(Session session) throws HibernateException {
515 if (lockMode != null) {
516 return (T) session.get(entityClass, id, lockMode);
517 }
518 else {
519 return (T) session.get(entityClass, id);
520 }
521 }
522 });
523 }
524
525 public Object get(String entityName, Serializable id) throws DataAccessException {
526 return get(entityName, id, null);
527 }
528
529 public Object get(final String entityName, final Serializable id, final LockMode lockMode)
530 throws DataAccessException {
531
532 return executeWithNativeSession(new HibernateCallback<Object>() {
533 public Object doInHibernate(Session session) throws HibernateException {
534 if (lockMode != null) {
535 return session.get(entityName, id, lockMode);
536 }
537 else {
538 return session.get(entityName, id);
539 }
540 }
541 });
542 }
543
544 public <T> T load(Class<T> entityClass, Serializable id) throws DataAccessException {
545 return load(entityClass, id, null);
546 }
547
548 public <T> T load(final Class<T> entityClass, final Serializable id, final LockMode lockMode)
549 throws DataAccessException {
550
551 return executeWithNativeSession(new HibernateCallback<T>() {
552 @SuppressWarnings("unchecked")
553 public T doInHibernate(Session session) throws HibernateException {
554 if (lockMode != null) {
555 return (T) session.load(entityClass, id, lockMode);
556 }
557 else {
558 return (T) session.load(entityClass, id);
559 }
560 }
561 });
562 }
563
564 public Object load(String entityName, Serializable id) throws DataAccessException {
565 return load(entityName, id, null);
566 }
567
568 public Object load(final String entityName, final Serializable id, final LockMode lockMode)
569 throws DataAccessException {
570
571 return executeWithNativeSession(new HibernateCallback<Object>() {
572 public Object doInHibernate(Session session) throws HibernateException {
573 if (lockMode != null) {
574 return session.load(entityName, id, lockMode);
575 }
576 else {
577 return session.load(entityName, id);
578 }
579 }
580 });
581 }
582
583 public <T> List<T> loadAll(final Class<T> entityClass) throws DataAccessException {
584 return executeWithNativeSession(new HibernateCallback<List<T>>() {
585 @SuppressWarnings("unchecked")
586 public List<T> doInHibernate(Session session) throws HibernateException {
587 Criteria criteria = session.createCriteria(entityClass);
588 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
589 prepareCriteria(criteria);
590 return criteria.list();
591 }
592 });
593 }
594
595 public void load(final Object entity, final Serializable id) throws DataAccessException {
596 executeWithNativeSession(new HibernateCallback<Object>() {
597 public Object doInHibernate(Session session) throws HibernateException {
598 session.load(entity, id);
599 return null;
600 }
601 });
602 }
603
604 public void refresh(final Object entity) throws DataAccessException {
605 refresh(entity, null);
606 }
607
608 public void refresh(final Object entity, final LockMode lockMode) throws DataAccessException {
609 executeWithNativeSession(new HibernateCallback<Object>() {
610 public Object doInHibernate(Session session) throws HibernateException {
611 if (lockMode != null) {
612 session.refresh(entity, lockMode);
613 }
614 else {
615 session.refresh(entity);
616 }
617 return null;
618 }
619 });
620 }
621
622 public boolean contains(final Object entity) throws DataAccessException {
623 return executeWithNativeSession(new HibernateCallback<Boolean>() {
624 public Boolean doInHibernate(Session session) {
625 return session.contains(entity);
626 }
627 });
628 }
629
630 public void evict(final Object entity) throws DataAccessException {
631 executeWithNativeSession(new HibernateCallback<Object>() {
632 public Object doInHibernate(Session session) throws HibernateException {
633 session.evict(entity);
634 return null;
635 }
636 });
637 }
638
639 public void initialize(Object proxy) throws DataAccessException {
640 try {
641 Hibernate.initialize(proxy);
642 }
643 catch (HibernateException ex) {
644 throw SessionFactoryUtils.convertHibernateAccessException(ex);
645 }
646 }
647
648 public Filter enableFilter(String filterName) throws IllegalStateException {
649 Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
650 Filter filter = session.getEnabledFilter(filterName);
651 if (filter == null) {
652 filter = session.enableFilter(filterName);
653 }
654 return filter;
655 }
656
657
658 //-------------------------------------------------------------------------
659 // Convenience methods for storing individual objects
660 //-------------------------------------------------------------------------
661
662 public void lock(final Object entity, final LockMode lockMode) throws DataAccessException {
663 executeWithNativeSession(new HibernateCallback<Object>() {
664 public Object doInHibernate(Session session) throws HibernateException {
665 session.lock(entity, lockMode);
666 return null;
667 }
668 });
669 }
670
671 public void lock(final String entityName, final Object entity, final LockMode lockMode)
672 throws DataAccessException {
673
674 executeWithNativeSession(new HibernateCallback<Object>() {
675 public Object doInHibernate(Session session) throws HibernateException {
676 session.lock(entityName, entity, lockMode);
677 return null;
678 }
679 });
680 }
681
682 public Serializable save(final Object entity) throws DataAccessException {
683 return executeWithNativeSession(new HibernateCallback<Serializable>() {
684 public Serializable doInHibernate(Session session) throws HibernateException {
685 checkWriteOperationAllowed(session);
686 return session.save(entity);
687 }
688 });
689 }
690
691 public Serializable save(final String entityName, final Object entity) throws DataAccessException {
692 return executeWithNativeSession(new HibernateCallback<Serializable>() {
693 public Serializable doInHibernate(Session session) throws HibernateException {
694 checkWriteOperationAllowed(session);
695 return session.save(entityName, entity);
696 }
697 });
698 }
699
700 public void update(Object entity) throws DataAccessException {
701 update(entity, null);
702 }
703
704 public void update(final Object entity, final LockMode lockMode) throws DataAccessException {
705 executeWithNativeSession(new HibernateCallback<Object>() {
706 public Object doInHibernate(Session session) throws HibernateException {
707 checkWriteOperationAllowed(session);
708 session.update(entity);
709 if (lockMode != null) {
710 session.lock(entity, lockMode);
711 }
712 return null;
713 }
714 });
715 }
716
717 public void update(String entityName, Object entity) throws DataAccessException {
718 update(entityName, entity, null);
719 }
720
721 public void update(final String entityName, final Object entity, final LockMode lockMode)
722 throws DataAccessException {
723
724 executeWithNativeSession(new HibernateCallback<Object>() {
725 public Object doInHibernate(Session session) throws HibernateException {
726 checkWriteOperationAllowed(session);
727 session.update(entityName, entity);
728 if (lockMode != null) {
729 session.lock(entity, lockMode);
730 }
731 return null;
732 }
733 });
734 }
735
736 public void saveOrUpdate(final Object entity) throws DataAccessException {
737 executeWithNativeSession(new HibernateCallback<Object>() {
738 public Object doInHibernate(Session session) throws HibernateException {
739 checkWriteOperationAllowed(session);
740 session.saveOrUpdate(entity);
741 return null;
742 }
743 });
744 }
745
746 public void saveOrUpdate(final String entityName, final Object entity) throws DataAccessException {
747 executeWithNativeSession(new HibernateCallback<Object>() {
748 public Object doInHibernate(Session session) throws HibernateException {
749 checkWriteOperationAllowed(session);
750 session.saveOrUpdate(entityName, entity);
751 return null;
752 }
753 });
754 }
755
756 public void saveOrUpdateAll(final Collection entities) throws DataAccessException {
757 executeWithNativeSession(new HibernateCallback<Object>() {
758 public Object doInHibernate(Session session) throws HibernateException {
759 checkWriteOperationAllowed(session);
760 for (Object entity : entities) {
761 session.saveOrUpdate(entity);
762 }
763 return null;
764 }
765 });
766 }
767
768 public void replicate(final Object entity, final ReplicationMode replicationMode)
769 throws DataAccessException {
770
771 executeWithNativeSession(new HibernateCallback<Object>() {
772 public Object doInHibernate(Session session) throws HibernateException {
773 checkWriteOperationAllowed(session);
774 session.replicate(entity, replicationMode);
775 return null;
776 }
777 });
778 }
779
780 public void replicate(final String entityName, final Object entity, final ReplicationMode replicationMode)
781 throws DataAccessException {
782
783 executeWithNativeSession(new HibernateCallback<Object>() {
784 public Object doInHibernate(Session session) throws HibernateException {
785 checkWriteOperationAllowed(session);
786 session.replicate(entityName, entity, replicationMode);
787 return null;
788 }
789 });
790 }
791
792 public void persist(final Object entity) throws DataAccessException {
793 executeWithNativeSession(new HibernateCallback<Object>() {
794 public Object doInHibernate(Session session) throws HibernateException {
795 checkWriteOperationAllowed(session);
796 session.persist(entity);
797 return null;
798 }
799 });
800 }
801
802 public void persist(final String entityName, final Object entity) throws DataAccessException {
803 executeWithNativeSession(new HibernateCallback<Object>() {
804 public Object doInHibernate(Session session) throws HibernateException {
805 checkWriteOperationAllowed(session);
806 session.persist(entityName, entity);
807 return null;
808 }
809 });
810 }
811
812 public <T> T merge(final T entity) throws DataAccessException {
813 return executeWithNativeSession(new HibernateCallback<T>() {
814 @SuppressWarnings("unchecked")
815 public T doInHibernate(Session session) throws HibernateException {
816 checkWriteOperationAllowed(session);
817 return (T) session.merge(entity);
818 }
819 });
820 }
821
822 public <T> T merge(final String entityName, final T entity) throws DataAccessException {
823 return executeWithNativeSession(new HibernateCallback<T>() {
824 @SuppressWarnings("unchecked")
825 public T doInHibernate(Session session) throws HibernateException {
826 checkWriteOperationAllowed(session);
827 return (T) session.merge(entityName, entity);
828 }
829 });
830 }
831
832 public void delete(Object entity) throws DataAccessException {
833 delete(entity, null);
834 }
835
836 public void delete(final Object entity, final LockMode lockMode) throws DataAccessException {
837 executeWithNativeSession(new HibernateCallback<Object>() {
838 public Object doInHibernate(Session session) throws HibernateException {
839 checkWriteOperationAllowed(session);
840 if (lockMode != null) {
841 session.lock(entity, lockMode);
842 }
843 session.delete(entity);
844 return null;
845 }
846 });
847 }
848
849 public void delete(String entityName, Object entity) throws DataAccessException {
850 delete(entityName, entity, null);
851 }
852
853 public void delete(final String entityName, final Object entity, final LockMode lockMode)
854 throws DataAccessException {
855
856 executeWithNativeSession(new HibernateCallback<Object>() {
857 public Object doInHibernate(Session session) throws HibernateException {
858 checkWriteOperationAllowed(session);
859 if (lockMode != null) {
860 session.lock(entityName, entity, lockMode);
861 }
862 session.delete(entityName, entity);
863 return null;
864 }
865 });
866 }
867
868 public void deleteAll(final Collection entities) throws DataAccessException {
869 executeWithNativeSession(new HibernateCallback<Object>() {
870 public Object doInHibernate(Session session) throws HibernateException {
871 checkWriteOperationAllowed(session);
872 for (Object entity : entities) {
873 session.delete(entity);
874 }
875 return null;
876 }
877 });
878 }
879
880 public void flush() throws DataAccessException {
881 executeWithNativeSession(new HibernateCallback<Object>() {
882 public Object doInHibernate(Session session) throws HibernateException {
883 session.flush();
884 return null;
885 }
886 });
887 }
888
889 public void clear() throws DataAccessException {
890 executeWithNativeSession(new HibernateCallback<Object>() {
891 public Object doInHibernate(Session session) {
892 session.clear();
893 return null;
894 }
895 });
896 }
897
898
899 //-------------------------------------------------------------------------
900 // Convenience finder methods for HQL strings
901 //-------------------------------------------------------------------------
902
903 public List find(String queryString) throws DataAccessException {
904 return find(queryString, (Object[]) null);
905 }
906
907 public List find(String queryString, Object value) throws DataAccessException {
908 return find(queryString, new Object[] {value});
909 }
910
911 public List find(final String queryString, final Object... values) throws DataAccessException {
912 return executeWithNativeSession(new HibernateCallback<List>() {
913 public List doInHibernate(Session session) throws HibernateException {
914 Query queryObject = session.createQuery(queryString);
915 prepareQuery(queryObject);
916 if (values != null) {
917 for (int i = 0; i < values.length; i++) {
918 queryObject.setParameter(i, values[i]);
919 }
920 }
921 return queryObject.list();
922 }
923 });
924 }
925
926 public List findByNamedParam(String queryString, String paramName, Object value)
927 throws DataAccessException {
928
929 return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value});
930 }
931
932 public List findByNamedParam(final String queryString, final String[] paramNames, final Object[] values)
933 throws DataAccessException {
934
935 if (paramNames.length != values.length) {
936 throw new IllegalArgumentException("Length of paramNames array must match length of values array");
937 }
938 return executeWithNativeSession(new HibernateCallback<List>() {
939 public List doInHibernate(Session session) throws HibernateException {
940 Query queryObject = session.createQuery(queryString);
941 prepareQuery(queryObject);
942 if (values != null) {
943 for (int i = 0; i < values.length; i++) {
944 applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
945 }
946 }
947 return queryObject.list();
948 }
949 });
950 }
951
952 public List findByValueBean(final String queryString, final Object valueBean)
953 throws DataAccessException {
954
955 return executeWithNativeSession(new HibernateCallback<List>() {
956 public List doInHibernate(Session session) throws HibernateException {
957 Query queryObject = session.createQuery(queryString);
958 prepareQuery(queryObject);
959 queryObject.setProperties(valueBean);
960 return queryObject.list();
961 }
962 });
963 }
964
965
966 //-------------------------------------------------------------------------
967 // Convenience finder methods for named queries
968 //-------------------------------------------------------------------------
969
970 public List findByNamedQuery(String queryName) throws DataAccessException {
971 return findByNamedQuery(queryName, (Object[]) null);
972 }
973
974 public List findByNamedQuery(String queryName, Object value) throws DataAccessException {
975 return findByNamedQuery(queryName, new Object[] {value});
976 }
977
978 public List findByNamedQuery(final String queryName, final Object... values) throws DataAccessException {
979 return executeWithNativeSession(new HibernateCallback<List>() {
980 public List doInHibernate(Session session) throws HibernateException {
981 Query queryObject = session.getNamedQuery(queryName);
982 prepareQuery(queryObject);
983 if (values != null) {
984 for (int i = 0; i < values.length; i++) {
985 queryObject.setParameter(i, values[i]);
986 }
987 }
988 return queryObject.list();
989 }
990 });
991 }
992
993 public List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
994 throws DataAccessException {
995
996 return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value});
997 }
998
999 public List findByNamedQueryAndNamedParam(
1000 final String queryName, final String[] paramNames, final Object[] values)
1001 throws DataAccessException {
1002
1003 if (paramNames != null && values != null && paramNames.length != values.length) {
1004 throw new IllegalArgumentException("Length of paramNames array must match length of values array");
1005 }
1006 return executeWithNativeSession(new HibernateCallback<List>() {
1007 public List doInHibernate(Session session) throws HibernateException {
1008 Query queryObject = session.getNamedQuery(queryName);
1009 prepareQuery(queryObject);
1010 if (values != null) {
1011 for (int i = 0; i < values.length; i++) {
1012 applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
1013 }
1014 }
1015 return queryObject.list();
1016 }
1017 });
1018 }
1019
1020 public List findByNamedQueryAndValueBean(final String queryName, final Object valueBean)
1021 throws DataAccessException {
1022
1023 return executeWithNativeSession(new HibernateCallback<List>() {
1024 public List doInHibernate(Session session) throws HibernateException {
1025 Query queryObject = session.getNamedQuery(queryName);
1026 prepareQuery(queryObject);
1027 queryObject.setProperties(valueBean);
1028 return queryObject.list();
1029 }
1030 });
1031 }
1032
1033
1034 //-------------------------------------------------------------------------
1035 // Convenience finder methods for detached criteria
1036 //-------------------------------------------------------------------------
1037
1038 public List findByCriteria(DetachedCriteria criteria) throws DataAccessException {
1039 return findByCriteria(criteria, -1, -1);
1040 }
1041
1042 public List findByCriteria(final DetachedCriteria criteria, final int firstResult, final int maxResults)
1043 throws DataAccessException {
1044
1045 Assert.notNull(criteria, "DetachedCriteria must not be null");
1046 return executeWithNativeSession(new HibernateCallback<List>() {
1047 public List doInHibernate(Session session) throws HibernateException {
1048 Criteria executableCriteria = criteria.getExecutableCriteria(session);
1049 prepareCriteria(executableCriteria);
1050 if (firstResult >= 0) {
1051 executableCriteria.setFirstResult(firstResult);
1052 }
1053 if (maxResults > 0) {
1054 executableCriteria.setMaxResults(maxResults);
1055 }
1056 return executableCriteria.list();
1057 }
1058 });
1059 }
1060
1061 public List findByExample(Object exampleEntity) throws DataAccessException {
1062 return findByExample(null, exampleEntity, -1, -1);
1063 }
1064
1065 public List findByExample(String entityName, Object exampleEntity) throws DataAccessException {
1066 return findByExample(entityName, exampleEntity, -1, -1);
1067 }
1068
1069 public List findByExample(Object exampleEntity, int firstResult, int maxResults) throws DataAccessException {
1070 return findByExample(null, exampleEntity, firstResult, maxResults);
1071 }
1072
1073 public List findByExample(
1074 final String entityName, final Object exampleEntity, final int firstResult, final int maxResults)
1075 throws DataAccessException {
1076
1077 Assert.notNull(exampleEntity, "Example entity must not be null");
1078 return executeWithNativeSession(new HibernateCallback<List>() {
1079 public List doInHibernate(Session session) throws HibernateException {
1080 Criteria executableCriteria = (entityName != null ?
1081 session.createCriteria(entityName) : session.createCriteria(exampleEntity.getClass()));
1082 executableCriteria.add(Example.create(exampleEntity));
1083 prepareCriteria(executableCriteria);
1084 if (firstResult >= 0) {
1085 executableCriteria.setFirstResult(firstResult);
1086 }
1087 if (maxResults > 0) {
1088 executableCriteria.setMaxResults(maxResults);
1089 }
1090 return executableCriteria.list();
1091 }
1092 });
1093 }
1094
1095
1096 //-------------------------------------------------------------------------
1097 // Convenience query methods for iteration and bulk updates/deletes
1098 //-------------------------------------------------------------------------
1099
1100 public Iterator iterate(String queryString) throws DataAccessException {
1101 return iterate(queryString, (Object[]) null);
1102 }
1103
1104 public Iterator iterate(String queryString, Object value) throws DataAccessException {
1105 return iterate(queryString, new Object[] {value});
1106 }
1107
1108 public Iterator iterate(final String queryString, final Object... values) throws DataAccessException {
1109 return executeWithNativeSession(new HibernateCallback<Iterator>() {
1110 public Iterator doInHibernate(Session session) throws HibernateException {
1111 Query queryObject = session.createQuery(queryString);
1112 prepareQuery(queryObject);
1113 if (values != null) {
1114 for (int i = 0; i < values.length; i++) {
1115 queryObject.setParameter(i, values[i]);
1116 }
1117 }
1118 return queryObject.iterate();
1119 }
1120 });
1121 }
1122
1123 public void closeIterator(Iterator it) throws DataAccessException {
1124 try {
1125 Hibernate.close(it);
1126 }
1127 catch (HibernateException ex) {
1128 throw SessionFactoryUtils.convertHibernateAccessException(ex);
1129 }
1130 }
1131
1132 public int bulkUpdate(String queryString) throws DataAccessException {
1133 return bulkUpdate(queryString, (Object[]) null);
1134 }
1135
1136 public int bulkUpdate(String queryString, Object value) throws DataAccessException {
1137 return bulkUpdate(queryString, new Object[] {value});
1138 }
1139
1140 public int bulkUpdate(final String queryString, final Object... values) throws DataAccessException {
1141 return executeWithNativeSession(new HibernateCallback<Integer>() {
1142 public Integer doInHibernate(Session session) throws HibernateException {
1143 Query queryObject = session.createQuery(queryString);
1144 prepareQuery(queryObject);
1145 if (values != null) {
1146 for (int i = 0; i < values.length; i++) {
1147 queryObject.setParameter(i, values[i]);
1148 }
1149 }
1150 return queryObject.executeUpdate();
1151 }
1152 });
1153 }
1154
1155
1156 //-------------------------------------------------------------------------
1157 // Helper methods used by the operations above
1158 //-------------------------------------------------------------------------
1159
1160 /**
1161 * Check whether write operations are allowed on the given Session.
1162 * <p>Default implementation throws an InvalidDataAccessApiUsageException in
1163 * case of <code>FlushMode.MANUAL</code>. Can be overridden in subclasses.
1164 * @param session current Hibernate Session
1165 * @throws InvalidDataAccessApiUsageException if write operations are not allowed
1166 * @see #setCheckWriteOperations
1167 * @see #getFlushMode()
1168 * @see #FLUSH_EAGER
1169 * @see org.hibernate.Session#getFlushMode()
1170 * @see org.hibernate.FlushMode#MANUAL
1171 */
1172 protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
1173 if (isCheckWriteOperations() && getFlushMode() != FLUSH_EAGER &&
1174 session.getFlushMode().lessThan(FlushMode.COMMIT)) {
1175 throw new InvalidDataAccessApiUsageException(
1176 "Write operations are not allowed in read-only mode (FlushMode.MANUAL): "+
1177 "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
1178 }
1179 }
1180
1181 /**
1182 * Prepare the given Query object, applying cache settings and/or
1183 * a transaction timeout.
1184 * @param queryObject the Query object to prepare
1185 * @see #setCacheQueries
1186 * @see #setQueryCacheRegion
1187 * @see SessionFactoryUtils#applyTransactionTimeout
1188 */
1189 protected void prepareQuery(Query queryObject) {
1190 if (isCacheQueries()) {
1191 queryObject.setCacheable(true);
1192 if (getQueryCacheRegion() != null) {
1193 queryObject.setCacheRegion(getQueryCacheRegion());
1194 }
1195 }
1196 if (getFetchSize() > 0) {
1197 queryObject.setFetchSize(getFetchSize());
1198 }
1199 if (getMaxResults() > 0) {
1200 queryObject.setMaxResults(getMaxResults());
1201 }
1202 SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
1203 }
1204
1205 /**
1206 * Prepare the given Criteria object, applying cache settings and/or
1207 * a transaction timeout.
1208 * @param criteria the Criteria object to prepare
1209 * @see #setCacheQueries
1210 * @see #setQueryCacheRegion
1211 * @see SessionFactoryUtils#applyTransactionTimeout
1212 */
1213 protected void prepareCriteria(Criteria criteria) {
1214 if (isCacheQueries()) {
1215 criteria.setCacheable(true);
1216 if (getQueryCacheRegion() != null) {
1217 criteria.setCacheRegion(getQueryCacheRegion());
1218 }
1219 }
1220 if (getFetchSize() > 0) {
1221 criteria.setFetchSize(getFetchSize());
1222 }
1223 if (getMaxResults() > 0) {
1224 criteria.setMaxResults(getMaxResults());
1225 }
1226 SessionFactoryUtils.applyTransactionTimeout(criteria, getSessionFactory());
1227 }
1228
1229 /**
1230 * Apply the given name parameter to the given Query object.
1231 * @param queryObject the Query object
1232 * @param paramName the name of the parameter
1233 * @param value the value of the parameter
1234 * @throws HibernateException if thrown by the Query object
1235 */
1236 protected void applyNamedParameterToQuery(Query queryObject, String paramName, Object value)
1237 throws HibernateException {
1238
1239 if (value instanceof Collection) {
1240 queryObject.setParameterList(paramName, (Collection) value);
1241 }
1242 else if (value instanceof Object[]) {
1243 queryObject.setParameterList(paramName, (Object[]) value);
1244 }
1245 else {
1246 queryObject.setParameter(paramName, value);
1247 }
1248 }
1249
1250
1251 /**
1252 * Invocation handler that suppresses close calls on Hibernate Sessions.
1253 * Also prepares returned Query and Criteria objects.
1254 * @see org.hibernate.Session#close
1255 */
1256 private class CloseSuppressingInvocationHandler implements InvocationHandler {
1257
1258 private final Session target;
1259
1260 public CloseSuppressingInvocationHandler(Session target) {
1261 this.target = target;
1262 }
1263
1264 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1265 // Invocation on Session interface coming in...
1266
1267 if (method.getName().equals("equals")) {
1268 // Only consider equal when proxies are identical.
1269 return (proxy == args[0]);
1270 }
1271 else if (method.getName().equals("hashCode")) {
1272 // Use hashCode of Session proxy.
1273 return System.identityHashCode(proxy);
1274 }
1275 else if (method.getName().equals("close")) {
1276 // Handle close method: suppress, not valid.
1277 return null;
1278 }
1279
1280 // Invoke method on target Session.
1281 try {
1282 Object retVal = method.invoke(this.target, args);
1283
1284 // If return value is a Query or Criteria, apply transaction timeout.
1285 // Applies to createQuery, getNamedQuery, createCriteria.
1286 if (retVal instanceof Query) {
1287 prepareQuery(((Query) retVal));
1288 }
1289 if (retVal instanceof Criteria) {
1290 prepareCriteria(((Criteria) retVal));
1291 }
1292
1293 return retVal;
1294 }
1295 catch (InvocationTargetException ex) {
1296 throw ex.getTargetException();
1297 }
1298 }
1299 }
1300
1301 }