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