Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » orm » jpa » [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.jpa;
   18   
   19   import java.util.HashMap;
   20   import java.util.Map;
   21   import java.util.Properties;
   22   
   23   import javax.persistence.EntityManager;
   24   import javax.persistence.EntityManagerFactory;
   25   import javax.persistence.EntityTransaction;
   26   import javax.persistence.PersistenceException;
   27   import javax.persistence.RollbackException;
   28   import javax.sql.DataSource;
   29   
   30   import org.springframework.beans.factory.InitializingBean;
   31   import org.springframework.dao.DataAccessException;
   32   import org.springframework.dao.support.DataAccessUtils;
   33   import org.springframework.jdbc.datasource.ConnectionHandle;
   34   import org.springframework.jdbc.datasource.ConnectionHolder;
   35   import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
   36   import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
   37   import org.springframework.transaction.CannotCreateTransactionException;
   38   import org.springframework.transaction.IllegalTransactionStateException;
   39   import org.springframework.transaction.NestedTransactionNotSupportedException;
   40   import org.springframework.transaction.SavepointManager;
   41   import org.springframework.transaction.TransactionDefinition;
   42   import org.springframework.transaction.TransactionException;
   43   import org.springframework.transaction.TransactionSystemException;
   44   import org.springframework.transaction.support.AbstractPlatformTransactionManager;
   45   import org.springframework.transaction.support.DefaultTransactionStatus;
   46   import org.springframework.transaction.support.ResourceTransactionManager;
   47   import org.springframework.transaction.support.TransactionSynchronizationManager;
   48   import org.springframework.util.CollectionUtils;
   49   
   50   /**
   51    * {@link org.springframework.transaction.PlatformTransactionManager} implementation
   52    * for a single JPA {@link javax.persistence.EntityManagerFactory}. Binds a JPA
   53    * EntityManager from the specified factory to the thread, potentially allowing for
   54    * one thread-bound EntityManager per factory. {@link SharedEntityManagerCreator}
   55    * and {@link JpaTemplate} are aware of thread-bound entity managers and participate
   56    * in such transactions automatically. Using either is required for JPA access code
   57    * supporting this transaction management mechanism.
   58    *
   59    * <p>This transaction manager is appropriate for applications that use a single
   60    * JPA EntityManagerFactory for  transactional data access. JTA (usually through
   61    * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary
   62    * for accessing multiple transactional resources within the same transaction.
   63    * Note that you need to configure your JPA provider accordingly in order to make
   64    * it participate in JTA transactions.
   65    *
   66    * <p>This transaction manager also supports direct DataSource access within a
   67    * transaction (i.e. plain JDBC code working with the same DataSource).
   68    * This allows for mixing services which access JPA and services which use plain
   69    * JDBC (without being aware of JPA)! Application code needs to stick to the
   70    * same simple Connection lookup pattern as with
   71    * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
   72    * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
   73    * or going through a
   74    * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
   75    * Note that this requires a vendor-specific {@link JpaDialect} to be configured.
   76    *
   77    * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
   78    * this instance needs to be aware of the DataSource ({@link #setDataSource}).
   79    * The given DataSource should obviously match the one used by the given
   80    * EntityManagerFactory. This transaction manager will autodetect the DataSource
   81    * used as known connection factory of the EntityManagerFactory, so you usually
   82    * don't need to explicitly specify the "dataSource" property.
   83    *
   84    * <p>On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
   85    * Savepoints. The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"}
   86    * flag defaults to "false", though, as nested transactions will just apply to the
   87    * JDBC Connection, not to the JPA EntityManager and its cached objects.
   88    * You can manually set the flag to "true" if you want to use nested transactions
   89    * for JDBC access code which participates in JPA transactions (provided that your
   90    * JDBC driver supports Savepoints). <i>Note that JPA itself does not support
   91    * nested transactions! Hence, do not expect JPA access code to semantically
   92    * participate in a nested transaction.</i>
   93    *
   94    * @author Juergen Hoeller
   95    * @since 2.0
   96    * @see #setEntityManagerFactory
   97    * @see #setDataSource
   98    * @see LocalEntityManagerFactoryBean
   99    * @see JpaTemplate#execute
  100    * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
  101    * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
  102    * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
  103    * @see org.springframework.jdbc.core.JdbcTemplate
  104    * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
  105    * @see org.springframework.transaction.jta.JtaTransactionManager
  106    */
  107   public class JpaTransactionManager extends AbstractPlatformTransactionManager
  108   		implements ResourceTransactionManager, InitializingBean {
  109   
  110   	private EntityManagerFactory entityManagerFactory;
  111   
  112   	private final Map jpaPropertyMap = new HashMap();
  113   
  114   	private DataSource dataSource;
  115   
  116   	private JpaDialect jpaDialect = new DefaultJpaDialect();
  117   
  118   
  119   	/**
  120   	 * Create a new JpaTransactionManager instance.
  121   	 * A EntityManagerFactory has to be set to be able to use it.
  122   	 * @see #setEntityManagerFactory
  123   	 */
  124   	public JpaTransactionManager() {
  125   		setNestedTransactionAllowed(true);
  126   	}
  127   
  128   	/**
  129   	 * Create a new JpaTransactionManager instance.
  130   	 * @param emf EntityManagerFactory to manage transactions for
  131   	 */
  132   	public JpaTransactionManager(EntityManagerFactory emf) {
  133   		this();
  134   		this.entityManagerFactory = emf;
  135   		afterPropertiesSet();
  136   	}
  137   
  138   
  139   	/**
  140   	 * Set the EntityManagerFactory that this instance should manage transactions for.
  141   	 */
  142   	public void setEntityManagerFactory(EntityManagerFactory emf) {
  143   		this.entityManagerFactory = emf;
  144   	}
  145   
  146   	/**
  147   	 * Return the EntityManagerFactory that this instance should manage transactions for.
  148   	 */
  149   	public EntityManagerFactory getEntityManagerFactory() {
  150   		return this.entityManagerFactory;
  151   	}
  152   
  153   	/**
  154   	 * Specify JPA properties, to be passed into
  155   	 * <code>EntityManagerFactory.createEntityManager(Map)</code> (if any).
  156   	 * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
  157   	 * or a "props" element in XML bean definitions.
  158   	 * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
  159   	 */
  160   	public void setJpaProperties(Properties jpaProperties) {
  161   		CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
  162   	}
  163   
  164   	/**
  165   	 * Specify JPA properties as a Map, to be passed into
  166   	 * <code>EntityManagerFactory.createEntityManager(Map)</code> (if any).
  167   	 * <p>Can be populated with a "map" or "props" element in XML bean definitions.
  168   	 * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
  169   	 */
  170   	public void setJpaPropertyMap(Map jpaProperties) {
  171   		if (jpaProperties != null) {
  172   			this.jpaPropertyMap.putAll(jpaProperties);
  173   		}
  174   	}
  175   
  176   	/**
  177   	 * Allow Map access to the JPA properties to be passed to the persistence
  178   	 * provider, with the option to add or override specific entries.
  179   	 * <p>Useful for specifying entries directly, for example via "jpaPropertyMap[myKey]".
  180   	 */
  181   	public Map getJpaPropertyMap() {
  182   		return this.jpaPropertyMap;
  183   	}
  184   
  185   	/**
  186   	 * Set the JDBC DataSource that this instance should manage transactions for.
  187      * The DataSource should match the one used by the JPA EntityManagerFactory:
  188   	 * for example, you could specify the same JNDI DataSource for both.
  189   	 * <p>If the EntityManagerFactory uses a known DataSource as connection factory,
  190   	 * the DataSource will be autodetected: You can still explictly specify the
  191   	 * DataSource, but you don't need to in this case.
  192   	 * <p>A transactional JDBC Connection for this DataSource will be provided to
  193   	 * application code accessing this DataSource directly via DataSourceUtils
  194   	 * or JdbcTemplate. The Connection will be taken from the JPA EntityManager.
  195   	 * <p>Note that you need to use a JPA dialect for a specific JPA implementation
  196   	 * to allow for exposing JPA transactions as JDBC transactions.
  197   	 * <p>The DataSource specified here should be the target DataSource to manage
  198   	 * transactions for, not a TransactionAwareDataSourceProxy. Only data access
  199   	 * code may work with TransactionAwareDataSourceProxy, while the transaction
  200   	 * manager needs to work on the underlying target DataSource. If there's
  201   	 * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
  202   	 * unwrapped to extract its target DataSource.
  203   	 * @see EntityManagerFactoryInfo#getDataSource()
  204   	 * @see #setJpaDialect
  205   	 * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
  206   	 * @see org.springframework.jdbc.datasource.DataSourceUtils
  207   	 * @see org.springframework.jdbc.core.JdbcTemplate
  208   	 */
  209   	public void setDataSource(DataSource dataSource) {
  210   		if (dataSource instanceof TransactionAwareDataSourceProxy) {
  211   			// If we got a TransactionAwareDataSourceProxy, we need to perform transactions
  212   			// for its underlying target DataSource, else data access code won't see
  213   			// properly exposed transactions (i.e. transactions for the target DataSource).
  214   			this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
  215   		}
  216   		else {
  217   			this.dataSource = dataSource;
  218   		}
  219   	}
  220   
  221   	/**
  222   	 * Return the JDBC DataSource that this instance manages transactions for.
  223   	 */
  224   	public DataSource getDataSource() {
  225   		return this.dataSource;
  226   	}
  227   
  228   	/**
  229   	 * Set the JPA dialect to use for this transaction manager.
  230   	 * Used for vendor-specific transaction management and JDBC connection exposure.
  231   	 * <p>If the EntityManagerFactory uses a known JpaDialect, it will be autodetected:
  232   	 * You can still explictly specify the DataSource, but you don't need to in this case.
  233   	 * <p>The dialect object can be used to retrieve the underlying JDBC connection
  234   	 * and thus allows for exposing JPA transactions as JDBC transactions.
  235   	 * @see EntityManagerFactoryInfo#getJpaDialect()
  236   	 * @see JpaDialect#beginTransaction
  237   	 * @see JpaDialect#getJdbcConnection
  238   	 */
  239   	public void setJpaDialect(JpaDialect jpaDialect) {
  240   		this.jpaDialect = (jpaDialect != null ? jpaDialect : new DefaultJpaDialect());
  241   	}
  242   
  243   	/**
  244   	 * Return the JPA dialect to use for this transaction manager.
  245   	 */
  246   	public JpaDialect getJpaDialect() {
  247   		return this.jpaDialect;
  248   	}
  249   
  250   	/**
  251   	 * Eagerly initialize the JPA dialect, creating a default one
  252   	 * for the specified EntityManagerFactory if none set.
  253   	 * Auto-detect the EntityManagerFactory's DataSource, if any.
  254   	 */
  255   	public void afterPropertiesSet() {
  256   		if (getEntityManagerFactory() == null) {
  257   			throw new IllegalArgumentException("Property 'entityManagerFactory' is required");
  258   		}
  259   		if (getEntityManagerFactory() instanceof EntityManagerFactoryInfo) {
  260   			EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) getEntityManagerFactory();
  261   			DataSource dataSource = emfInfo.getDataSource();
  262   			if (dataSource != null) {
  263   				setDataSource(dataSource);
  264   			}
  265   			JpaDialect jpaDialect = emfInfo.getJpaDialect();
  266   			if (jpaDialect != null) {
  267   				setJpaDialect(jpaDialect);
  268   			}
  269   		}
  270   	}
  271   
  272   
  273   	public Object getResourceFactory() {
  274   		return getEntityManagerFactory();
  275   	}
  276   
  277   	protected Object doGetTransaction() {
  278   		JpaTransactionObject txObject = new JpaTransactionObject();
  279   		txObject.setSavepointAllowed(isNestedTransactionAllowed());
  280   
  281   		EntityManagerHolder emHolder = (EntityManagerHolder)
  282   				TransactionSynchronizationManager.getResource(getEntityManagerFactory());
  283   		if (emHolder != null) {
  284   			if (logger.isDebugEnabled()) {
  285   				logger.debug("Found thread-bound EntityManager [" +
  286   						emHolder.getEntityManager() + "] for JPA transaction");
  287   			}
  288   			txObject.setEntityManagerHolder(emHolder, false);
  289   		}
  290   
  291   		if (getDataSource() != null) {
  292   			ConnectionHolder conHolder = (ConnectionHolder)
  293   					TransactionSynchronizationManager.getResource(getDataSource());
  294   			txObject.setConnectionHolder(conHolder);
  295   		}
  296   
  297   		return txObject;
  298   	}
  299   
  300   	protected boolean isExistingTransaction(Object transaction) {
  301   		return ((JpaTransactionObject) transaction).hasTransaction();
  302   	}
  303   
  304   	protected void doBegin(Object transaction, TransactionDefinition definition) {
  305   		JpaTransactionObject txObject = (JpaTransactionObject) transaction;
  306   
  307   		if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
  308   			throw new IllegalTransactionStateException(
  309   					"Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
  310   					"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
  311   					"It is recommended to use a single JpaTransactionManager for all transactions " +
  312   					"on a single DataSource, no matter whether JPA or JDBC access.");
  313   		}
  314   
  315   		EntityManager em = null;
  316   
  317   		try {
  318   			if (txObject.getEntityManagerHolder() == null ||
  319   					txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
  320   				EntityManager newEm = createEntityManagerForTransaction();
  321   				if (logger.isDebugEnabled()) {
  322   					logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
  323   				}
  324   				txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
  325   			}
  326   
  327   			em = txObject.getEntityManagerHolder().getEntityManager();
  328   
  329   			// Delegate to JpaDialect for actual transaction begin.
  330   			Object transactionData = getJpaDialect().beginTransaction(em, definition);
  331   			txObject.setTransactionData(transactionData);
  332   
  333   			// Register transaction timeout.
  334   			int timeout = determineTimeout(definition);
  335   			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  336   				txObject.getEntityManagerHolder().setTimeoutInSeconds(timeout);
  337   			}
  338   
  339   			// Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
  340   			if (getDataSource() != null) {
  341   				ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
  342   				if (conHandle != null) {
  343   					ConnectionHolder conHolder = new ConnectionHolder(conHandle);
  344   					if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  345   						conHolder.setTimeoutInSeconds(timeout);
  346   					}
  347   					if (logger.isDebugEnabled()) {
  348   						logger.debug("Exposing JPA transaction as JDBC transaction [" + conHolder.getConnectionHandle() + "]");
  349   					}
  350   					TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
  351   					txObject.setConnectionHolder(conHolder);
  352   				}
  353   				else {
  354   					if (logger.isDebugEnabled()) {
  355   						logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because JpaDialect [" +
  356   								getJpaDialect() + "] does not support JDBC Connection retrieval");
  357   					}
  358   				}
  359   			}
  360   
  361   			// Bind the entity manager holder to the thread.
  362   			if (txObject.isNewEntityManagerHolder()) {
  363   				TransactionSynchronizationManager.bindResource(
  364   						getEntityManagerFactory(), txObject.getEntityManagerHolder());
  365   			}
  366   			txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
  367   		}
  368   
  369   		catch (TransactionException ex) {
  370   			closeEntityManagerAfterFailedBegin(txObject);
  371   			throw ex;
  372   		}
  373   		catch (Exception ex) {
  374   			closeEntityManagerAfterFailedBegin(txObject);
  375   			throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
  376   		}
  377   	}
  378   
  379   	/**
  380   	 * Create a JPA EntityManager to be used for a transaction.
  381   	 * <p>The default implementation checks whether the EntityManagerFactory
  382   	 * is a Spring proxy and unwraps it first.
  383   	 * @see javax.persistence.EntityManagerFactory#createEntityManager()
  384   	 * @see EntityManagerFactoryInfo#getNativeEntityManagerFactory()
  385   	 */
  386   	protected EntityManager createEntityManagerForTransaction() {
  387   		EntityManagerFactory emf = getEntityManagerFactory();
  388   		if (emf instanceof EntityManagerFactoryInfo) {
  389   			emf = ((EntityManagerFactoryInfo) emf).getNativeEntityManagerFactory();
  390   		}
  391   		Map properties = getJpaPropertyMap();
  392   		return (!CollectionUtils.isEmpty(properties) ?
  393   				emf.createEntityManager(properties) : emf.createEntityManager());
  394   	}
  395   
  396   	/**
  397   	 * Close the current transaction's EntityManager.
  398   	 * Called after a transaction begin attempt failed.
  399   	 * @param txObject the current transaction
  400   	 */
  401   	protected void closeEntityManagerAfterFailedBegin(JpaTransactionObject txObject) {
  402   		if (txObject.isNewEntityManagerHolder()) {
  403   			EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
  404   			try {
  405   				if (em.getTransaction().isActive()) {
  406   					em.getTransaction().rollback();
  407   				}
  408   			}
  409   			catch (Throwable ex) {
  410   				logger.debug("Could not rollback EntityManager after failed transaction begin", ex);
  411   			}
  412   			finally {
  413   				EntityManagerFactoryUtils.closeEntityManager(em);
  414   			}
  415   		}
  416   	}
  417   
  418   	protected Object doSuspend(Object transaction) {
  419   		JpaTransactionObject txObject = (JpaTransactionObject) transaction;
  420   		txObject.setEntityManagerHolder(null, false);
  421   		EntityManagerHolder entityManagerHolder = (EntityManagerHolder)
  422   				TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
  423   		txObject.setConnectionHolder(null);
  424   		ConnectionHolder connectionHolder = null;
  425   		if (getDataSource() != null && TransactionSynchronizationManager.hasResource(getDataSource())) {
  426   			connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
  427   		}
  428   		return new SuspendedResourcesHolder(entityManagerHolder, connectionHolder);
  429   	}
  430   
  431   	protected void doResume(Object transaction, Object suspendedResources) {
  432   		SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
  433   		TransactionSynchronizationManager.bindResource(
  434   				getEntityManagerFactory(), resourcesHolder.getEntityManagerHolder());
  435   		if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) {
  436   			TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
  437   		}
  438   	}
  439   
  440   	/**
  441   	 * This implementation returns "true": a JPA commit will properly handle
  442   	 * transactions that have been marked rollback-only at a global level.
  443   	 */
  444   	protected boolean shouldCommitOnGlobalRollbackOnly() {
  445   		return true;
  446   	}
  447   
  448   	protected void doCommit(DefaultTransactionStatus status) {
  449   		JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
  450   		if (status.isDebug()) {
  451   			logger.debug("Committing JPA transaction on EntityManager [" +
  452   					txObject.getEntityManagerHolder().getEntityManager() + "]");
  453   		}
  454   		try {
  455   			EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
  456   			tx.commit();
  457   		}
  458   		catch (RollbackException ex) {
  459   			if (ex.getCause() instanceof RuntimeException) {
  460   				DataAccessException dex = getJpaDialect().translateExceptionIfPossible((RuntimeException) ex.getCause());
  461   				if (dex != null) {
  462   					throw dex;
  463   				}
  464   			}
  465   			throw new TransactionSystemException("Could not commit JPA transaction", ex);
  466   		}
  467   		catch (RuntimeException ex) {
  468   			// Assumably failed to flush changes to database.
  469   			throw DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
  470   		}
  471   	}
  472   
  473   	protected void doRollback(DefaultTransactionStatus status) {
  474   		JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
  475   		if (status.isDebug()) {
  476   			logger.debug("Rolling back JPA transaction on EntityManager [" +
  477   					txObject.getEntityManagerHolder().getEntityManager() + "]");
  478   		}
  479   		try {
  480   			EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
  481   			if (tx.isActive()) {
  482   				tx.rollback();
  483   			}
  484   		}
  485   		catch (PersistenceException ex) {
  486   			throw new TransactionSystemException("Could not roll back JPA transaction", ex);
  487   		}
  488   		finally {
  489   			if (!txObject.isNewEntityManagerHolder()) {
  490   				// Clear all pending inserts/updates/deletes in the EntityManager.
  491   				// Necessary for pre-bound EntityManagers, to avoid inconsistent state.
  492   				txObject.getEntityManagerHolder().getEntityManager().clear();
  493   			}
  494   		}
  495   	}
  496   
  497   	protected void doSetRollbackOnly(DefaultTransactionStatus status) {
  498   		JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
  499   		if (status.isDebug()) {
  500   			logger.debug("Setting JPA transaction on EntityManager [" +
  501   					txObject.getEntityManagerHolder().getEntityManager() + "] rollback-only");
  502   		}
  503   		txObject.setRollbackOnly();
  504   	}
  505   
  506   	protected void doCleanupAfterCompletion(Object transaction) {
  507   		JpaTransactionObject txObject = (JpaTransactionObject) transaction;
  508   
  509   		// Remove the entity manager holder from the thread.
  510   		if (txObject.isNewEntityManagerHolder()) {
  511   			TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
  512   		}
  513   		txObject.getEntityManagerHolder().clear();
  514   
  515   		// Remove the JDBC connection holder from the thread, if exposed.
  516   		if (txObject.hasConnectionHolder()) {
  517   			TransactionSynchronizationManager.unbindResource(getDataSource());
  518   			try {
  519   				getJpaDialect().releaseJdbcConnection(txObject.getConnectionHolder().getConnectionHandle(),
  520   						txObject.getEntityManagerHolder().getEntityManager());
  521   			}
  522   			catch (Exception ex) {
  523   				// Just log it, to keep a transaction-related exception.
  524   				logger.error("Could not close JDBC connection after transaction", ex);
  525   			}
  526   		}
  527   
  528   		getJpaDialect().cleanupTransaction(txObject.getTransactionData());
  529   
  530   		// Remove the entity manager holder from the thread.
  531   		if (txObject.isNewEntityManagerHolder()) {
  532   			EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
  533   			if (logger.isDebugEnabled()) {
  534   				logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
  535   			}
  536   			EntityManagerFactoryUtils.closeEntityManager(em);
  537   		}
  538   		else {
  539   			logger.debug("Not closing pre-bound JPA EntityManager after transaction");
  540   		}
  541   	}
  542   
  543   
  544   	/**
  545   	 * JPA transaction object, representing a EntityManagerHolder.
  546   	 * Used as transaction object by JpaTransactionManager.
  547   	 */
  548   	private static class JpaTransactionObject extends JdbcTransactionObjectSupport {
  549   
  550   		private EntityManagerHolder entityManagerHolder;
  551   
  552   		private boolean newEntityManagerHolder;
  553   
  554   		private Object transactionData;
  555   
  556   		public void setEntityManagerHolder(
  557   				EntityManagerHolder entityManagerHolder, boolean newEntityManagerHolder) {
  558   			this.entityManagerHolder = entityManagerHolder;
  559   			this.newEntityManagerHolder = newEntityManagerHolder;
  560   		}
  561   
  562   		public EntityManagerHolder getEntityManagerHolder() {
  563   			return this.entityManagerHolder;
  564   		}
  565   
  566   		public boolean isNewEntityManagerHolder() {
  567   			return this.newEntityManagerHolder;
  568   		}
  569   
  570   		public boolean hasTransaction() {
  571   			return (this.entityManagerHolder != null && this.entityManagerHolder.isTransactionActive());
  572   		}
  573   
  574   		public void setTransactionData(Object transactionData) {
  575   			this.transactionData = transactionData;
  576   			this.entityManagerHolder.setTransactionActive(true);
  577   			if (transactionData instanceof SavepointManager) {
  578   				this.entityManagerHolder.setSavepointManager((SavepointManager) transactionData);
  579   			}
  580   		}
  581   
  582   		public Object getTransactionData() {
  583   			return this.transactionData;
  584   		}
  585   
  586   		public void setRollbackOnly() {
  587   			EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction();
  588   			if (tx.isActive()) {
  589   				tx.setRollbackOnly();
  590   			}
  591   			if (hasConnectionHolder()) {
  592   				getConnectionHolder().setRollbackOnly();
  593   			}
  594   		}
  595   
  596   		public boolean isRollbackOnly() {
  597   			EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction();
  598   			return tx.getRollbackOnly();
  599   		}
  600   
  601   		public Object createSavepoint() throws TransactionException {
  602   			return getSavepointManager().createSavepoint();
  603   		}
  604   
  605   		public void rollbackToSavepoint(Object savepoint) throws TransactionException {
  606   			getSavepointManager().rollbackToSavepoint(savepoint);
  607   		}
  608   
  609   		public void releaseSavepoint(Object savepoint) throws TransactionException {
  610   			getSavepointManager().releaseSavepoint(savepoint);
  611   		}
  612   
  613   		private SavepointManager getSavepointManager() {
  614   			if (!isSavepointAllowed()) {
  615   				throw new NestedTransactionNotSupportedException(
  616   						"Transaction manager does not allow nested transactions");
  617   			}
  618   			SavepointManager savepointManager = getEntityManagerHolder().getSavepointManager();
  619   			if (savepointManager == null) {
  620   				throw new NestedTransactionNotSupportedException(
  621   						"JpaDialect does not support savepoints - check your JPA provider's capabilities");
  622   			}
  623   			return savepointManager;
  624   		}
  625   	}
  626   
  627   
  628   	/**
  629   	 * Holder for suspended resources.
  630   	 * Used internally by <code>doSuspend</code> and <code>doResume</code>.
  631   	 */
  632   	private static class SuspendedResourcesHolder {
  633   
  634   		private final EntityManagerHolder entityManagerHolder;
  635   
  636   		private final ConnectionHolder connectionHolder;
  637   
  638   		private SuspendedResourcesHolder(EntityManagerHolder emHolder, ConnectionHolder conHolder) {
  639   			this.entityManagerHolder = emHolder;
  640   			this.connectionHolder = conHolder;
  641   		}
  642   
  643   		private EntityManagerHolder getEntityManagerHolder() {
  644   			return this.entityManagerHolder;
  645   		}
  646   
  647   		private ConnectionHolder getConnectionHolder() {
  648   			return this.connectionHolder;
  649   		}
  650   	}
  651   
  652   }

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