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