001/*
002 * Copyright 2002-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.orm.hibernate3;
018
019import java.sql.SQLException;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import org.hibernate.FlushMode;
024import org.hibernate.HibernateException;
025import org.hibernate.Interceptor;
026import org.hibernate.JDBCException;
027import org.hibernate.Session;
028import org.hibernate.SessionFactory;
029import org.hibernate.exception.GenericJDBCException;
030
031import org.springframework.beans.BeansException;
032import org.springframework.beans.factory.BeanFactory;
033import org.springframework.beans.factory.BeanFactoryAware;
034import org.springframework.beans.factory.InitializingBean;
035import org.springframework.core.Constants;
036import org.springframework.dao.DataAccessException;
037import org.springframework.jdbc.support.SQLExceptionTranslator;
038
039/**
040 * Base class for {@link HibernateTemplate} and {@link HibernateInterceptor},
041 * defining common properties such as SessionFactory and flushing behavior.
042 *
043 * <p>Not intended to be used directly.
044 * See {@link HibernateTemplate} and {@link HibernateInterceptor}.
045 *
046 * @author Juergen Hoeller
047 * @since 1.2
048 * @see HibernateTemplate
049 * @see HibernateInterceptor
050 * @see #setFlushMode
051 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
052 */
053@Deprecated
054public abstract class HibernateAccessor implements InitializingBean, BeanFactoryAware {
055
056        /**
057         * Never flush is a good strategy for read-only units of work.
058         * Hibernate will not track and look for changes in this case,
059         * avoiding any overhead of modification detection.
060         * <p>In case of an existing Session, FLUSH_NEVER will turn the flush mode
061         * to NEVER for the scope of the current operation, resetting the previous
062         * flush mode afterwards.
063         * @see #setFlushMode
064         */
065        public static final int FLUSH_NEVER = 0;
066
067        /**
068         * Automatic flushing is the default mode for a Hibernate Session.
069         * A session will get flushed on transaction commit, and on certain find
070         * operations that might involve already modified instances, but not
071         * after each unit of work like with eager flushing.
072         * <p>In case of an existing Session, FLUSH_AUTO will participate in the
073         * existing flush mode, not modifying it for the current operation.
074         * This in particular means that this setting will not modify an existing
075         * flush mode NEVER, in contrast to FLUSH_EAGER.
076         * @see #setFlushMode
077         */
078        public static final int FLUSH_AUTO = 1;
079
080        /**
081         * Eager flushing leads to immediate synchronization with the database,
082         * even if in a transaction. This causes inconsistencies to show up and throw
083         * a respective exception immediately, and JDBC access code that participates
084         * in the same transaction will see the changes as the database is already
085         * aware of them then. But the drawbacks are:
086         * <ul>
087         * <li>additional communication roundtrips with the database, instead of a
088         * single batch at transaction commit;
089         * <li>the fact that an actual database rollback is needed if the Hibernate
090         * transaction rolls back (due to already submitted SQL statements).
091         * </ul>
092         * <p>In case of an existing Session, FLUSH_EAGER will turn the flush mode
093         * to AUTO for the scope of the current operation and issue a flush at the
094         * end, resetting the previous flush mode afterwards.
095         * @see #setFlushMode
096         */
097        public static final int FLUSH_EAGER = 2;
098
099        /**
100         * Flushing at commit only is intended for units of work where no
101         * intermediate flushing is desired, not even for find operations
102         * that might involve already modified instances.
103         * <p>In case of an existing Session, FLUSH_COMMIT will turn the flush mode
104         * to COMMIT for the scope of the current operation, resetting the previous
105         * flush mode afterwards. The only exception is an existing flush mode
106         * NEVER, which will not be modified through this setting.
107         * @see #setFlushMode
108         */
109        public static final int FLUSH_COMMIT = 3;
110
111        /**
112         * Flushing before every query statement is rarely necessary.
113         * It is only available for special needs.
114         * <p>In case of an existing Session, FLUSH_ALWAYS will turn the flush mode
115         * to ALWAYS for the scope of the current operation, resetting the previous
116         * flush mode afterwards.
117         * @see #setFlushMode
118         */
119        public static final int FLUSH_ALWAYS = 4;
120
121
122        /** Constants instance for HibernateAccessor */
123        private static final Constants constants = new Constants(HibernateAccessor.class);
124
125        /** Logger available to subclasses */
126        protected final Log logger = LogFactory.getLog(getClass());
127
128        private SessionFactory sessionFactory;
129
130        private Object entityInterceptor;
131
132        private SQLExceptionTranslator jdbcExceptionTranslator;
133
134        private SQLExceptionTranslator defaultJdbcExceptionTranslator;
135
136        private int flushMode = FLUSH_AUTO;
137
138        private String[] filterNames;
139
140        /**
141         * Just needed for entityInterceptorBeanName.
142         * @see #setEntityInterceptorBeanName
143         */
144        private BeanFactory beanFactory;
145
146
147        /**
148         * Set the Hibernate SessionFactory that should be used to create
149         * Hibernate Sessions.
150         */
151        public void setSessionFactory(SessionFactory sessionFactory) {
152                this.sessionFactory = sessionFactory;
153        }
154
155        /**
156         * Return the Hibernate SessionFactory that should be used to create
157         * Hibernate Sessions.
158         */
159        public SessionFactory getSessionFactory() {
160                return this.sessionFactory;
161        }
162
163        /**
164         * Set the bean name of a Hibernate entity interceptor that allows to inspect
165         * and change property values before writing to and reading from the database.
166         * Will get applied to any new Session created by this transaction manager.
167         * <p>Requires the bean factory to be known, to be able to resolve the bean
168         * name to an interceptor instance on session creation. Typically used for
169         * prototype interceptors, i.e. a new interceptor instance per session.
170         * <p>Can also be used for shared interceptor instances, but it is recommended
171         * to set the interceptor reference directly in such a scenario.
172         * @param entityInterceptorBeanName the name of the entity interceptor in
173         * the bean factory
174         * @see #setBeanFactory
175         * @see #setEntityInterceptor
176         */
177        public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
178                this.entityInterceptor = entityInterceptorBeanName;
179        }
180
181        /**
182         * Set a Hibernate entity interceptor that allows to inspect and change
183         * property values before writing to and reading from the database.
184         * Will get applied to any <b>new</b> Session created by this object.
185         * <p>Such an interceptor can either be set at the SessionFactory level,
186         * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
187         * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
188         * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
189         * to avoid repeated configuration and guarantee consistent behavior in transactions.
190         * @see #setEntityInterceptorBeanName
191         * @see LocalSessionFactoryBean#setEntityInterceptor
192         * @see HibernateTransactionManager#setEntityInterceptor
193         */
194        public void setEntityInterceptor(Interceptor entityInterceptor) {
195                this.entityInterceptor = entityInterceptor;
196        }
197
198        /**
199         * Return the current Hibernate entity interceptor, or {@code null} if none.
200         * Resolves an entity interceptor bean name via the bean factory,
201         * if necessary.
202         * @throws IllegalStateException if bean name specified but no bean factory set
203         * @throws org.springframework.beans.BeansException if bean name resolution via the bean factory failed
204         * @see #setEntityInterceptor
205         * @see #setEntityInterceptorBeanName
206         * @see #setBeanFactory
207         */
208        public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
209                if (this.entityInterceptor instanceof String) {
210                        if (this.beanFactory == null) {
211                                throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
212                        }
213                        return this.beanFactory.getBean((String) this.entityInterceptor, Interceptor.class);
214                }
215                return (Interceptor) this.entityInterceptor;
216        }
217
218        /**
219         * Set the JDBC exception translator for this instance.
220         * <p>Applied to any SQLException root cause of a Hibernate JDBCException,
221         * overriding Hibernate's default SQLException translation (which is
222         * based on Hibernate's Dialect for a specific target database).
223         * @param jdbcExceptionTranslator the exception translator
224         * @see java.sql.SQLException
225         * @see org.hibernate.JDBCException
226         * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
227         * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
228         */
229        public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
230                this.jdbcExceptionTranslator = jdbcExceptionTranslator;
231        }
232
233        /**
234         * Return the JDBC exception translator for this instance, if any.
235         */
236        public SQLExceptionTranslator getJdbcExceptionTranslator() {
237                return this.jdbcExceptionTranslator;
238        }
239
240        /**
241         * Set the flush behavior by the name of the respective constant
242         * in this class, e.g. "FLUSH_AUTO". Default is "FLUSH_AUTO".
243         * @param constantName name of the constant
244         * @see #setFlushMode
245         * @see #FLUSH_AUTO
246         */
247        public void setFlushModeName(String constantName) {
248                setFlushMode(constants.asNumber(constantName).intValue());
249        }
250
251        /**
252         * Set the flush behavior to one of the constants in this class.
253         * Default is FLUSH_AUTO.
254         * @see #setFlushModeName
255         * @see #FLUSH_AUTO
256         */
257        public void setFlushMode(int flushMode) {
258                this.flushMode = flushMode;
259        }
260
261        /**
262         * Return if a flush should be forced after executing the callback code.
263         */
264        public int getFlushMode() {
265                return this.flushMode;
266        }
267
268        /**
269         * Set the name of a Hibernate filter to be activated for all
270         * Sessions that this accessor works with.
271         * <p>This filter will be enabled at the beginning of each operation
272         * and correspondingly disabled at the end of the operation.
273         * This will work for newly opened Sessions as well as for existing
274         * Sessions (for example, within a transaction).
275         * @see #enableFilters(org.hibernate.Session)
276         * @see org.hibernate.Session#enableFilter(String)
277         * @see LocalSessionFactoryBean#setFilterDefinitions
278         */
279        public void setFilterName(String filter) {
280                this.filterNames = new String[] {filter};
281        }
282
283        /**
284         * Set one or more names of Hibernate filters to be activated for all
285         * Sessions that this accessor works with.
286         * <p>Each of those filters will be enabled at the beginning of each
287         * operation and correspondingly disabled at the end of the operation.
288         * This will work for newly opened Sessions as well as for existing
289         * Sessions (for example, within a transaction).
290         * @see #enableFilters(org.hibernate.Session)
291         * @see org.hibernate.Session#enableFilter(String)
292         * @see LocalSessionFactoryBean#setFilterDefinitions
293         */
294        public void setFilterNames(String... filterNames) {
295                this.filterNames = filterNames;
296        }
297
298        /**
299         * Return the names of Hibernate filters to be activated, if any.
300         */
301        public String[] getFilterNames() {
302                return this.filterNames;
303        }
304
305        /**
306         * The bean factory just needs to be known for resolving entity interceptor
307         * bean names. It does not need to be set for any other mode of operation.
308         * @see #setEntityInterceptorBeanName
309         */
310        @Override
311        public void setBeanFactory(BeanFactory beanFactory) {
312                this.beanFactory = beanFactory;
313        }
314
315        @Override
316        public void afterPropertiesSet() {
317                if (getSessionFactory() == null) {
318                        throw new IllegalArgumentException("Property 'sessionFactory' is required");
319                }
320        }
321
322
323        /**
324         * Apply the flush mode that's been specified for this accessor
325         * to the given Session.
326         * @param session the current Hibernate Session
327         * @param existingTransaction if executing within an existing transaction
328         * @return the previous flush mode to restore after the operation,
329         * or {@code null} if none
330         * @see #setFlushMode
331         * @see org.hibernate.Session#setFlushMode
332         */
333        protected FlushMode applyFlushMode(Session session, boolean existingTransaction) {
334                if (getFlushMode() == FLUSH_NEVER) {
335                        if (existingTransaction) {
336                                FlushMode previousFlushMode = session.getFlushMode();
337                                if (!previousFlushMode.lessThan(FlushMode.COMMIT)) {
338                                        session.setFlushMode(FlushMode.MANUAL);
339                                        return previousFlushMode;
340                                }
341                        }
342                        else {
343                                session.setFlushMode(FlushMode.MANUAL);
344                        }
345                }
346                else if (getFlushMode() == FLUSH_EAGER) {
347                        if (existingTransaction) {
348                                FlushMode previousFlushMode = session.getFlushMode();
349                                if (!previousFlushMode.equals(FlushMode.AUTO)) {
350                                        session.setFlushMode(FlushMode.AUTO);
351                                        return previousFlushMode;
352                                }
353                        }
354                        else {
355                                // rely on default FlushMode.AUTO
356                        }
357                }
358                else if (getFlushMode() == FLUSH_COMMIT) {
359                        if (existingTransaction) {
360                                FlushMode previousFlushMode = session.getFlushMode();
361                                if (previousFlushMode.equals(FlushMode.AUTO) || previousFlushMode.equals(FlushMode.ALWAYS)) {
362                                        session.setFlushMode(FlushMode.COMMIT);
363                                        return previousFlushMode;
364                                }
365                        }
366                        else {
367                                session.setFlushMode(FlushMode.COMMIT);
368                        }
369                }
370                else if (getFlushMode() == FLUSH_ALWAYS) {
371                        if (existingTransaction) {
372                                FlushMode previousFlushMode = session.getFlushMode();
373                                if (!previousFlushMode.equals(FlushMode.ALWAYS)) {
374                                        session.setFlushMode(FlushMode.ALWAYS);
375                                        return previousFlushMode;
376                                }
377                        }
378                        else {
379                                session.setFlushMode(FlushMode.ALWAYS);
380                        }
381                }
382                return null;
383        }
384
385        /**
386         * Flush the given Hibernate Session if necessary.
387         * @param session the current Hibernate Session
388         * @param existingTransaction if executing within an existing transaction
389         * @throws HibernateException in case of Hibernate flushing errors
390         */
391        protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException {
392                if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
393                        logger.debug("Eagerly flushing Hibernate session");
394                        session.flush();
395                }
396        }
397
398
399        /**
400         * Convert the given HibernateException to an appropriate exception
401         * from the {@code org.springframework.dao} hierarchy.
402         * <p>Will automatically apply a specified SQLExceptionTranslator to a
403         * Hibernate JDBCException, else rely on Hibernate's default translation.
404         * @param ex HibernateException that occured
405         * @return a corresponding DataAccessException
406         * @see SessionFactoryUtils#convertHibernateAccessException
407         * @see #setJdbcExceptionTranslator
408         */
409        public DataAccessException convertHibernateAccessException(HibernateException ex) {
410                if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
411                        return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
412                }
413                else if (GenericJDBCException.class == ex.getClass()) {
414                        return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
415                }
416                return SessionFactoryUtils.convertHibernateAccessException(ex);
417        }
418
419        /**
420         * Convert the given Hibernate JDBCException to an appropriate exception
421         * from the {@code org.springframework.dao} hierarchy, using the
422         * given SQLExceptionTranslator.
423         * @param ex Hibernate JDBCException that occured
424         * @param translator the SQLExceptionTranslator to use
425         * @return a corresponding DataAccessException
426         */
427        protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
428                return translator.translate("Hibernate operation: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
429        }
430
431        /**
432         * Convert the given SQLException to an appropriate exception from the
433         * {@code org.springframework.dao} hierarchy. Can be overridden in subclasses.
434         * <p>Note that a direct SQLException can just occur when callback code
435         * performs direct JDBC access via {@code Session.connection()}.
436         * @param ex the SQLException
437         * @return the corresponding DataAccessException instance
438         * @see #setJdbcExceptionTranslator
439         */
440        protected DataAccessException convertJdbcAccessException(SQLException ex) {
441                SQLExceptionTranslator translator = getJdbcExceptionTranslator();
442                if (translator == null) {
443                        translator = getDefaultJdbcExceptionTranslator();
444                }
445                return translator.translate("Hibernate-related JDBC operation", null, ex);
446        }
447
448        /**
449         * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
450         * <p>Creates a default
451         * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
452         * for the SessionFactory's underlying DataSource.
453         */
454        protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
455                if (this.defaultJdbcExceptionTranslator == null) {
456                        this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
457                }
458                return this.defaultJdbcExceptionTranslator;
459        }
460
461
462        /**
463         * Enable the specified filters on the given Session.
464         * @param session the current Hibernate Session
465         * @see #setFilterNames
466         * @see org.hibernate.Session#enableFilter(String)
467         */
468        protected void enableFilters(Session session) {
469                String[] filterNames = getFilterNames();
470                if (filterNames != null) {
471                        for (String filterName : filterNames) {
472                                session.enableFilter(filterName);
473                        }
474                }
475        }
476
477        /**
478         * Disable the specified filters on the given Session.
479         * @param session the current Hibernate Session
480         * @see #setFilterNames
481         * @see org.hibernate.Session#disableFilter(String)
482         */
483        protected void disableFilters(Session session) {
484                String[] filterNames = getFilterNames();
485                if (filterNames != null) {
486                        for (String filterName : filterNames) {
487                                session.disableFilter(filterName);
488                        }
489                }
490        }
491
492}