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