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