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