001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.orm.jpa;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Properties;
022import javax.persistence.EntityManager;
023import javax.persistence.EntityManagerFactory;
024import javax.persistence.EntityTransaction;
025import javax.persistence.PersistenceException;
026import javax.persistence.RollbackException;
027import javax.sql.DataSource;
028
029import org.springframework.beans.BeansException;
030import org.springframework.beans.factory.BeanFactory;
031import org.springframework.beans.factory.BeanFactoryAware;
032import org.springframework.beans.factory.InitializingBean;
033import org.springframework.beans.factory.ListableBeanFactory;
034import org.springframework.dao.DataAccessException;
035import org.springframework.dao.support.DataAccessUtils;
036import org.springframework.jdbc.datasource.ConnectionHandle;
037import org.springframework.jdbc.datasource.ConnectionHolder;
038import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
039import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
040import org.springframework.transaction.CannotCreateTransactionException;
041import org.springframework.transaction.IllegalTransactionStateException;
042import org.springframework.transaction.NestedTransactionNotSupportedException;
043import org.springframework.transaction.SavepointManager;
044import org.springframework.transaction.TransactionDefinition;
045import org.springframework.transaction.TransactionException;
046import org.springframework.transaction.TransactionSystemException;
047import org.springframework.transaction.support.AbstractPlatformTransactionManager;
048import org.springframework.transaction.support.DefaultTransactionStatus;
049import org.springframework.transaction.support.DelegatingTransactionDefinition;
050import org.springframework.transaction.support.ResourceTransactionManager;
051import org.springframework.transaction.support.TransactionSynchronizationManager;
052import org.springframework.util.CollectionUtils;
053
054/**
055 * {@link org.springframework.transaction.PlatformTransactionManager} implementation
056 * for a single JPA {@link javax.persistence.EntityManagerFactory}. Binds a JPA
057 * EntityManager from the specified factory to the thread, potentially allowing for
058 * one thread-bound EntityManager per factory. {@link SharedEntityManagerCreator} and
059 * {@code @PersistenceContext} are aware of thread-bound entity managers and participate
060 * in such transactions automatically. Using either is required for JPA access code
061 * supporting this transaction management mechanism.
062 *
063 * <p>This transaction manager is appropriate for applications that use a single
064 * JPA EntityManagerFactory for transactional data access. JTA (usually through
065 * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary
066 * for accessing multiple transactional resources within the same transaction.
067 * Note that you need to configure your JPA provider accordingly in order to make
068 * it participate in JTA transactions.
069 *
070 * <p>This transaction manager also supports direct DataSource access within a
071 * transaction (i.e. plain JDBC code working with the same DataSource).
072 * This allows for mixing services which access JPA and services which use plain
073 * JDBC (without being aware of JPA)! Application code needs to stick to the
074 * same simple Connection lookup pattern as with
075 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
076 * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
077 * or going through a
078 * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
079 * Note that this requires a vendor-specific {@link JpaDialect} to be configured.
080 *
081 * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
082 * this instance needs to be aware of the DataSource ({@link #setDataSource}).
083 * The given DataSource should obviously match the one used by the given
084 * EntityManagerFactory. This transaction manager will autodetect the DataSource
085 * used as the connection factory of the EntityManagerFactory, so you usually
086 * don't need to explicitly specify the "dataSource" property.
087 *
088 * <p>This transaction manager supports nested transactions via JDBC 3.0 Savepoints.
089 * The {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults
090 * to {@code false} though, since nested transactions will just apply to the JDBC
091 * Connection, not to the JPA EntityManager and its cached entity objects and related
092 * context. You can manually set the flag to {@code true} if you want to use nested
093 * transactions for JDBC access code which participates in JPA transactions (provided
094 * that your JDBC driver supports Savepoints). <i>Note that JPA itself does not support
095 * nested transactions! Hence, do not expect JPA access code to semantically
096 * participate in a nested transaction.</i>
097 *
098 * @author Juergen Hoeller
099 * @since 2.0
100 * @see #setEntityManagerFactory
101 * @see #setDataSource
102 * @see LocalEntityManagerFactoryBean
103 * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
104 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
105 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
106 * @see org.springframework.jdbc.core.JdbcTemplate
107 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
108 * @see org.springframework.transaction.jta.JtaTransactionManager
109 */
110@SuppressWarnings("serial")
111public class JpaTransactionManager extends AbstractPlatformTransactionManager
112                implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
113
114        private EntityManagerFactory entityManagerFactory;
115
116        private String persistenceUnitName;
117
118        private final Map<String, Object> jpaPropertyMap = new HashMap<String, Object>();
119
120        private DataSource dataSource;
121
122        private JpaDialect jpaDialect = new DefaultJpaDialect();
123
124
125        /**
126         * Create a new JpaTransactionManager instance.
127         * <p>An EntityManagerFactory has to be set to be able to use it.
128         * @see #setEntityManagerFactory
129         */
130        public JpaTransactionManager() {
131                setNestedTransactionAllowed(true);
132        }
133
134        /**
135         * Create a new JpaTransactionManager instance.
136         * @param emf the EntityManagerFactory to manage transactions for
137         */
138        public JpaTransactionManager(EntityManagerFactory emf) {
139                this();
140                this.entityManagerFactory = emf;
141                afterPropertiesSet();
142        }
143
144
145        /**
146         * Set the EntityManagerFactory that this instance should manage transactions for.
147         * <p>Alternatively, specify the persistence unit name of the target EntityManagerFactory.
148         * By default, a default EntityManagerFactory will be retrieved by finding a
149         * single unique bean of type EntityManagerFactory in the containing BeanFactory.
150         * @see #setPersistenceUnitName
151         */
152        public void setEntityManagerFactory(EntityManagerFactory emf) {
153                this.entityManagerFactory = emf;
154        }
155
156        /**
157         * Return the EntityManagerFactory that this instance should manage transactions for.
158         */
159        public EntityManagerFactory getEntityManagerFactory() {
160                return this.entityManagerFactory;
161        }
162
163        /**
164         * Set the name of the persistence unit to manage transactions for.
165         * <p>This is an alternative to specifying the EntityManagerFactory by direct reference,
166         * resolving it by its persistence unit name instead. If no EntityManagerFactory and
167         * no persistence unit name have been specified, a default EntityManagerFactory will
168         * be retrieved by finding a single unique bean of type EntityManagerFactory.
169         * @see #setEntityManagerFactory
170         */
171        public void setPersistenceUnitName(String persistenceUnitName) {
172                this.persistenceUnitName = persistenceUnitName;
173        }
174
175        /**
176         * Return the name of the persistence unit to manage transactions for, if any.
177         */
178        public String getPersistenceUnitName() {
179                return this.persistenceUnitName;
180        }
181
182        /**
183         * Specify JPA properties, to be passed into
184         * {@code EntityManagerFactory.createEntityManager(Map)} (if any).
185         * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
186         * or a "props" element in XML bean definitions.
187         * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
188         */
189        public void setJpaProperties(Properties jpaProperties) {
190                CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
191        }
192
193        /**
194         * Specify JPA properties as a Map, to be passed into
195         * {@code EntityManagerFactory.createEntityManager(Map)} (if any).
196         * <p>Can be populated with a "map" or "props" element in XML bean definitions.
197         * @see javax.persistence.EntityManagerFactory#createEntityManager(java.util.Map)
198         */
199        public void setJpaPropertyMap(Map<String, ?> jpaProperties) {
200                if (jpaProperties != null) {
201                        this.jpaPropertyMap.putAll(jpaProperties);
202                }
203        }
204
205        /**
206         * Allow Map access to the JPA properties to be passed to the persistence
207         * provider, with the option to add or override specific entries.
208         * <p>Useful for specifying entries directly, for example via "jpaPropertyMap[myKey]".
209         */
210        public Map<String, Object> getJpaPropertyMap() {
211                return this.jpaPropertyMap;
212        }
213
214        /**
215         * Set the JDBC DataSource that this instance should manage transactions for.
216         * The DataSource should match the one used by the JPA EntityManagerFactory:
217         * for example, you could specify the same JNDI DataSource for both.
218         * <p>If the EntityManagerFactory uses a known DataSource as its connection factory,
219         * the DataSource will be autodetected: You can still explicitly specify the
220         * DataSource, but you don't need to in this case.
221         * <p>A transactional JDBC Connection for this DataSource will be provided to
222         * application code accessing this DataSource directly via DataSourceUtils
223         * or JdbcTemplate. The Connection will be taken from the JPA EntityManager.
224         * <p>Note that you need to use a JPA dialect for a specific JPA implementation
225         * to allow for exposing JPA transactions as JDBC transactions.
226         * <p>The DataSource specified here should be the target DataSource to manage
227         * transactions for, not a TransactionAwareDataSourceProxy. Only data access
228         * code may work with TransactionAwareDataSourceProxy, while the transaction
229         * manager needs to work on the underlying target DataSource. If there's
230         * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
231         * unwrapped to extract its target DataSource.
232         * @see EntityManagerFactoryInfo#getDataSource()
233         * @see #setJpaDialect
234         * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
235         * @see org.springframework.jdbc.datasource.DataSourceUtils
236         * @see org.springframework.jdbc.core.JdbcTemplate
237         */
238        public void setDataSource(DataSource dataSource) {
239                if (dataSource instanceof TransactionAwareDataSourceProxy) {
240                        // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
241                        // for its underlying target DataSource, else data access code won't see
242                        // properly exposed transactions (i.e. transactions for the target DataSource).
243                        this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
244                }
245                else {
246                        this.dataSource = dataSource;
247                }
248        }
249
250        /**
251         * Return the JDBC DataSource that this instance manages transactions for.
252         */
253        public DataSource getDataSource() {
254                return this.dataSource;
255        }
256
257        /**
258         * Set the JPA dialect to use for this transaction manager.
259         * Used for vendor-specific transaction management and JDBC connection exposure.
260         * <p>If the EntityManagerFactory uses a known JpaDialect, it will be autodetected:
261         * You can still explicitly specify the DataSource, but you don't need to in this case.
262         * <p>The dialect object can be used to retrieve the underlying JDBC connection
263         * and thus allows for exposing JPA transactions as JDBC transactions.
264         * @see EntityManagerFactoryInfo#getJpaDialect()
265         * @see JpaDialect#beginTransaction
266         * @see JpaDialect#getJdbcConnection
267         */
268        public void setJpaDialect(JpaDialect jpaDialect) {
269                this.jpaDialect = (jpaDialect != null ? jpaDialect : new DefaultJpaDialect());
270        }
271
272        /**
273         * Return the JPA dialect to use for this transaction manager.
274         */
275        public JpaDialect getJpaDialect() {
276                return this.jpaDialect;
277        }
278
279        /**
280         * Retrieves an EntityManagerFactory by persistence unit name, if none set explicitly.
281         * Falls back to a default EntityManagerFactory bean if no persistence unit specified.
282         * @see #setPersistenceUnitName
283         */
284        @Override
285        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
286                if (getEntityManagerFactory() == null) {
287                        if (!(beanFactory instanceof ListableBeanFactory)) {
288                                throw new IllegalStateException("Cannot retrieve EntityManagerFactory by persistence unit name " +
289                                                "in a non-listable BeanFactory: " + beanFactory);
290                        }
291                        ListableBeanFactory lbf = (ListableBeanFactory) beanFactory;
292                        setEntityManagerFactory(EntityManagerFactoryUtils.findEntityManagerFactory(lbf, getPersistenceUnitName()));
293                }
294        }
295
296        /**
297         * Eagerly initialize the JPA dialect, creating a default one
298         * for the specified EntityManagerFactory if none set.
299         * Auto-detect the EntityManagerFactory's DataSource, if any.
300         */
301        @Override
302        public void afterPropertiesSet() {
303                if (getEntityManagerFactory() == null) {
304                        throw new IllegalArgumentException("'entityManagerFactory' or 'persistenceUnitName' is required");
305                }
306                if (getEntityManagerFactory() instanceof EntityManagerFactoryInfo) {
307                        EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) getEntityManagerFactory();
308                        DataSource dataSource = emfInfo.getDataSource();
309                        if (dataSource != null) {
310                                setDataSource(dataSource);
311                        }
312                        JpaDialect jpaDialect = emfInfo.getJpaDialect();
313                        if (jpaDialect != null) {
314                                setJpaDialect(jpaDialect);
315                        }
316                }
317        }
318
319
320        @Override
321        public Object getResourceFactory() {
322                return getEntityManagerFactory();
323        }
324
325        @Override
326        protected Object doGetTransaction() {
327                JpaTransactionObject txObject = new JpaTransactionObject();
328                txObject.setSavepointAllowed(isNestedTransactionAllowed());
329
330                EntityManagerHolder emHolder = (EntityManagerHolder)
331                                TransactionSynchronizationManager.getResource(getEntityManagerFactory());
332                if (emHolder != null) {
333                        if (logger.isDebugEnabled()) {
334                                logger.debug("Found thread-bound EntityManager [" + emHolder.getEntityManager() +
335                                                "] for JPA transaction");
336                        }
337                        txObject.setEntityManagerHolder(emHolder, false);
338                }
339
340                if (getDataSource() != null) {
341                        ConnectionHolder conHolder = (ConnectionHolder)
342                                        TransactionSynchronizationManager.getResource(getDataSource());
343                        txObject.setConnectionHolder(conHolder);
344                }
345
346                return txObject;
347        }
348
349        @Override
350        protected boolean isExistingTransaction(Object transaction) {
351                return ((JpaTransactionObject) transaction).hasTransaction();
352        }
353
354        @Override
355        protected void doBegin(Object transaction, TransactionDefinition definition) {
356                JpaTransactionObject txObject = (JpaTransactionObject) transaction;
357
358                if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
359                        throw new IllegalTransactionStateException(
360                                        "Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
361                                        "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
362                                        "It is recommended to use a single JpaTransactionManager for all transactions " +
363                                        "on a single DataSource, no matter whether JPA or JDBC access.");
364                }
365
366                try {
367                        if (txObject.getEntityManagerHolder() == null ||
368                                        txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
369                                EntityManager newEm = createEntityManagerForTransaction();
370                                if (logger.isDebugEnabled()) {
371                                        logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
372                                }
373                                txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
374                        }
375
376                        EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
377
378                        // Delegate to JpaDialect for actual transaction begin.
379                        final int timeoutToUse = determineTimeout(definition);
380                        Object transactionData = getJpaDialect().beginTransaction(em,
381                                        new DelegatingTransactionDefinition(definition) {
382                                                @Override
383                                                public int getTimeout() {
384                                                        return timeoutToUse;
385                                                }
386                                        });
387                        txObject.setTransactionData(transactionData);
388
389                        // Register transaction timeout.
390                        if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
391                                txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
392                        }
393
394                        // Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
395                        if (getDataSource() != null) {
396                                ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
397                                if (conHandle != null) {
398                                        ConnectionHolder conHolder = new ConnectionHolder(conHandle);
399                                        if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
400                                                conHolder.setTimeoutInSeconds(timeoutToUse);
401                                        }
402                                        if (logger.isDebugEnabled()) {
403                                                logger.debug("Exposing JPA transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
404                                        }
405                                        TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
406                                        txObject.setConnectionHolder(conHolder);
407                                }
408                                else {
409                                        if (logger.isDebugEnabled()) {
410                                                logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because " +
411                                                                "JpaDialect [" + getJpaDialect() + "] does not support JDBC Connection retrieval");
412                                        }
413                                }
414                        }
415
416                        // Bind the entity manager holder to the thread.
417                        if (txObject.isNewEntityManagerHolder()) {
418                                TransactionSynchronizationManager.bindResource(
419                                                getEntityManagerFactory(), txObject.getEntityManagerHolder());
420                        }
421                        txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
422                }
423
424                catch (TransactionException ex) {
425                        closeEntityManagerAfterFailedBegin(txObject);
426                        throw ex;
427                }
428                catch (Throwable ex) {
429                        closeEntityManagerAfterFailedBegin(txObject);
430                        throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
431                }
432        }
433
434        /**
435         * Create a JPA EntityManager to be used for a transaction.
436         * <p>The default implementation checks whether the EntityManagerFactory
437         * is a Spring proxy and unwraps it first.
438         * @see javax.persistence.EntityManagerFactory#createEntityManager()
439         * @see EntityManagerFactoryInfo#getNativeEntityManagerFactory()
440         */
441        protected EntityManager createEntityManagerForTransaction() {
442                EntityManagerFactory emf = getEntityManagerFactory();
443                if (emf instanceof EntityManagerFactoryInfo) {
444                        emf = ((EntityManagerFactoryInfo) emf).getNativeEntityManagerFactory();
445                }
446                Map<String, Object> properties = getJpaPropertyMap();
447                return (!CollectionUtils.isEmpty(properties) ?
448                                emf.createEntityManager(properties) : emf.createEntityManager());
449        }
450
451        /**
452         * Close the current transaction's EntityManager.
453         * Called after a transaction begin attempt failed.
454         * @param txObject the current transaction
455         */
456        protected void closeEntityManagerAfterFailedBegin(JpaTransactionObject txObject) {
457                if (txObject.isNewEntityManagerHolder()) {
458                        EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
459                        try {
460                                if (em.getTransaction().isActive()) {
461                                        em.getTransaction().rollback();
462                                }
463                        }
464                        catch (Throwable ex) {
465                                logger.debug("Could not rollback EntityManager after failed transaction begin", ex);
466                        }
467                        finally {
468                                EntityManagerFactoryUtils.closeEntityManager(em);
469                        }
470                        txObject.setEntityManagerHolder(null, false);
471                }
472        }
473
474        @Override
475        protected Object doSuspend(Object transaction) {
476                JpaTransactionObject txObject = (JpaTransactionObject) transaction;
477                txObject.setEntityManagerHolder(null, false);
478                EntityManagerHolder entityManagerHolder = (EntityManagerHolder)
479                                TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
480                txObject.setConnectionHolder(null);
481                ConnectionHolder connectionHolder = null;
482                if (getDataSource() != null && TransactionSynchronizationManager.hasResource(getDataSource())) {
483                        connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
484                }
485                return new SuspendedResourcesHolder(entityManagerHolder, connectionHolder);
486        }
487
488        @Override
489        protected void doResume(Object transaction, Object suspendedResources) {
490                SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
491                TransactionSynchronizationManager.bindResource(
492                                getEntityManagerFactory(), resourcesHolder.getEntityManagerHolder());
493                if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) {
494                        TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
495                }
496        }
497
498        /**
499         * This implementation returns "true": a JPA commit will properly handle
500         * transactions that have been marked rollback-only at a global level.
501         */
502        @Override
503        protected boolean shouldCommitOnGlobalRollbackOnly() {
504                return true;
505        }
506
507        @Override
508        protected void doCommit(DefaultTransactionStatus status) {
509                JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
510                if (status.isDebug()) {
511                        logger.debug("Committing JPA transaction on EntityManager [" +
512                                        txObject.getEntityManagerHolder().getEntityManager() + "]");
513                }
514                try {
515                        EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
516                        tx.commit();
517                }
518                catch (RollbackException ex) {
519                        if (ex.getCause() instanceof RuntimeException) {
520                                DataAccessException dex = getJpaDialect().translateExceptionIfPossible((RuntimeException) ex.getCause());
521                                if (dex != null) {
522                                        throw dex;
523                                }
524                        }
525                        throw new TransactionSystemException("Could not commit JPA transaction", ex);
526                }
527                catch (RuntimeException ex) {
528                        // Assumably failed to flush changes to database.
529                        throw DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
530                }
531        }
532
533        @Override
534        protected void doRollback(DefaultTransactionStatus status) {
535                JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
536                if (status.isDebug()) {
537                        logger.debug("Rolling back JPA transaction on EntityManager [" +
538                                        txObject.getEntityManagerHolder().getEntityManager() + "]");
539                }
540                try {
541                        EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
542                        if (tx.isActive()) {
543                                tx.rollback();
544                        }
545                }
546                catch (PersistenceException ex) {
547                        throw new TransactionSystemException("Could not roll back JPA transaction", ex);
548                }
549                finally {
550                        if (!txObject.isNewEntityManagerHolder()) {
551                                // Clear all pending inserts/updates/deletes in the EntityManager.
552                                // Necessary for pre-bound EntityManagers, to avoid inconsistent state.
553                                txObject.getEntityManagerHolder().getEntityManager().clear();
554                        }
555                }
556        }
557
558        @Override
559        protected void doSetRollbackOnly(DefaultTransactionStatus status) {
560                JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
561                if (status.isDebug()) {
562                        logger.debug("Setting JPA transaction on EntityManager [" +
563                                        txObject.getEntityManagerHolder().getEntityManager() + "] rollback-only");
564                }
565                txObject.setRollbackOnly();
566        }
567
568        @Override
569        protected void doCleanupAfterCompletion(Object transaction) {
570                JpaTransactionObject txObject = (JpaTransactionObject) transaction;
571
572                // Remove the entity manager holder from the thread, if still there.
573                // (Could have been removed by EntityManagerFactoryUtils in order
574                // to replace it with an unsynchronized EntityManager).
575                if (txObject.isNewEntityManagerHolder()) {
576                        TransactionSynchronizationManager.unbindResourceIfPossible(getEntityManagerFactory());
577                }
578                txObject.getEntityManagerHolder().clear();
579
580                // Remove the JDBC connection holder from the thread, if exposed.
581                if (txObject.hasConnectionHolder()) {
582                        TransactionSynchronizationManager.unbindResource(getDataSource());
583                        ConnectionHandle conHandle = txObject.getConnectionHolder().getConnectionHandle();
584                        if (conHandle != null) {
585                                try {
586                                        getJpaDialect().releaseJdbcConnection(conHandle,
587                                                        txObject.getEntityManagerHolder().getEntityManager());
588                                }
589                                catch (Throwable ex) {
590                                        // Just log it, to keep a transaction-related exception.
591                                        logger.error("Failed to release JDBC connection after transaction", ex);
592                                }
593                        }
594                }
595
596                getJpaDialect().cleanupTransaction(txObject.getTransactionData());
597
598                // Remove the entity manager holder from the thread.
599                if (txObject.isNewEntityManagerHolder()) {
600                        EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
601                        if (logger.isDebugEnabled()) {
602                                logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
603                        }
604                        EntityManagerFactoryUtils.closeEntityManager(em);
605                }
606                else {
607                        logger.debug("Not closing pre-bound JPA EntityManager after transaction");
608                }
609        }
610
611
612        /**
613         * JPA transaction object, representing a EntityManagerHolder.
614         * Used as transaction object by JpaTransactionManager.
615         */
616        private class JpaTransactionObject extends JdbcTransactionObjectSupport {
617
618                private EntityManagerHolder entityManagerHolder;
619
620                private boolean newEntityManagerHolder;
621
622                private Object transactionData;
623
624                public void setEntityManagerHolder(
625                                EntityManagerHolder entityManagerHolder, boolean newEntityManagerHolder) {
626                        this.entityManagerHolder = entityManagerHolder;
627                        this.newEntityManagerHolder = newEntityManagerHolder;
628                }
629
630                public EntityManagerHolder getEntityManagerHolder() {
631                        return this.entityManagerHolder;
632                }
633
634                public boolean isNewEntityManagerHolder() {
635                        return this.newEntityManagerHolder;
636                }
637
638                public boolean hasTransaction() {
639                        return (this.entityManagerHolder != null && this.entityManagerHolder.isTransactionActive());
640                }
641
642                public void setTransactionData(Object transactionData) {
643                        this.transactionData = transactionData;
644                        this.entityManagerHolder.setTransactionActive(true);
645                        if (transactionData instanceof SavepointManager) {
646                                this.entityManagerHolder.setSavepointManager((SavepointManager) transactionData);
647                        }
648                }
649
650                public Object getTransactionData() {
651                        return this.transactionData;
652                }
653
654                public void setRollbackOnly() {
655                        EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction();
656                        if (tx.isActive()) {
657                                tx.setRollbackOnly();
658                        }
659                        if (hasConnectionHolder()) {
660                                getConnectionHolder().setRollbackOnly();
661                        }
662                }
663
664                @Override
665                public boolean isRollbackOnly() {
666                        EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction();
667                        return tx.getRollbackOnly();
668                }
669
670                @Override
671                public void flush() {
672                        try {
673                                this.entityManagerHolder.getEntityManager().flush();
674                        }
675                        catch (RuntimeException ex) {
676                                throw DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
677                        }
678                }
679
680                @Override
681                public Object createSavepoint() throws TransactionException {
682                        return getSavepointManager().createSavepoint();
683                }
684
685                @Override
686                public void rollbackToSavepoint(Object savepoint) throws TransactionException {
687                        getSavepointManager().rollbackToSavepoint(savepoint);
688                }
689
690                @Override
691                public void releaseSavepoint(Object savepoint) throws TransactionException {
692                        getSavepointManager().releaseSavepoint(savepoint);
693                }
694
695                private SavepointManager getSavepointManager() {
696                        if (!isSavepointAllowed()) {
697                                throw new NestedTransactionNotSupportedException(
698                                                "Transaction manager does not allow nested transactions");
699                        }
700                        SavepointManager savepointManager = getEntityManagerHolder().getSavepointManager();
701                        if (savepointManager == null) {
702                                throw new NestedTransactionNotSupportedException(
703                                                "JpaDialect does not support savepoints - check your JPA provider's capabilities");
704                        }
705                        return savepointManager;
706                }
707        }
708
709
710        /**
711         * Holder for suspended resources.
712         * Used internally by {@code doSuspend} and {@code doResume}.
713         */
714        private static class SuspendedResourcesHolder {
715
716                private final EntityManagerHolder entityManagerHolder;
717
718                private final ConnectionHolder connectionHolder;
719
720                private SuspendedResourcesHolder(EntityManagerHolder emHolder, ConnectionHolder conHolder) {
721                        this.entityManagerHolder = emHolder;
722                        this.connectionHolder = conHolder;
723                }
724
725                private EntityManagerHolder getEntityManagerHolder() {
726                        return this.entityManagerHolder;
727                }
728
729                private ConnectionHolder getConnectionHolder() {
730                        return this.connectionHolder;
731                }
732        }
733
734}