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