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.hibernate;
018:
019: import java.sql.SQLException;
020:
021: import net.sf.hibernate.FlushMode;
022: import net.sf.hibernate.HibernateException;
023: import net.sf.hibernate.Interceptor;
024: import net.sf.hibernate.JDBCException;
025: import net.sf.hibernate.Session;
026: import net.sf.hibernate.SessionFactory;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import org.springframework.beans.BeansException;
031: import org.springframework.beans.factory.BeanFactory;
032: import org.springframework.beans.factory.BeanFactoryAware;
033: import org.springframework.beans.factory.InitializingBean;
034: import org.springframework.core.Constants;
035: import org.springframework.dao.DataAccessException;
036: import org.springframework.jdbc.support.SQLExceptionTranslator;
037:
038: /**
039: * Base class for {@link HibernateTemplate} and {@link HibernateInterceptor},
040: * defining common properties such as SessionFactory and flushing behavior.
041: *
042: * <p>Not intended to be used directly.
043: * See {@link HibernateTemplate} and {@link HibernateInterceptor}.
044: *
045: * @author Juergen Hoeller
046: * @since 29.07.2003
047: * @see HibernateTemplate
048: * @see HibernateInterceptor
049: * @see #setFlushMode
050: */
051: public abstract class HibernateAccessor implements InitializingBean,
052: BeanFactoryAware {
053:
054: /**
055: * Never flush is a good strategy for read-only units of work.
056: * Hibernate will not track and look for changes in this case,
057: * avoiding any overhead of modification detection.
058: * <p>In case of an existing Session, FLUSH_NEVER will turn the flush mode
059: * to NEVER for the scope of the current operation, resetting the previous
060: * flush mode afterwards.
061: * @see #setFlushMode
062: */
063: public static final int FLUSH_NEVER = 0;
064:
065: /**
066: * Automatic flushing is the default mode for a Hibernate Session.
067: * A session will get flushed on transaction commit, and on certain find
068: * operations that might involve already modified instances, but not
069: * after each unit of work like with eager flushing.
070: * <p>In case of an existing Session, FLUSH_AUTO will participate in the
071: * existing flush mode, not modifying it for the current operation.
072: * This in particular means that this setting will not modify an existing
073: * flush mode NEVER, in contrast to FLUSH_EAGER.
074: * @see #setFlushMode
075: */
076: public static final int FLUSH_AUTO = 1;
077:
078: /**
079: * Eager flushing leads to immediate synchronization with the database,
080: * even if in a transaction. This causes inconsistencies to show up and throw
081: * a respective exception immediately, and JDBC access code that participates
082: * in the same transaction will see the changes as the database is already
083: * aware of them then. But the drawbacks are:
084: * <ul>
085: * <li>additional communication roundtrips with the database, instead of a
086: * single batch at transaction commit;
087: * <li>the fact that an actual database rollback is needed if the Hibernate
088: * transaction rolls back (due to already submitted SQL statements).
089: * </ul>
090: * <p>In case of an existing Session, FLUSH_EAGER will turn the flush mode
091: * to AUTO for the scope of the current operation and issue a flush at the
092: * end, resetting the previous flush mode afterwards.
093: * @see #setFlushMode
094: */
095: public static final int FLUSH_EAGER = 2;
096:
097: /**
098: * Flushing at commit only is intended for units of work where no
099: * intermediate flushing is desired, not even for find operations
100: * that might involve already modified instances.
101: * <p>In case of an existing Session, FLUSH_COMMIT will turn the flush mode
102: * to COMMIT for the scope of the current operation, resetting the previous
103: * flush mode afterwards. The only exception is an existing flush mode
104: * NEVER, which will not be modified through this setting.
105: * @see #setFlushMode
106: */
107: public static final int FLUSH_COMMIT = 3;
108:
109: /** Constants instance for HibernateAccessor */
110: private static final Constants constants = new Constants(
111: HibernateAccessor.class);
112:
113: /** Logger available to subclasses */
114: protected final Log logger = LogFactory.getLog(getClass());
115:
116: private SessionFactory sessionFactory;
117:
118: private Object entityInterceptor;
119:
120: private SQLExceptionTranslator jdbcExceptionTranslator;
121:
122: private int flushMode = FLUSH_AUTO;
123:
124: /**
125: * Just needed for entityInterceptorBeanName.
126: * @see #setEntityInterceptorBeanName
127: */
128: private BeanFactory beanFactory;
129:
130: /**
131: * Set the Hibernate SessionFactory that should be used to create
132: * Hibernate Sessions.
133: */
134: public void setSessionFactory(SessionFactory sessionFactory) {
135: this .sessionFactory = sessionFactory;
136: }
137:
138: /**
139: * Return the Hibernate SessionFactory that should be used to create
140: * Hibernate Sessions.
141: */
142: public SessionFactory getSessionFactory() {
143: return this .sessionFactory;
144: }
145:
146: /**
147: * Set the bean name of a Hibernate entity interceptor that allows to inspect
148: * and change property values before writing to and reading from the database.
149: * Will get applied to any new Session created by this transaction manager.
150: * <p>Requires the bean factory to be known, to be able to resolve the bean
151: * name to an interceptor instance on session creation. Typically used for
152: * prototype interceptors, i.e. a new interceptor instance per session.
153: * <p>Can also be used for shared interceptor instances, but it is recommended
154: * to set the interceptor reference directly in such a scenario.
155: * @param entityInterceptorBeanName the name of the entity interceptor in
156: * the bean factory
157: * @see #setBeanFactory
158: * @see #setEntityInterceptor
159: */
160: public void setEntityInterceptorBeanName(
161: String entityInterceptorBeanName) {
162: this .entityInterceptor = entityInterceptorBeanName;
163: }
164:
165: /**
166: * Set a Hibernate entity interceptor that allows to inspect and change
167: * property values before writing to and reading from the database.
168: * Will get applied to any <b>new</b> Session created by this object.
169: * <p>Such an interceptor can either be set at the SessionFactory level,
170: * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
171: * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
172: * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
173: * to avoid repeated configuration and guarantee consistent behavior in transactions.
174: * @see #setEntityInterceptorBeanName
175: * @see LocalSessionFactoryBean#setEntityInterceptor
176: * @see HibernateTransactionManager#setEntityInterceptor
177: */
178: public void setEntityInterceptor(Interceptor entityInterceptor) {
179: this .entityInterceptor = entityInterceptor;
180: }
181:
182: /**
183: * Return the current Hibernate entity interceptor, or <code>null</code> if none.
184: * Resolves an entity interceptor bean name via the bean factory,
185: * if necessary.
186: * @throws IllegalStateException if bean name specified but no bean factory set
187: * @throws org.springframework.beans.BeansException if bean name resolution via the bean factory failed
188: * @see #setEntityInterceptor
189: * @see #setEntityInterceptorBeanName
190: * @see #setBeanFactory
191: */
192: public Interceptor getEntityInterceptor()
193: throws IllegalStateException, BeansException {
194: if (this .entityInterceptor instanceof String) {
195: if (this .beanFactory == null) {
196: throw new IllegalStateException(
197: "Cannot get entity interceptor via bean name if no bean factory set");
198: }
199: return (Interceptor) this .beanFactory.getBean(
200: (String) this .entityInterceptor, Interceptor.class);
201: }
202: return (Interceptor) this .entityInterceptor;
203: }
204:
205: /**
206: * Set the JDBC exception translator for this instance.
207: * Applied to SQLExceptions thrown by callback code, be it direct
208: * SQLExceptions or wrapped Hibernate JDBCExceptions.
209: * <p>The default exception translator is either a SQLErrorCodeSQLExceptionTranslator
210: * if a DataSource is available, or a SQLStateSQLExceptionTranslator else.
211: * @param jdbcExceptionTranslator the exception translator
212: * @see java.sql.SQLException
213: * @see net.sf.hibernate.JDBCException
214: * @see SessionFactoryUtils#newJdbcExceptionTranslator
215: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
216: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
217: */
218: public void setJdbcExceptionTranslator(
219: SQLExceptionTranslator jdbcExceptionTranslator) {
220: this .jdbcExceptionTranslator = jdbcExceptionTranslator;
221: }
222:
223: /**
224: * Return the JDBC exception translator for this instance.
225: * <p>Creates a default SQLErrorCodeSQLExceptionTranslator or SQLStateSQLExceptionTranslator
226: * for the specified SessionFactory, if no exception translator explicitly specified.
227: */
228: public synchronized SQLExceptionTranslator getJdbcExceptionTranslator() {
229: if (this .jdbcExceptionTranslator == null) {
230: this .jdbcExceptionTranslator = SessionFactoryUtils
231: .newJdbcExceptionTranslator(getSessionFactory());
232: }
233: return this .jdbcExceptionTranslator;
234: }
235:
236: /**
237: * Set the flush behavior by the name of the respective constant
238: * in this class, e.g. "FLUSH_AUTO". Default is "FLUSH_AUTO".
239: * @param constantName name of the constant
240: * @see #setFlushMode
241: * @see #FLUSH_AUTO
242: */
243: public void setFlushModeName(String constantName) {
244: setFlushMode(constants.asNumber(constantName).intValue());
245: }
246:
247: /**
248: * Set the flush behavior to one of the constants in this class.
249: * Default is FLUSH_AUTO.
250: * @see #setFlushModeName
251: * @see #FLUSH_AUTO
252: */
253: public void setFlushMode(int flushMode) {
254: this .flushMode = flushMode;
255: }
256:
257: /**
258: * Return if a flush should be forced after executing the callback code.
259: */
260: public int getFlushMode() {
261: return this .flushMode;
262: }
263:
264: /**
265: * The bean factory just needs to be known for resolving entity interceptor
266: * bean names. It does not need to be set for any other mode of operation.
267: * @see #setEntityInterceptorBeanName
268: */
269: public void setBeanFactory(BeanFactory beanFactory) {
270: this .beanFactory = beanFactory;
271: }
272:
273: public void afterPropertiesSet() {
274: if (getSessionFactory() == null) {
275: throw new IllegalArgumentException(
276: "Property 'sessionFactory' is required");
277: }
278: }
279:
280: /**
281: * Apply the flush mode that's been specified for this accessor
282: * to the given Session.
283: * @param session the current Hibernate Session
284: * @param existingTransaction if executing within an existing transaction
285: * @return the previous flush mode to restore after the operation,
286: * or <code>null</code> if none
287: * @see #setFlushMode
288: * @see net.sf.hibernate.Session#setFlushMode
289: */
290: protected FlushMode applyFlushMode(Session session,
291: boolean existingTransaction) {
292: if (getFlushMode() == FLUSH_NEVER) {
293: if (existingTransaction) {
294: FlushMode previousFlushMode = session.getFlushMode();
295: if (!previousFlushMode.equals(FlushMode.NEVER)) {
296: session.setFlushMode(FlushMode.NEVER);
297: return previousFlushMode;
298: }
299: } else {
300: session.setFlushMode(FlushMode.NEVER);
301: }
302: } else if (getFlushMode() == FLUSH_EAGER) {
303: if (existingTransaction) {
304: FlushMode previousFlushMode = session.getFlushMode();
305: if (!previousFlushMode.equals(FlushMode.AUTO)) {
306: session.setFlushMode(FlushMode.AUTO);
307: return previousFlushMode;
308: }
309: } else {
310: // rely on default FlushMode.AUTO
311: }
312: } else if (getFlushMode() == FLUSH_COMMIT) {
313: if (existingTransaction) {
314: FlushMode previousFlushMode = session.getFlushMode();
315: if (previousFlushMode.equals(FlushMode.AUTO)) {
316: session.setFlushMode(FlushMode.COMMIT);
317: return previousFlushMode;
318: }
319: } else {
320: session.setFlushMode(FlushMode.COMMIT);
321: }
322: }
323: return null;
324: }
325:
326: /**
327: * Flush the given Hibernate Session if necessary.
328: * @param session the current Hibernate Session
329: * @param existingTransaction if executing within an existing transaction
330: * @throws HibernateException in case of Hibernate flushing errors
331: */
332: protected void flushIfNecessary(Session session,
333: boolean existingTransaction) throws HibernateException {
334: if (getFlushMode() == FLUSH_EAGER
335: || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
336: logger.debug("Eagerly flushing Hibernate session");
337: session.flush();
338: }
339: }
340:
341: /**
342: * Convert the given HibernateException to an appropriate exception from the
343: * <code>org.springframework.dao</code> hierarchy. Will automatically detect
344: * wrapped SQLExceptions and convert them accordingly.
345: * <p>The default implementation delegates to SessionFactoryUtils
346: * and {@link #convertJdbcAccessException}. Can be overridden in subclasses.
347: * @param ex HibernateException that occured
348: * @return the corresponding DataAccessException instance
349: * @see #convertJdbcAccessException(net.sf.hibernate.JDBCException)
350: * @see SessionFactoryUtils#convertHibernateAccessException
351: */
352: public DataAccessException convertHibernateAccessException(
353: HibernateException ex) {
354: if (ex instanceof JDBCException) {
355: return convertJdbcAccessException((JDBCException) ex);
356: }
357: return SessionFactoryUtils.convertHibernateAccessException(ex);
358: }
359:
360: /**
361: * Convert the given JDBCException to an appropriate exception from the
362: * <code>org.springframework.dao</code> hierarchy. Can be overridden in subclasses.
363: * @param ex JDBCException that occured, wrapping a SQLException
364: * @return the corresponding DataAccessException instance
365: * @see #setJdbcExceptionTranslator
366: */
367: protected DataAccessException convertJdbcAccessException(
368: JDBCException ex) {
369: return getJdbcExceptionTranslator().translate(
370: "Hibernate operation: " + ex.getMessage(), null,
371: ex.getSQLException());
372: }
373:
374: /**
375: * Convert the given SQLException to an appropriate exception from the
376: * <code>org.springframework.dao</code> hierarchy. Can be overridden in subclasses.
377: * <p>Note that a direct SQLException can just occur when callback code
378: * performs direct JDBC access via <code>Session.connection()</code>.
379: * @param ex the SQLException
380: * @return the corresponding DataAccessException instance
381: * @see #setJdbcExceptionTranslator
382: * @see net.sf.hibernate.Session#connection()
383: */
384: protected DataAccessException convertJdbcAccessException(
385: SQLException ex) {
386: return getJdbcExceptionTranslator().translate(
387: "Hibernate operation", null, ex);
388: }
389:
390: }
|