Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » orm » hibernate3 » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.springframework.orm.hibernate3;
   18   
   19   import java.sql.Connection;
   20   
   21   import javax.sql.DataSource;
   22   
   23   import org.hibernate.ConnectionReleaseMode;
   24   import org.hibernate.FlushMode;
   25   import org.hibernate.HibernateException;
   26   import org.hibernate.Interceptor;
   27   import org.hibernate.JDBCException;
   28   import org.hibernate.Session;
   29   import org.hibernate.SessionFactory;
   30   import org.hibernate.Transaction;
   31   import org.hibernate.exception.GenericJDBCException;
   32   import org.hibernate.impl.SessionImpl;
   33   
   34   import org.springframework.beans.BeansException;
   35   import org.springframework.beans.factory.BeanFactory;
   36   import org.springframework.beans.factory.BeanFactoryAware;
   37   import org.springframework.beans.factory.InitializingBean;
   38   import org.springframework.dao.DataAccessException;
   39   import org.springframework.dao.DataAccessResourceFailureException;
   40   import org.springframework.jdbc.datasource.ConnectionHolder;
   41   import org.springframework.jdbc.datasource.DataSourceUtils;
   42   import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
   43   import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
   44   import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
   45   import org.springframework.jdbc.support.SQLExceptionTranslator;
   46   import org.springframework.transaction.CannotCreateTransactionException;
   47   import org.springframework.transaction.IllegalTransactionStateException;
   48   import org.springframework.transaction.InvalidIsolationLevelException;
   49   import org.springframework.transaction.TransactionDefinition;
   50   import org.springframework.transaction.TransactionSystemException;
   51   import org.springframework.transaction.support.AbstractPlatformTransactionManager;
   52   import org.springframework.transaction.support.DefaultTransactionStatus;
   53   import org.springframework.transaction.support.ResourceTransactionManager;
   54   import org.springframework.transaction.support.TransactionSynchronizationManager;
   55   
   56   /**
   57    * {@link org.springframework.transaction.PlatformTransactionManager}
   58    * implementation for a single Hibernate {@link org.hibernate.SessionFactory}.
   59    * Binds a Hibernate Session from the specified factory to the thread, potentially
   60    * allowing for one thread-bound Session per factory. {@link SessionFactoryUtils}
   61    * and {@link HibernateTemplate} are aware of thread-bound Sessions and participate
   62    * in such transactions automatically. Using either of those or going through
   63    * <code>SessionFactory.getCurrentSession()</code> is required for Hibernate
   64    * access code that needs to support this transaction handling mechanism.
   65    *
   66    * <p>Supports custom isolation levels, and timeouts that get applied as
   67    * Hibernate transaction timeouts.
   68    *
   69    * <p>This transaction manager is appropriate for applications that use a single
   70    * Hibernate SessionFactory for transactional data access, but it also supports
   71    * direct DataSource access within a transaction (i.e. plain JDBC code working
   72    * with the same DataSource). This allows for mixing services which access Hibernate
   73    * and services which use plain JDBC (without being aware of Hibernate)!
   74    * Application code needs to stick to the same simple Connection lookup pattern as
   75    * with {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
   76    * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
   77    * or going through a
   78    * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
   79    *
   80    * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
   81    * this instance needs to be aware of the DataSource ({@link #setDataSource}).
   82    * The given DataSource should obviously match the one used by the given
   83    * SessionFactory. To achieve this, configure both to the same JNDI DataSource,
   84    * or preferably create the SessionFactory with {@link LocalSessionFactoryBean} and
   85    * a local DataSource (which will be autodetected by this transaction manager).
   86    *
   87    * <p>JTA (usually through {@link org.springframework.transaction.jta.JtaTransactionManager})
   88    * is necessary for accessing multiple transactional resources within the same
   89    * transaction. The DataSource that Hibernate uses needs to be JTA-enabled in
   90    * such a scenario (see container setup). Normally, JTA setup for Hibernate is
   91    * somewhat container-specific due to the JTA TransactionManager lookup, required
   92    * for proper transactional handling of the SessionFactory-level read-write cache.
   93    *
   94    * <p>Fortunately, there is an easier way with Spring: {@link SessionFactoryUtils}
   95    * (and thus {@link HibernateTemplate}) registers synchronizations with Spring's
   96    * {@link org.springframework.transaction.support.TransactionSynchronizationManager}
   97    * (as used by {@link org.springframework.transaction.jta.JtaTransactionManager}),
   98    * for proper after-completion callbacks. Therefore, as long as Spring's
   99    * JtaTransactionManager drives the JTA transactions, Hibernate does not require
  100    * any special configuration for proper JTA participation. Note that there are
  101    * special restrictions with EJB CMT and restrictive JTA subsystems: See
  102    * {@link org.springframework.transaction.jta.JtaTransactionManager}'s javadoc for details.
  103    *
  104    * <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
  105    * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
  106    * flag defaults to "false", though, as nested transactions will just apply to the
  107    * JDBC Connection, not to the Hibernate Session and its cached objects. You can
  108    * manually set the flag to "true" if you want to use nested transactions for
  109    * JDBC access code which participates in Hibernate transactions (provided that
  110    * your JDBC driver supports Savepoints). <i>Note that Hibernate itself does not
  111    * support nested transactions! Hence, do not expect Hibernate access code to
  112    * semantically participate in a nested transaction.</i>
  113    *
  114    * <p>Requires Hibernate 3.1 or later, as of Spring 2.5.
  115    *
  116    * @author Juergen Hoeller
  117    * @since 1.2
  118    * @see #setSessionFactory
  119    * @see #setDataSource
  120    * @see LocalSessionFactoryBean
  121    * @see SessionFactoryUtils#getSession
  122    * @see SessionFactoryUtils#applyTransactionTimeout
  123    * @see SessionFactoryUtils#releaseSession
  124    * @see HibernateTemplate
  125    * @see org.hibernate.SessionFactory#getCurrentSession()
  126    * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
  127    * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout
  128    * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
  129    * @see org.springframework.jdbc.core.JdbcTemplate
  130    * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
  131    * @see org.springframework.transaction.jta.JtaTransactionManager
  132    */
  133   public class HibernateTransactionManager extends AbstractPlatformTransactionManager
  134   		implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
  135   
  136   	private SessionFactory sessionFactory;
  137   
  138   	private DataSource dataSource;
  139   
  140   	private boolean autodetectDataSource = true;
  141   
  142   	private boolean prepareConnection = true;
  143   
  144   	private boolean hibernateManagedSession = false;
  145   
  146   	private boolean earlyFlushBeforeCommit = false;
  147   
  148   	private Object entityInterceptor;
  149   
  150   	private SQLExceptionTranslator jdbcExceptionTranslator;
  151   
  152   	private SQLExceptionTranslator defaultJdbcExceptionTranslator;
  153   
  154   	/**
  155   	 * Just needed for entityInterceptorBeanName.
  156   	 * @see #setEntityInterceptorBeanName
  157   	 */
  158   	private BeanFactory beanFactory;
  159   
  160   
  161   	/**
  162   	 * Create a new HibernateTransactionManager instance.
  163   	 * A SessionFactory has to be set to be able to use it.
  164   	 * @see #setSessionFactory
  165   	 */
  166   	public HibernateTransactionManager() {
  167   	}
  168   
  169   	/**
  170   	 * Create a new HibernateTransactionManager instance.
  171   	 * @param sessionFactory SessionFactory to manage transactions for
  172   	 */
  173   	public HibernateTransactionManager(SessionFactory sessionFactory) {
  174   		this.sessionFactory = sessionFactory;
  175   		afterPropertiesSet();
  176   	}
  177   
  178   
  179   	/**
  180   	 * Set the SessionFactory that this instance should manage transactions for.
  181   	 */
  182   	public void setSessionFactory(SessionFactory sessionFactory) {
  183   		this.sessionFactory = sessionFactory;
  184   	}
  185   
  186   	/**
  187   	 * Return the SessionFactory that this instance should manage transactions for.
  188   	 */
  189   	public SessionFactory getSessionFactory() {
  190   		return this.sessionFactory;
  191   	}
  192   
  193   	/**
  194   	 * Set the JDBC DataSource that this instance should manage transactions for.
  195   	 * The DataSource should match the one used by the Hibernate SessionFactory:
  196   	 * for example, you could specify the same JNDI DataSource for both.
  197   	 * <p>If the SessionFactory was configured with LocalDataSourceConnectionProvider,
  198   	 * i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
  199   	 * the DataSource will be auto-detected: You can still explictly specify the
  200   	 * DataSource, but you don't need to in this case.
  201   	 * <p>A transactional JDBC Connection for this DataSource will be provided to
  202   	 * application code accessing this DataSource directly via DataSourceUtils
  203   	 * or JdbcTemplate. The Connection will be taken from the Hibernate Session.
  204   	 * <p>The DataSource specified here should be the target DataSource to manage
  205   	 * transactions for, not a TransactionAwareDataSourceProxy. Only data access
  206   	 * code may work with TransactionAwareDataSourceProxy, while the transaction
  207   	 * manager needs to work on the underlying target DataSource. If there's
  208   	 * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
  209   	 * unwrapped to extract its target DataSource.
  210   	 * @see #setAutodetectDataSource
  211   	 * @see LocalDataSourceConnectionProvider
  212   	 * @see LocalSessionFactoryBean#setDataSource
  213   	 * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
  214   	 * @see org.springframework.jdbc.datasource.DataSourceUtils
  215   	 * @see org.springframework.jdbc.core.JdbcTemplate
  216   	 */
  217   	public void setDataSource(DataSource dataSource) {
  218   		if (dataSource instanceof TransactionAwareDataSourceProxy) {
  219   			// If we got a TransactionAwareDataSourceProxy, we need to perform transactions
  220   			// for its underlying target DataSource, else data access code won't see
  221   			// properly exposed transactions (i.e. transactions for the target DataSource).
  222   			this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
  223   		}
  224   		else {
  225   			this.dataSource = dataSource;
  226   		}
  227   	}
  228   
  229   	/**
  230   	 * Return the JDBC DataSource that this instance manages transactions for.
  231   	 */
  232   	public DataSource getDataSource() {
  233   		return this.dataSource;
  234   	}
  235   
  236   	/**
  237   	 * Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
  238   	 * if set via LocalSessionFactoryBean's <code>setDataSource</code>. Default is "true".
  239   	 * <p>Can be turned off to deliberately ignore an available DataSource, in order
  240   	 * to not expose Hibernate transactions as JDBC transactions for that DataSource.
  241   	 * @see #setDataSource
  242   	 * @see LocalSessionFactoryBean#setDataSource
  243   	 */
  244   	public void setAutodetectDataSource(boolean autodetectDataSource) {
  245   		this.autodetectDataSource = autodetectDataSource;
  246   	}
  247   
  248   	/**
  249   	 * Set whether to prepare the underlying JDBC Connection of a transactional
  250   	 * Hibernate Session, that is, whether to apply a transaction-specific
  251   	 * isolation level and/or the transaction's read-only flag to the underlying
  252   	 * JDBC Connection.
  253   	 * <p>Default is "true". If you turn this flag off, the transaction manager
  254   	 * will not support per-transaction isolation levels anymore. It will not
  255   	 * call <code>Connection.setReadOnly(true)</code> for read-only transactions
  256   	 * anymore either. If this flag is turned off, no cleanup of a JDBC Connection
  257   	 * is required after a transaction, since no Connection settings will get modified.
  258   	 * <p>It is recommended to turn this flag off if running against Hibernate 3.1
  259   	 * and a connection pool that does not reset connection settings (for example,
  260   	 * Jakarta Commons DBCP). To keep this flag turned on, you can set the
  261   	 * "hibernate.connection.release_mode" property to "on_close" instead,
  262   	 * or consider using a smarter connection pool (for example, C3P0).
  263   	 * @see java.sql.Connection#setTransactionIsolation
  264   	 * @see java.sql.Connection#setReadOnly
  265   	 */
  266   	public void setPrepareConnection(boolean prepareConnection) {
  267   		this.prepareConnection = prepareConnection;
  268   	}
  269   
  270   	/**
  271   	 * Set whether to operate on a Hibernate-managed Session instead of a
  272   	 * Spring-managed Session, that is, whether to obtain the Session through
  273   	 * Hibernate's {@link org.hibernate.SessionFactory#getCurrentSession()}
  274   	 * instead of {@link org.hibernate.SessionFactory#openSession()} (with a Spring
  275   	 * {@link org.springframework.transaction.support.TransactionSynchronizationManager}
  276   	 * check preceding it).
  277   	 * <p>Default is "false", i.e. using a Spring-managed Session: taking the current
  278   	 * thread-bound Session if available (e.g. in an Open-Session-in-View scenario),
  279   	 * creating a new Session for the current transaction otherwise.
  280   	 * <p>Switch this flag to "true" in order to enforce use of a Hibernate-managed Session.
  281   	 * Note that this requires {@link org.hibernate.SessionFactory#getCurrentSession()}
  282   	 * to always return a proper Session when called for a Spring-managed transaction;
  283   	 * transaction begin will fail if the <code>getCurrentSession()</code> call fails.
  284   	 * <p>This mode will typically be used in combination with a custom Hibernate
  285   	 * {@link org.hibernate.context.CurrentSessionContext} implementation that stores
  286   	 * Sessions in a place other than Spring's TransactionSynchronizationManager.
  287   	 * It may also be used in combination with Spring's Open-Session-in-View support
  288   	 * (using Spring's default {@link SpringSessionContext}), in which case it subtly
  289   	 * differs from the Spring-managed Session mode: The pre-bound Session will <i>not</i>
  290   	 * receive a <code>clear()</code> call (on rollback) or a <code>disconnect()</code>
  291   	 * call (on transaction completion) in such a scenario; this is rather left up
  292   	 * to a custom CurrentSessionContext implementation (if desired).
  293   	 */
  294   	public void setHibernateManagedSession(boolean hibernateManagedSession) {
  295   		this.hibernateManagedSession = hibernateManagedSession;
  296   	}
  297   
  298   	/**
  299   	 * Set whether to perform an early flush before proceeding with a commit.
  300   	 * <p>Default is "false", performing an implicit flush as part of the actual
  301   	 * commit step. Switch this to "true" in order to enforce an explicit early
  302   	 * flush right <i>before</i> the actual commit step.
  303   	 * <p>An early flush happens before the before-commit synchronization phase,
  304   	 * making flushed state visible to <code>beforeCommit</code> callbacks of registered
  305   	 * {@link org.springframework.transaction.support.TransactionSynchronization}
  306   	 * objects. Such explicit flush behavior is consistent with Spring-driven
  307   	 * flushing in a JTA transaction environment, so may also get enforced for
  308   	 * consistency with JTA transaction behavior.
  309   	 * @see #prepareForCommit
  310   	 */
  311   	public void setEarlyFlushBeforeCommit(boolean earlyFlushBeforeCommit) {
  312   		this.earlyFlushBeforeCommit = earlyFlushBeforeCommit;
  313   	}
  314   
  315   	/**
  316   	 * Set the bean name of a Hibernate entity interceptor that allows to inspect
  317   	 * and change property values before writing to and reading from the database.
  318   	 * Will get applied to any new Session created by this transaction manager.
  319   	 * <p>Requires the bean factory to be known, to be able to resolve the bean
  320   	 * name to an interceptor instance on session creation. Typically used for
  321   	 * prototype interceptors, i.e. a new interceptor instance per session.
  322   	 * <p>Can also be used for shared interceptor instances, but it is recommended
  323   	 * to set the interceptor reference directly in such a scenario.
  324   	 * @param entityInterceptorBeanName the name of the entity interceptor in
  325   	 * the bean factory
  326   	 * @see #setBeanFactory
  327   	 * @see #setEntityInterceptor
  328   	 */
  329   	public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
  330   		this.entityInterceptor = entityInterceptorBeanName;
  331   	}
  332   
  333   	/**
  334   	 * Set a Hibernate entity interceptor that allows to inspect and change
  335   	 * property values before writing to and reading from the database.
  336   	 * Will get applied to any new Session created by this transaction manager.
  337   	 * <p>Such an interceptor can either be set at the SessionFactory level,
  338   	 * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
  339   	 * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
  340   	 * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
  341   	 * to avoid repeated configuration and guarantee consistent behavior in transactions.
  342   	 * @see LocalSessionFactoryBean#setEntityInterceptor
  343   	 * @see HibernateTemplate#setEntityInterceptor
  344   	 * @see HibernateInterceptor#setEntityInterceptor
  345   	 */
  346   	public void setEntityInterceptor(Interceptor entityInterceptor) {
  347   		this.entityInterceptor = entityInterceptor;
  348   	}
  349   
  350   	/**
  351   	 * Return the current Hibernate entity interceptor, or <code>null</code> if none.
  352   	 * Resolves an entity interceptor bean name via the bean factory,
  353   	 * if necessary.
  354   	 * @throws IllegalStateException if bean name specified but no bean factory set
  355   	 * @throws BeansException if bean name resolution via the bean factory failed
  356   	 * @see #setEntityInterceptor
  357   	 * @see #setEntityInterceptorBeanName
  358   	 * @see #setBeanFactory
  359   	 */
  360   	public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
  361   		if (this.entityInterceptor instanceof Interceptor) {
  362   			return (Interceptor) entityInterceptor;
  363   		}
  364   		else if (this.entityInterceptor instanceof String) {
  365   			if (this.beanFactory == null) {
  366   				throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
  367   			}
  368   			String beanName = (String) this.entityInterceptor;
  369   			return (Interceptor) this.beanFactory.getBean(beanName, Interceptor.class);
  370   		}
  371   		else {
  372   			return null;
  373   		}
  374   	}
  375   
  376   	/**
  377   	 * Set the JDBC exception translator for this transaction manager.
  378   	 * <p>Applied to any SQLException root cause of a Hibernate JDBCException that
  379   	 * is thrown on flush, overriding Hibernate's default SQLException translation
  380   	 * (which is based on Hibernate's Dialect for a specific target database).
  381   	 * @param jdbcExceptionTranslator the exception translator
  382   	 * @see java.sql.SQLException
  383   	 * @see org.hibernate.JDBCException
  384   	 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
  385   	 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
  386   	 */
  387   	public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
  388   		this.jdbcExceptionTranslator = jdbcExceptionTranslator;
  389   	}
  390   
  391   	/**
  392   	 * Return the JDBC exception translator for this transaction manager, if any.
  393   	 */
  394   	public SQLExceptionTranslator getJdbcExceptionTranslator() {
  395   		return this.jdbcExceptionTranslator;
  396   	}
  397   
  398   	/**
  399   	 * The bean factory just needs to be known for resolving entity interceptor
  400   	 * bean names. It does not need to be set for any other mode of operation.
  401   	 * @see #setEntityInterceptorBeanName
  402   	 */
  403   	public void setBeanFactory(BeanFactory beanFactory) {
  404   		this.beanFactory = beanFactory;
  405   	}
  406   
  407   	public void afterPropertiesSet() {
  408   		if (getSessionFactory() == null) {
  409   			throw new IllegalArgumentException("Property 'sessionFactory' is required");
  410   		}
  411   		if (this.entityInterceptor instanceof String && this.beanFactory == null) {
  412   			throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'");
  413   		}
  414   
  415   		// Check for SessionFactory's DataSource.
  416   		if (this.autodetectDataSource && getDataSource() == null) {
  417   			DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
  418   			if (sfds != null) {
  419   				// Use the SessionFactory's DataSource for exposing transactions to JDBC code.
  420   				if (logger.isInfoEnabled()) {
  421   					logger.info("Using DataSource [" + sfds +
  422   							"] of Hibernate SessionFactory for HibernateTransactionManager");
  423   				}
  424   				setDataSource(sfds);
  425   			}
  426   		}
  427   	}
  428   
  429   
  430   	public Object getResourceFactory() {
  431   		return getSessionFactory();
  432   	}
  433   
  434   	protected Object doGetTransaction() {
  435   		HibernateTransactionObject txObject = new HibernateTransactionObject();
  436   		txObject.setSavepointAllowed(isNestedTransactionAllowed());
  437   
  438   		SessionHolder sessionHolder =
  439   				(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
  440   		if (sessionHolder != null) {
  441   			if (logger.isDebugEnabled()) {
  442   				logger.debug("Found thread-bound Session [" +
  443   						SessionFactoryUtils.toString(sessionHolder.getSession()) + "] for Hibernate transaction");
  444   			}
  445   			txObject.setSessionHolder(sessionHolder);
  446   		}
  447   		else if (this.hibernateManagedSession) {
  448   			try {
  449   				Session session = getSessionFactory().getCurrentSession();
  450   				if (logger.isDebugEnabled()) {
  451   					logger.debug("Found Hibernate-managed Session [" +
  452   							SessionFactoryUtils.toString(session) + "] for Spring-managed transaction");
  453   				}
  454   				txObject.setExistingSession(session);
  455   			}
  456   			catch (HibernateException ex) {
  457   				throw new DataAccessResourceFailureException(
  458   						"Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
  459   			}
  460   		}
  461   
  462   		if (getDataSource() != null) {
  463   			ConnectionHolder conHolder = (ConnectionHolder)
  464   					TransactionSynchronizationManager.getResource(getDataSource());
  465   			txObject.setConnectionHolder(conHolder);
  466   		}
  467   
  468   		return txObject;
  469   	}
  470   
  471   	protected boolean isExistingTransaction(Object transaction) {
  472   		HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  473   		return (txObject.hasSpringManagedTransaction() ||
  474   				(this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()));
  475   	}
  476   
  477   	protected void doBegin(Object transaction, TransactionDefinition definition) {
  478   		HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  479   
  480   		if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
  481   			throw new IllegalTransactionStateException(
  482   					"Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
  483   					"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
  484   					"It is recommended to use a single HibernateTransactionManager for all transactions " +
  485   					"on a single DataSource, no matter whether Hibernate or JDBC access.");
  486   		}
  487   
  488   		Session session = null;
  489   
  490   		try {
  491   			if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
  492   				Interceptor entityInterceptor = getEntityInterceptor();
  493   				Session newSession = (entityInterceptor != null ?
  494   						getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession());
  495   				if (logger.isDebugEnabled()) {
  496   					logger.debug("Opened new Session [" + SessionFactoryUtils.toString(newSession) +
  497   							"] for Hibernate transaction");
  498   				}
  499   				txObject.setSession(newSession);
  500   			}
  501   
  502   			session = txObject.getSessionHolder().getSession();
  503   
  504   			if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
  505   				// We're allowed to change the transaction settings of the JDBC Connection.
  506   				if (logger.isDebugEnabled()) {
  507   					logger.debug(
  508   							"Preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]");
  509   				}
  510   				Connection con = session.connection();
  511   				Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
  512   				txObject.setPreviousIsolationLevel(previousIsolationLevel);
  513   			}
  514   			else {
  515   				// Not allowed to change the transaction settings of the JDBC Connection.
  516   				if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
  517   					// We should set a specific isolation level but are not allowed to...
  518   					throw new InvalidIsolationLevelException(
  519   							"HibernateTransactionManager is not allowed to support custom isolation levels: " +
  520   							"make sure that its 'prepareConnection' flag is on (the default) and that the " +
  521   							"Hibernate connection release mode is set to 'on_close' (SpringTransactionFactory's default). " +
  522   							"Make sure that your LocalSessionFactoryBean actually uses SpringTransactionFactory: Your " +
  523   							"Hibernate properties should *not* include a 'hibernate.transaction.factory_class' property!");
  524   				}
  525   				if (logger.isDebugEnabled()) {
  526   					logger.debug(
  527   							"Not preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]");
  528   				}
  529   			}
  530   
  531   			if (definition.isReadOnly() && txObject.isNewSession()) {
  532   				// Just set to NEVER in case of a new Session for this transaction.
  533   				session.setFlushMode(FlushMode.NEVER);
  534   			}
  535   
  536   			if (!definition.isReadOnly() && !txObject.isNewSession()) {
  537   				// We need AUTO or COMMIT for a non-read-only transaction.
  538   				FlushMode flushMode = session.getFlushMode();
  539   				if (flushMode.lessThan(FlushMode.COMMIT)) {
  540   					session.setFlushMode(FlushMode.AUTO);
  541   					txObject.getSessionHolder().setPreviousFlushMode(flushMode);
  542   				}
  543   			}
  544   
  545   			Transaction hibTx = null;
  546   
  547   			// Register transaction timeout.
  548   			int timeout = determineTimeout(definition);
  549   			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  550   				// Use Hibernate's own transaction timeout mechanism on Hibernate 3.1
  551   				// Applies to all statements, also to inserts, updates and deletes!
  552   				hibTx = session.getTransaction();
  553   				hibTx.setTimeout(timeout);
  554   				hibTx.begin();
  555   			}
  556   			else {
  557   				// Open a plain Hibernate transaction without specified timeout.
  558   				hibTx = session.beginTransaction();
  559   			}
  560   
  561   			// Add the Hibernate transaction to the session holder.
  562   			txObject.getSessionHolder().setTransaction(hibTx);
  563   
  564   			// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
  565   			if (getDataSource() != null) {
  566   				Connection con = session.connection();
  567   				ConnectionHolder conHolder = new ConnectionHolder(con);
  568   				if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  569   					conHolder.setTimeoutInSeconds(timeout);
  570   				}
  571   				if (logger.isDebugEnabled()) {
  572   					logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
  573   				}
  574   				TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
  575   				txObject.setConnectionHolder(conHolder);
  576   			}
  577   
  578   			// Bind the session holder to the thread.
  579   			if (txObject.isNewSessionHolder()) {
  580   				TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
  581   			}
  582   			txObject.getSessionHolder().setSynchronizedWithTransaction(true);
  583   		}
  584   
  585   		catch (Exception ex) {
  586   			if (txObject.isNewSession()) {
  587   				try {
  588   					if (session.getTransaction().isActive()) {
  589   						session.getTransaction().rollback();
  590   					}
  591   				}
  592   				catch (Throwable ex2) {
  593   					logger.debug("Could not rollback Session after failed transaction begin", ex);
  594   				}
  595   				finally {
  596   					SessionFactoryUtils.closeSession(session);
  597   				}
  598   			}
  599   			throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
  600   		}
  601   	}
  602   
  603   	protected Object doSuspend(Object transaction) {
  604   		HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  605   		txObject.setSessionHolder(null);
  606   		SessionHolder sessionHolder =
  607   				(SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
  608   		txObject.setConnectionHolder(null);
  609   		ConnectionHolder connectionHolder = null;
  610   		if (getDataSource() != null) {
  611   			connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
  612   		}
  613   		return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
  614   	}
  615   
  616   	protected void doResume(Object transaction, Object suspendedResources) {
  617   		SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
  618   		if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
  619   			// From non-transactional code running in active transaction synchronization
  620   			// -> can be safely removed, will be closed on transaction completion.
  621   			TransactionSynchronizationManager.unbindResource(getSessionFactory());
  622   		}
  623   		TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder());
  624   		if (getDataSource() != null) {
  625   			TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
  626   		}
  627   	}
  628   
  629   	protected void prepareForCommit(DefaultTransactionStatus status) {
  630   		if (this.earlyFlushBeforeCommit && status.isNewTransaction()) {
  631   			HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  632   			Session session = txObject.getSessionHolder().getSession();
  633   			if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
  634   				logger.debug("Performing an early flush for Hibernate transaction");
  635   				try {
  636   					session.flush();
  637   				}
  638   				catch (HibernateException ex) {
  639   					throw convertHibernateAccessException(ex);
  640   				}
  641   				finally {
  642   					session.setFlushMode(FlushMode.NEVER);
  643   				}
  644   			}
  645   		}
  646   	}
  647   
  648   	protected void doCommit(DefaultTransactionStatus status) {
  649   		HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  650   		if (status.isDebug()) {
  651   			logger.debug("Committing Hibernate transaction on Session [" +
  652   					SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "]");
  653   		}
  654   		try {
  655   			txObject.getSessionHolder().getTransaction().commit();
  656   		}
  657   		catch (org.hibernate.TransactionException ex) {
  658   			// assumably from commit call to the underlying JDBC connection
  659   			throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
  660   		}
  661   		catch (HibernateException ex) {
  662   			// assumably failed to flush changes to database
  663   			throw convertHibernateAccessException(ex);
  664   		}
  665   	}
  666   
  667   	protected void doRollback(DefaultTransactionStatus status) {
  668   		HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  669   		if (status.isDebug()) {
  670   			logger.debug("Rolling back Hibernate transaction on Session [" +
  671   					SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "]");
  672   		}
  673   		try {
  674   			txObject.getSessionHolder().getTransaction().rollback();
  675   		}
  676   		catch (org.hibernate.TransactionException ex) {
  677   			throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
  678   		}
  679   		catch (HibernateException ex) {
  680   			// Shouldn't really happen, as a rollback doesn't cause a flush.
  681   			throw convertHibernateAccessException(ex);
  682   		}
  683   		finally {
  684   			if (!txObject.isNewSession() && !this.hibernateManagedSession) {
  685   				// Clear all pending inserts/updates/deletes in the Session.
  686   				// Necessary for pre-bound Sessions, to avoid inconsistent state.
  687   				txObject.getSessionHolder().getSession().clear();
  688   			}
  689   		}
  690   	}
  691   
  692   	protected void doSetRollbackOnly(DefaultTransactionStatus status) {
  693   		HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
  694   		if (status.isDebug()) {
  695   			logger.debug("Setting Hibernate transaction on Session [" +
  696   					SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "] rollback-only");
  697   		}
  698   		txObject.setRollbackOnly();
  699   	}
  700   
  701   	protected void doCleanupAfterCompletion(Object transaction) {
  702   		HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
  703   
  704   		// Remove the session holder from the thread.
  705   		if (txObject.isNewSessionHolder()) {
  706   			TransactionSynchronizationManager.unbindResource(getSessionFactory());
  707   		}
  708   
  709   		// Remove the JDBC connection holder from the thread, if exposed.
  710   		if (getDataSource() != null) {
  711   			TransactionSynchronizationManager.unbindResource(getDataSource());
  712   		}
  713   
  714   		Session session = txObject.getSessionHolder().getSession();
  715   		if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
  716   			// We're running with connection release mode "on_close": We're able to reset
  717   			// the isolation level and/or read-only flag of the JDBC Connection here.
  718   			// Else, we need to rely on the connection pool to perform proper cleanup.
  719   			try {
  720   				Connection con = session.connection();
  721   				DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
  722   			}
  723   			catch (HibernateException ex) {
  724   				logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
  725   			}
  726   		}
  727   
  728   		if (txObject.isNewSession()) {
  729   			if (logger.isDebugEnabled()) {
  730   				logger.debug("Closing Hibernate Session [" + SessionFactoryUtils.toString(session) +
  731   						"] after transaction");
  732   			}
  733   			SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
  734   		}
  735   		else {
  736   			if (logger.isDebugEnabled()) {
  737   				logger.debug("Not closing pre-bound Hibernate Session [" +
  738   						SessionFactoryUtils.toString(session) + "] after transaction");
  739   			}
  740   			if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
  741   				session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
  742   			}
  743   			if (!this.hibernateManagedSession) {
  744   				session.disconnect();
  745   			}
  746   		}
  747   		txObject.getSessionHolder().clear();
  748   	}
  749   
  750   	/**
  751   	 * Return whether the given Hibernate Session will always hold the same
  752   	 * JDBC Connection. This is used to check whether the transaction manager
  753   	 * can safely prepare and clean up the JDBC Connection used for a transaction.
  754   	 * <p>Default implementation checks the Session's connection release mode
  755   	 * to be "on_close". Unfortunately, this requires casting to SessionImpl,
  756   	 * as of Hibernate 3.1. If that cast doesn't work, we'll simply assume
  757   	 * we're safe and return <code>true</code>.
  758   	 * @param session the Hibernate Session to check
  759   	 * @see org.hibernate.impl.SessionImpl#getConnectionReleaseMode()
  760   	 * @see org.hibernate.ConnectionReleaseMode#ON_CLOSE
  761   	 */
  762   	protected boolean isSameConnectionForEntireSession(Session session) {
  763   		if (!(session instanceof SessionImpl)) {
  764   			// The best we can do is to assume we're safe.
  765   			return true;
  766   		}
  767   		ConnectionReleaseMode releaseMode = ((SessionImpl) session).getConnectionReleaseMode();
  768   		return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
  769   	}
  770   
  771   
  772   	/**
  773   	 * Convert the given HibernateException to an appropriate exception
  774   	 * from the <code>org.springframework.dao</code> hierarchy.
  775   	 * <p>Will automatically apply a specified SQLExceptionTranslator to a
  776   	 * Hibernate JDBCException, else rely on Hibernate's default translation.
  777   	 * @param ex HibernateException that occured
  778   	 * @return a corresponding DataAccessException
  779   	 * @see SessionFactoryUtils#convertHibernateAccessException
  780   	 * @see #setJdbcExceptionTranslator
  781   	 */
  782   	protected DataAccessException convertHibernateAccessException(HibernateException ex) {
  783   		if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
  784   			return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
  785   		}
  786   		else if (GenericJDBCException.class.equals(ex.getClass())) {
  787   			return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
  788   		}
  789   		return SessionFactoryUtils.convertHibernateAccessException(ex);
  790   	}
  791   
  792   	/**
  793   	 * Convert the given Hibernate JDBCException to an appropriate exception
  794   	 * from the <code>org.springframework.dao</code> hierarchy, using the
  795   	 * given SQLExceptionTranslator.
  796   	 * @param ex Hibernate JDBCException that occured
  797   	 * @param translator the SQLExceptionTranslator to use
  798   	 * @return a corresponding DataAccessException
  799   	 */
  800   	protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
  801   		return translator.translate("Hibernate flushing: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
  802   	}
  803   
  804   	/**
  805   	 * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
  806   	 * <p>Creates a default
  807   	 * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
  808   	 * for the SessionFactory's underlying DataSource.
  809   	 */
  810   	protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
  811   		if (this.defaultJdbcExceptionTranslator == null) {
  812   			if (getDataSource() != null) {
  813   				this.defaultJdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(getDataSource());
  814   			}
  815   			else {
  816   				this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
  817   			}
  818   		}
  819   		return this.defaultJdbcExceptionTranslator;
  820   	}
  821   
  822   
  823   	/**
  824   	 * Hibernate transaction object, representing a SessionHolder.
  825   	 * Used as transaction object by HibernateTransactionManager.
  826   	 */
  827   	private static class HibernateTransactionObject extends JdbcTransactionObjectSupport {
  828   
  829   		private SessionHolder sessionHolder;
  830   
  831   		private boolean newSessionHolder;
  832   
  833   		private boolean newSession;
  834   
  835   		public void setSession(Session session) {
  836   			this.sessionHolder = new SessionHolder(session);
  837   			this.newSessionHolder = true;
  838   			this.newSession = true;
  839   		}
  840   
  841   		public void setExistingSession(Session session) {
  842   			this.sessionHolder = new SessionHolder(session);
  843   			this.newSessionHolder = true;
  844   			this.newSession = false;
  845   		}
  846   
  847   		public void setSessionHolder(SessionHolder sessionHolder) {
  848   			this.sessionHolder = sessionHolder;
  849   			this.newSessionHolder = false;
  850   			this.newSession = false;
  851   		}
  852   
  853   		public SessionHolder getSessionHolder() {
  854   			return this.sessionHolder;
  855   		}
  856   
  857   		public boolean isNewSessionHolder() {
  858   			return this.newSessionHolder;
  859   		}
  860   
  861   		public boolean isNewSession() {
  862   			return this.newSession;
  863   		}
  864   
  865   		public boolean hasSpringManagedTransaction() {
  866   			return (this.sessionHolder != null && this.sessionHolder.getTransaction() != null);
  867   		}
  868   
  869   		public boolean hasHibernateManagedTransaction() {
  870   			return (this.sessionHolder != null && this.sessionHolder.getSession().getTransaction().isActive());
  871   		}
  872   
  873   		public void setRollbackOnly() {
  874   			getSessionHolder().setRollbackOnly();
  875   			if (hasConnectionHolder()) {
  876   				getConnectionHolder().setRollbackOnly();
  877   			}
  878   		}
  879   
  880   		public boolean isRollbackOnly() {
  881   			return getSessionHolder().isRollbackOnly() ||
  882   					(hasConnectionHolder() && getConnectionHolder().isRollbackOnly());
  883   		}
  884   	}
  885   
  886   
  887   	/**
  888   	 * Holder for suspended resources.
  889   	 * Used internally by <code>doSuspend</code> and <code>doResume</code>.
  890   	 */
  891   	private static class SuspendedResourcesHolder {
  892   
  893   		private final SessionHolder sessionHolder;
  894   
  895   		private final ConnectionHolder connectionHolder;
  896   
  897   		private SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) {
  898   			this.sessionHolder = sessionHolder;
  899   			this.connectionHolder = conHolder;
  900   		}
  901   
  902   		private SessionHolder getSessionHolder() {
  903   			return this.sessionHolder;
  904   		}
  905   
  906   		private ConnectionHolder getConnectionHolder() {
  907   			return this.connectionHolder;
  908   		}
  909   	}
  910   
  911   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » orm » hibernate3 » [javadoc | source]