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

Save This Page
Home » Spring-Framework-090522 » org.springframework » orm » hibernate3 » [javadoc | source]