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 }