1 /*
2 * Copyright 2002-2008 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 Object execute(HibernateCallback 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 Object executeWithNewSession(HibernateCallback 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 Object executeWithNativeSession(HibernateCallback 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 enforceNativeSession whether to enforce exposure of the native
381 * Hibernate Session to callback code
382 * @return a result object returned by the action, or <code>null</code>
383 * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
384 * @deprecated as of Spring 2.5, in favor of {@link #executeWithNativeSession}
385 */
386 public Object execute(HibernateCallback action, boolean enforceNativeSession) throws DataAccessException {
387 return doExecute(action, false, enforceNativeSession);
388 }
389
390 /**
391 * Execute the action specified by the given action object within a Session.
392 * @param action callback object that specifies the Hibernate action
393 * @param enforceNewSession whether to enforce a new Session for this template
394 * even if there is a pre-bound transactional Session
395 * @param enforceNativeSession whether to enforce exposure of the native
396 * Hibernate Session to callback code
397 * @return a result object returned by the action, or <code>null</code>
398 * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
399 */
400 protected Object doExecute(HibernateCallback action, boolean enforceNewSession, boolean enforceNativeSession)
401 throws DataAccessException {
402
403 Assert.notNull(action, "Callback object must not be null");
404
405 Session session = (enforceNewSession ?
406 SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession());
407 boolean existingTransaction = (!enforceNewSession &&
408 (!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
409 if (existingTransaction) {
410 logger.debug("Found thread-bound Session for HibernateTemplate");
411 }
412
413 FlushMode previousFlushMode = null;
414 try {
415 previousFlushMode = applyFlushMode(session, existingTransaction);
416 enableFilters(session);
417 Session sessionToExpose =
418 (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
419 Object result = action.doInHibernate(sessionToExpose);
420 flushIfNecessary(session, existingTransaction);
421 return result;
422 }
423 catch (HibernateException ex) {
424 throw convertHibernateAccessException(ex);
425 }
426 catch (SQLException ex) {
427 throw convertJdbcAccessException(ex);
428 }
429 catch (RuntimeException ex) {
430 // Callback code threw application exception...
431 throw ex;
432 }
433 finally {
434 if (existingTransaction) {
435 logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate");
436 disableFilters(session);
437 if (previousFlushMode != null) {
438 session.setFlushMode(previousFlushMode);
439 }
440 }
441 else {
442 // Never use deferred close for an explicitly new Session.
443 if (isAlwaysUseNewSession()) {
444 SessionFactoryUtils.closeSession(session);
445 }
446 else {
447 SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
448 }
449 }
450 }
451 }
452
453 /**
454 * Return a Session for use by this template.
455 * <p>Returns a new Session in case of "alwaysUseNewSession" (using the same
456 * JDBC Connection as a transactional Session, if applicable), a pre-bound
457 * Session in case of "allowCreate" turned off, and a pre-bound or new Session
458 * otherwise (new only if no transactional or otherwise pre-bound Session exists).
459 * @return the Session to use (never <code>null</code>)
460 * @see SessionFactoryUtils#getSession
461 * @see SessionFactoryUtils#getNewSession
462 * @see #setAlwaysUseNewSession
463 * @see #setAllowCreate
464 */
465 protected Session getSession() {
466 if (isAlwaysUseNewSession()) {
467 return SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor());
468 }
469 else if (isAllowCreate()) {
470 return SessionFactoryUtils.getSession(
471 getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
472 }
473 else {
474 try {
475 return getSessionFactory().getCurrentSession();
476 }
477 catch (HibernateException ex) {
478 throw new DataAccessResourceFailureException("Could not obtain current Hibernate Session", ex);
479 }
480 }
481 }
482
483 /**
484 * Create a close-suppressing proxy for the given Hibernate Session.
485 * The proxy also prepares returned Query and Criteria objects.
486 * @param session the Hibernate Session to create a proxy for
487 * @return the Session proxy
488 * @see org.hibernate.Session#close()
489 * @see #prepareQuery
490 * @see #prepareCriteria
491 */
492 protected Session createSessionProxy(Session session) {
493 Class[] sessionIfcs = null;
494 Class mainIfc = (session instanceof org.hibernate.classic.Session ?
495 org.hibernate.classic.Session.class : Session.class);
496 if (session instanceof EventSource) {
497 sessionIfcs = new Class[] {mainIfc, EventSource.class};
498 }
499 else if (session instanceof SessionImplementor) {
500 sessionIfcs = new Class[] {mainIfc, SessionImplementor.class};
501 }
502 else {
503 sessionIfcs = new Class[] {mainIfc};
504 }
505 return (Session) Proxy.newProxyInstance(
506 session.getClass().getClassLoader(), sessionIfcs,
507 new CloseSuppressingInvocationHandler(session));
508 }
509
510
511 //-------------------------------------------------------------------------
512 // Convenience methods for loading individual objects
513 //-------------------------------------------------------------------------
514
515 public Object get(Class entityClass, Serializable id) throws DataAccessException {
516 return get(entityClass, id, null);
517 }
518
519 public Object get(final Class entityClass, final Serializable id, final LockMode lockMode)
520 throws DataAccessException {
521
522 return executeWithNativeSession(new HibernateCallback() {
523 public Object doInHibernate(Session session) throws HibernateException {
524 if (lockMode != null) {
525 return session.get(entityClass, id, lockMode);
526 }
527 else {
528 return session.get(entityClass, id);
529 }
530 }
531 });
532 }
533
534 public Object get(String entityName, Serializable id) throws DataAccessException {
535 return get(entityName, id, null);
536 }
537
538 public Object get(final String entityName, final Serializable id, final LockMode lockMode)
539 throws DataAccessException {
540
541 return executeWithNativeSession(new HibernateCallback() {
542 public Object doInHibernate(Session session) throws HibernateException {
543 if (lockMode != null) {
544 return session.get(entityName, id, lockMode);
545 }
546 else {
547 return session.get(entityName, id);
548 }
549 }
550 });
551 }
552
553 public Object load(Class entityClass, Serializable id) throws DataAccessException {
554 return load(entityClass, id, null);
555 }
556
557 public Object load(final Class entityClass, final Serializable id, final LockMode lockMode)
558 throws DataAccessException {
559
560 return executeWithNativeSession(new HibernateCallback() {
561 public Object doInHibernate(Session session) throws HibernateException {
562 if (lockMode != null) {
563 return session.load(entityClass, id, lockMode);
564 }
565 else {
566 return session.load(entityClass, id);
567 }
568 }
569 });
570 }
571
572 public Object load(String entityName, Serializable id) throws DataAccessException {
573 return load(entityName, id, null);
574 }
575
576 public Object load(final String entityName, final Serializable id, final LockMode lockMode)
577 throws DataAccessException {
578
579 return executeWithNativeSession(new HibernateCallback() {
580 public Object doInHibernate(Session session) throws HibernateException {
581 if (lockMode != null) {
582 return session.load(entityName, id, lockMode);
583 }
584 else {
585 return session.load(entityName, id);
586 }
587 }
588 });
589 }
590
591 public List loadAll(final Class entityClass) throws DataAccessException {
592 return (List) executeWithNativeSession(new HibernateCallback() {
593 public Object doInHibernate(Session session) throws HibernateException {
594 Criteria criteria = session.createCriteria(entityClass);
595 prepareCriteria(criteria);
596 return criteria.list();
597 }
598 });
599 }
600
601 public void load(final Object entity, final Serializable id) throws DataAccessException {
602 executeWithNativeSession(new HibernateCallback() {
603 public Object doInHibernate(Session session) throws HibernateException {
604 session.load(entity, id);
605 return null;
606 }
607 });
608 }
609
610 public void refresh(final Object entity) throws DataAccessException {
611 refresh(entity, null);
612 }
613
614 public void refresh(final Object entity, final LockMode lockMode) throws DataAccessException {
615 executeWithNativeSession(new HibernateCallback() {
616 public Object doInHibernate(Session session) throws HibernateException {
617 if (lockMode != null) {
618 session.refresh(entity, lockMode);
619 }
620 else {
621 session.refresh(entity);
622 }
623 return null;
624 }
625 });
626 }
627
628 public boolean contains(final Object entity) throws DataAccessException {
629 Boolean result = (Boolean) executeWithNativeSession(new HibernateCallback() {
630 public Object doInHibernate(Session session) {
631 return (session.contains(entity) ? Boolean.TRUE : Boolean.FALSE);
632 }
633 });
634 return result.booleanValue();
635 }
636
637 public void evict(final Object entity) throws DataAccessException {
638 executeWithNativeSession(new HibernateCallback() {
639 public Object doInHibernate(Session session) throws HibernateException {
640 session.evict(entity);
641 return null;
642 }
643 });
644 }
645
646 public void initialize(Object proxy) throws DataAccessException {
647 try {
648 Hibernate.initialize(proxy);
649 }
650 catch (HibernateException ex) {
651 throw SessionFactoryUtils.convertHibernateAccessException(ex);
652 }
653 }
654
655 public Filter enableFilter(String filterName) throws IllegalStateException {
656 Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
657 Filter filter = session.getEnabledFilter(filterName);
658 if (filter == null) {
659 filter = session.enableFilter(filterName);
660 }
661 return filter;
662 }
663
664
665 //-------------------------------------------------------------------------
666 // Convenience methods for storing individual objects
667 //-------------------------------------------------------------------------
668
669 public void lock(final Object entity, final LockMode lockMode) throws DataAccessException {
670 executeWithNativeSession(new HibernateCallback() {
671 public Object doInHibernate(Session session) throws HibernateException {
672 session.lock(entity, lockMode);
673 return null;
674 }
675 });
676 }
677
678 public void lock(final String entityName, final Object entity, final LockMode lockMode)
679 throws DataAccessException {
680
681 executeWithNativeSession(new HibernateCallback() {
682 public Object doInHibernate(Session session) throws HibernateException {
683 session.lock(entityName, entity, lockMode);
684 return null;
685 }
686 });
687 }
688
689 public Serializable save(final Object entity) throws DataAccessException {
690 return (Serializable) executeWithNativeSession(new HibernateCallback() {
691 public Object doInHibernate(Session session) throws HibernateException {
692 checkWriteOperationAllowed(session);
693 return session.save(entity);
694 }
695 });
696 }
697
698 public Serializable save(final String entityName, final Object entity) throws DataAccessException {
699 return (Serializable) executeWithNativeSession(new HibernateCallback() {
700 public Object doInHibernate(Session session) throws HibernateException {
701 checkWriteOperationAllowed(session);
702 return session.save(entityName, entity);
703 }
704 });
705 }
706
707 public void update(Object entity) throws DataAccessException {
708 update(entity, null);
709 }
710
711 public void update(final Object entity, final LockMode lockMode) throws DataAccessException {
712 executeWithNativeSession(new HibernateCallback() {
713 public Object doInHibernate(Session session) throws HibernateException {
714 checkWriteOperationAllowed(session);
715 session.update(entity);
716 if (lockMode != null) {
717 session.lock(entity, lockMode);
718 }
719 return null;
720 }
721 });
722 }
723
724 public void update(String entityName, Object entity) throws DataAccessException {
725 update(entityName, entity, null);
726 }
727
728 public void update(final String entityName, final Object entity, final LockMode lockMode)
729 throws DataAccessException {
730
731 executeWithNativeSession(new HibernateCallback() {
732 public Object doInHibernate(Session session) throws HibernateException {
733 checkWriteOperationAllowed(session);
734 session.update(entityName, entity);
735 if (lockMode != null) {
736 session.lock(entity, lockMode);
737 }
738 return null;
739 }
740 });
741 }
742
743 public void saveOrUpdate(final Object entity) throws DataAccessException {
744 executeWithNativeSession(new HibernateCallback() {
745 public Object doInHibernate(Session session) throws HibernateException {
746 checkWriteOperationAllowed(session);
747 session.saveOrUpdate(entity);
748 return null;
749 }
750 });
751 }
752
753 public void saveOrUpdate(final String entityName, final Object entity) throws DataAccessException {
754 executeWithNativeSession(new HibernateCallback() {
755 public Object doInHibernate(Session session) throws HibernateException {
756 checkWriteOperationAllowed(session);
757 session.saveOrUpdate(entityName, entity);
758 return null;
759 }
760 });
761 }
762
763 public void saveOrUpdateAll(final Collection entities) throws DataAccessException {
764 executeWithNativeSession(new HibernateCallback() {
765 public Object doInHibernate(Session session) throws HibernateException {
766 checkWriteOperationAllowed(session);
767 for (Iterator it = entities.iterator(); it.hasNext();) {
768 session.saveOrUpdate(it.next());
769 }
770 return null;
771 }
772 });
773 }
774
775 public void replicate(final Object entity, final ReplicationMode replicationMode)
776 throws DataAccessException {
777
778 executeWithNativeSession(new HibernateCallback() {
779 public Object doInHibernate(Session session) throws HibernateException {
780 checkWriteOperationAllowed(session);
781 session.replicate(entity, replicationMode);
782 return null;
783 }
784 });
785 }
786
787 public void replicate(final String entityName, final Object entity, final ReplicationMode replicationMode)
788 throws DataAccessException {
789
790 executeWithNativeSession(new HibernateCallback() {
791 public Object doInHibernate(Session session) throws HibernateException {
792 checkWriteOperationAllowed(session);
793 session.replicate(entityName, entity, replicationMode);
794 return null;
795 }
796 });
797 }
798
799 public void persist(final Object entity) throws DataAccessException {
800 executeWithNativeSession(new HibernateCallback() {
801 public Object doInHibernate(Session session) throws HibernateException {
802 checkWriteOperationAllowed(session);
803 session.persist(entity);
804 return null;
805 }
806 });
807 }
808
809 public void persist(final String entityName, final Object entity) throws DataAccessException {
810 executeWithNativeSession(new HibernateCallback() {
811 public Object doInHibernate(Session session) throws HibernateException {
812 checkWriteOperationAllowed(session);
813 session.persist(entityName, entity);
814 return null;
815 }
816 });
817 }
818
819 public Object merge(final Object entity) throws DataAccessException {
820 return executeWithNativeSession(new HibernateCallback() {
821 public Object doInHibernate(Session session) throws HibernateException {
822 checkWriteOperationAllowed(session);
823 return session.merge(entity);
824 }
825 });
826 }
827
828 public Object merge(final String entityName, final Object entity) throws DataAccessException {
829 return executeWithNativeSession(new HibernateCallback() {
830 public Object doInHibernate(Session session) throws HibernateException {
831 checkWriteOperationAllowed(session);
832 return session.merge(entityName, entity);
833 }
834 });
835 }
836
837 public void delete(Object entity) throws DataAccessException {
838 delete(entity, null);
839 }
840
841 public void delete(final Object entity, final LockMode lockMode) throws DataAccessException {
842 executeWithNativeSession(new HibernateCallback() {
843 public Object doInHibernate(Session session) throws HibernateException {
844 checkWriteOperationAllowed(session);
845 if (lockMode != null) {
846 session.lock(entity, lockMode);
847 }
848 session.delete(entity);
849 return null;
850 }
851 });
852 }
853
854 public void delete(String entityName, Object entity) throws DataAccessException {
855 delete(entityName, entity, null);
856 }
857
858 public void delete(final String entityName, final Object entity, final LockMode lockMode)
859 throws DataAccessException {
860
861 executeWithNativeSession(new HibernateCallback() {
862 public Object doInHibernate(Session session) throws HibernateException {
863 checkWriteOperationAllowed(session);
864 if (lockMode != null) {
865 session.lock(entityName, entity, lockMode);
866 }
867 session.delete(entityName, entity);
868 return null;
869 }
870 });
871 }
872
873 public void deleteAll(final Collection entities) throws DataAccessException {
874 executeWithNativeSession(new HibernateCallback() {
875 public Object doInHibernate(Session session) throws HibernateException {
876 checkWriteOperationAllowed(session);
877 for (Iterator it = entities.iterator(); it.hasNext();) {
878 session.delete(it.next());
879 }
880 return null;
881 }
882 });
883 }
884
885 public void flush() throws DataAccessException {
886 executeWithNativeSession(new HibernateCallback() {
887 public Object doInHibernate(Session session) throws HibernateException {
888 session.flush();
889 return null;
890 }
891 });
892 }
893
894 public void clear() throws DataAccessException {
895 executeWithNativeSession(new HibernateCallback() {
896 public Object doInHibernate(Session session) {
897 session.clear();
898 return null;
899 }
900 });
901 }
902
903
904 //-------------------------------------------------------------------------
905 // Convenience finder methods for HQL strings
906 //-------------------------------------------------------------------------
907
908 public List find(String queryString) throws DataAccessException {
909 return find(queryString, (Object[]) null);
910 }
911
912 public List find(String queryString, Object value) throws DataAccessException {
913 return find(queryString, new Object[] {value});
914 }
915
916 public List find(final String queryString, final Object[] values) throws DataAccessException {
917 return (List) executeWithNativeSession(new HibernateCallback() {
918 public Object doInHibernate(Session session) throws HibernateException {
919 Query queryObject = session.createQuery(queryString);
920 prepareQuery(queryObject);
921 if (values != null) {
922 for (int i = 0; i < values.length; i++) {
923 queryObject.setParameter(i, values[i]);
924 }
925 }
926 return queryObject.list();
927 }
928 });
929 }
930
931 public List findByNamedParam(String queryString, String paramName, Object value)
932 throws DataAccessException {
933
934 return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value});
935 }
936
937 public List findByNamedParam(final String queryString, final String[] paramNames, final Object[] values)
938 throws DataAccessException {
939
940 if (paramNames.length != values.length) {
941 throw new IllegalArgumentException("Length of paramNames array must match length of values array");
942 }
943 return (List) executeWithNativeSession(new HibernateCallback() {
944 public Object doInHibernate(Session session) throws HibernateException {
945 Query queryObject = session.createQuery(queryString);
946 prepareQuery(queryObject);
947 if (values != null) {
948 for (int i = 0; i < values.length; i++) {
949 applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
950 }
951 }
952 return queryObject.list();
953 }
954 });
955 }
956
957 public List findByValueBean(final String queryString, final Object valueBean)
958 throws DataAccessException {
959
960 return (List) executeWithNativeSession(new HibernateCallback() {
961 public Object doInHibernate(Session session) throws HibernateException {
962 Query queryObject = session.createQuery(queryString);
963 prepareQuery(queryObject);
964 queryObject.setProperties(valueBean);
965 return queryObject.list();
966 }
967 });
968 }
969
970
971 //-------------------------------------------------------------------------
972 // Convenience finder methods for named queries
973 //-------------------------------------------------------------------------
974
975 public List findByNamedQuery(String queryName) throws DataAccessException {
976 return findByNamedQuery(queryName, (Object[]) null);
977 }
978
979 public List findByNamedQuery(String queryName, Object value) throws DataAccessException {
980 return findByNamedQuery(queryName, new Object[] {value});
981 }
982
983 public List findByNamedQuery(final String queryName, final Object[] values) throws DataAccessException {
984 return (List) executeWithNativeSession(new HibernateCallback() {
985 public Object doInHibernate(Session session) throws HibernateException {
986 Query queryObject = session.getNamedQuery(queryName);
987 prepareQuery(queryObject);
988 if (values != null) {
989 for (int i = 0; i < values.length; i++) {
990 queryObject.setParameter(i, values[i]);
991 }
992 }
993 return queryObject.list();
994 }
995 });
996 }
997
998 public List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value)
999 throws DataAccessException {
1000
1001 return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value});
1002 }
1003
1004 public List findByNamedQueryAndNamedParam(
1005 final String queryName, final String[] paramNames, final Object[] values)
1006 throws DataAccessException {
1007
1008 if (paramNames != null && values != null && paramNames.length != values.length) {
1009 throw new IllegalArgumentException("Length of paramNames array must match length of values array");
1010 }
1011 return (List) executeWithNativeSession(new HibernateCallback() {
1012 public Object doInHibernate(Session session) throws HibernateException {
1013 Query queryObject = session.getNamedQuery(queryName);
1014 prepareQuery(queryObject);
1015 if (values != null) {
1016 for (int i = 0; i < values.length; i++) {
1017 applyNamedParameterToQuery(queryObject, paramNames[i], values[i]);
1018 }
1019 }
1020 return queryObject.list();
1021 }
1022 });
1023 }
1024
1025 public List findByNamedQueryAndValueBean(final String queryName, final Object valueBean)
1026 throws DataAccessException {
1027
1028 return (List) executeWithNativeSession(new HibernateCallback() {
1029 public Object doInHibernate(Session session) throws HibernateException {
1030 Query queryObject = session.getNamedQuery(queryName);
1031 prepareQuery(queryObject);
1032 queryObject.setProperties(valueBean);
1033 return queryObject.list();
1034 }
1035 });
1036 }
1037
1038
1039 //-------------------------------------------------------------------------
1040 // Convenience finder methods for detached criteria
1041 //-------------------------------------------------------------------------
1042
1043 public List findByCriteria(DetachedCriteria criteria) throws DataAccessException {
1044 return findByCriteria(criteria, -1, -1);
1045 }
1046
1047 public List findByCriteria(final DetachedCriteria criteria, final int firstResult, final int maxResults)
1048 throws DataAccessException {
1049
1050 Assert.notNull(criteria, "DetachedCriteria must not be null");
1051 return (List) executeWithNativeSession(new HibernateCallback() {
1052 public Object doInHibernate(Session session) throws HibernateException {
1053 Criteria executableCriteria = criteria.getExecutableCriteria(session);
1054 prepareCriteria(executableCriteria);
1055 if (firstResult >= 0) {
1056 executableCriteria.setFirstResult(firstResult);
1057 }
1058 if (maxResults > 0) {
1059 executableCriteria.setMaxResults(maxResults);
1060 }
1061 return executableCriteria.list();
1062 }
1063 });
1064 }
1065
1066 public List findByExample(Object exampleEntity) throws DataAccessException {
1067 return findByExample(null, exampleEntity, -1, -1);
1068 }
1069
1070 public List findByExample(String entityName, Object exampleEntity) throws DataAccessException {
1071 return findByExample(entityName, exampleEntity, -1, -1);
1072 }
1073
1074 public List findByExample(Object exampleEntity, int firstResult, int maxResults) throws DataAccessException {
1075 return findByExample(null, exampleEntity, firstResult, maxResults);
1076 }
1077
1078 public List findByExample(
1079 final String entityName, final Object exampleEntity, final int firstResult, final int maxResults)
1080 throws DataAccessException {
1081
1082 Assert.notNull(exampleEntity, "Example entity must not be null");
1083 return (List) executeWithNativeSession(new HibernateCallback() {
1084 public Object doInHibernate(Session session) throws HibernateException {
1085 Criteria executableCriteria = (entityName != null ?
1086 session.createCriteria(entityName) : session.createCriteria(exampleEntity.getClass()));
1087 executableCriteria.add(Example.create(exampleEntity));
1088 prepareCriteria(executableCriteria);
1089 if (firstResult >= 0) {
1090 executableCriteria.setFirstResult(firstResult);
1091 }
1092 if (maxResults > 0) {
1093 executableCriteria.setMaxResults(maxResults);
1094 }
1095 return executableCriteria.list();
1096 }
1097 });
1098 }
1099
1100
1101 //-------------------------------------------------------------------------
1102 // Convenience query methods for iteration and bulk updates/deletes
1103 //-------------------------------------------------------------------------
1104
1105 public Iterator iterate(String queryString) throws DataAccessException {
1106 return iterate(queryString, (Object[]) null);
1107 }
1108
1109 public Iterator iterate(String queryString, Object value) throws DataAccessException {
1110 return iterate(queryString, new Object[] {value});
1111 }
1112
1113 public Iterator iterate(final String queryString, final Object[] values) throws DataAccessException {
1114 return (Iterator) executeWithNativeSession(new HibernateCallback() {
1115 public Object doInHibernate(Session session) throws HibernateException {
1116 Query queryObject = session.createQuery(queryString);
1117 prepareQuery(queryObject);
1118 if (values != null) {
1119 for (int i = 0; i < values.length; i++) {
1120 queryObject.setParameter(i, values[i]);
1121 }
1122 }
1123 return queryObject.iterate();
1124 }
1125 });
1126 }
1127
1128 public void closeIterator(Iterator it) throws DataAccessException {
1129 try {
1130 Hibernate.close(it);
1131 }
1132 catch (HibernateException ex) {
1133 throw SessionFactoryUtils.convertHibernateAccessException(ex);
1134 }
1135 }
1136
1137 public int bulkUpdate(String queryString) throws DataAccessException {
1138 return bulkUpdate(queryString, (Object[]) null);
1139 }
1140
1141 public int bulkUpdate(String queryString, Object value) throws DataAccessException {
1142 return bulkUpdate(queryString, new Object[] {value});
1143 }
1144
1145 public int bulkUpdate(final String queryString, final Object[] values) throws DataAccessException {
1146 Integer updateCount = (Integer) executeWithNativeSession(new HibernateCallback() {
1147 public Object doInHibernate(Session session) throws HibernateException {
1148 Query queryObject = session.createQuery(queryString);
1149 prepareQuery(queryObject);
1150 if (values != null) {
1151 for (int i = 0; i < values.length; i++) {
1152 queryObject.setParameter(i, values[i]);
1153 }
1154 }
1155 return new Integer(queryObject.executeUpdate());
1156 }
1157 });
1158 return updateCount.intValue();
1159 }
1160
1161
1162 //-------------------------------------------------------------------------
1163 // Helper methods used by the operations above
1164 //-------------------------------------------------------------------------
1165
1166 /**
1167 * Check whether write operations are allowed on the given Session.
1168 * <p>Default implementation throws an InvalidDataAccessApiUsageException in
1169 * case of <code>FlushMode.NEVER/MANUAL</code>. Can be overridden in subclasses.
1170 * @param session current Hibernate Session
1171 * @throws InvalidDataAccessApiUsageException if write operations are not allowed
1172 * @see #setCheckWriteOperations
1173 * @see #getFlushMode()
1174 * @see #FLUSH_EAGER
1175 * @see org.hibernate.Session#getFlushMode()
1176 * @see org.hibernate.FlushMode#NEVER
1177 * @see org.hibernate.FlushMode#MANUAL
1178 */
1179 protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
1180 if (isCheckWriteOperations() && getFlushMode() != FLUSH_EAGER &&
1181 session.getFlushMode().lessThan(FlushMode.COMMIT)) {
1182 throw new InvalidDataAccessApiUsageException(
1183 "Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): "+
1184 "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
1185 }
1186 }
1187
1188 /**
1189 * Prepare the given Query object, applying cache settings and/or
1190 * a transaction timeout.
1191 * @param queryObject the Query object to prepare
1192 * @see #setCacheQueries
1193 * @see #setQueryCacheRegion
1194 * @see SessionFactoryUtils#applyTransactionTimeout
1195 */
1196 protected void prepareQuery(Query queryObject) {
1197 if (isCacheQueries()) {
1198 queryObject.setCacheable(true);
1199 if (getQueryCacheRegion() != null) {
1200 queryObject.setCacheRegion(getQueryCacheRegion());
1201 }
1202 }
1203 if (getFetchSize() > 0) {
1204 queryObject.setFetchSize(getFetchSize());
1205 }
1206 if (getMaxResults() > 0) {
1207 queryObject.setMaxResults(getMaxResults());
1208 }
1209 SessionFactoryUtils.applyTransactionTimeout(queryObject, getSessionFactory());
1210 }
1211
1212 /**
1213 * Prepare the given Criteria object, applying cache settings and/or
1214 * a transaction timeout.
1215 * @param criteria the Criteria object to prepare
1216 * @see #setCacheQueries
1217 * @see #setQueryCacheRegion
1218 * @see SessionFactoryUtils#applyTransactionTimeout
1219 */
1220 protected void prepareCriteria(Criteria criteria) {
1221 if (isCacheQueries()) {
1222 criteria.setCacheable(true);
1223 if (getQueryCacheRegion() != null) {
1224 criteria.setCacheRegion(getQueryCacheRegion());
1225 }
1226 }
1227 if (getFetchSize() > 0) {
1228 criteria.setFetchSize(getFetchSize());
1229 }
1230 if (getMaxResults() > 0) {
1231 criteria.setMaxResults(getMaxResults());
1232 }
1233 SessionFactoryUtils.applyTransactionTimeout(criteria, getSessionFactory());
1234 }
1235
1236 /**
1237 * Apply the given name parameter to the given Query object.
1238 * @param queryObject the Query object
1239 * @param paramName the name of the parameter
1240 * @param value the value of the parameter
1241 * @throws HibernateException if thrown by the Query object
1242 */
1243 protected void applyNamedParameterToQuery(Query queryObject, String paramName, Object value)
1244 throws HibernateException {
1245
1246 if (value instanceof Collection) {
1247 queryObject.setParameterList(paramName, (Collection) value);
1248 }
1249 else if (value instanceof Object[]) {
1250 queryObject.setParameterList(paramName, (Object[]) value);
1251 }
1252 else {
1253 queryObject.setParameter(paramName, value);
1254 }
1255 }
1256
1257
1258 /**
1259 * Invocation handler that suppresses close calls on Hibernate Sessions.
1260 * Also prepares returned Query and Criteria objects.
1261 * @see org.hibernate.Session#close
1262 */
1263 private class CloseSuppressingInvocationHandler implements InvocationHandler {
1264
1265 private final Session target;
1266
1267 public CloseSuppressingInvocationHandler(Session target) {
1268 this.target = target;
1269 }
1270
1271 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1272 // Invocation on Session interface coming in...
1273
1274 if (method.getName().equals("equals")) {
1275 // Only consider equal when proxies are identical.
1276 return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
1277 }
1278 else if (method.getName().equals("hashCode")) {
1279 // Use hashCode of Session proxy.
1280 return new Integer(System.identityHashCode(proxy));
1281 }
1282 else if (method.getName().equals("close")) {
1283 // Handle close method: suppress, not valid.
1284 return null;
1285 }
1286
1287 // Invoke method on target Session.
1288 try {
1289 Object retVal = method.invoke(this.target, args);
1290
1291 // If return value is a Query or Criteria, apply transaction timeout.
1292 // Applies to createQuery, getNamedQuery, createCriteria.
1293 if (retVal instanceof Query) {
1294 prepareQuery(((Query) retVal));
1295 }
1296 if (retVal instanceof Criteria) {
1297 prepareCriteria(((Criteria) retVal));
1298 }
1299
1300 return retVal;
1301 }
1302 catch (InvocationTargetException ex) {
1303 throw ex.getTargetException();
1304 }
1305 }
1306 }
1307
1308 }