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