1 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.support.DataAccessUtils; 32 import org.springframework.jdbc.datasource.ConnectionHandle; 33 import org.springframework.jdbc.datasource.ConnectionHolder; 34 import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport; 35 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; 36 import org.springframework.transaction.CannotCreateTransactionException; 37 import org.springframework.transaction.IllegalTransactionStateException; 38 import org.springframework.transaction.TransactionDefinition; 39 import org.springframework.transaction.TransactionException; 40 import org.springframework.transaction.TransactionSystemException; 41 import org.springframework.transaction.UnexpectedRollbackException; 42 import org.springframework.transaction.support.AbstractPlatformTransactionManager; 43 import org.springframework.transaction.support.DefaultTransactionStatus; 44 import org.springframework.transaction.support.TransactionSynchronizationManager; 45 import org.springframework.transaction.support.ResourceTransactionManager; 46 import org.springframework.util.CollectionUtils; 47 48 105 public class JpaTransactionManager extends AbstractPlatformTransactionManager 106 implements ResourceTransactionManager, InitializingBean { 107 108 private EntityManagerFactory entityManagerFactory; 109 110 private final Map jpaPropertyMap = new HashMap (); 111 112 private DataSource dataSource; 113 114 private JpaDialect jpaDialect = new DefaultJpaDialect(); 115 116 117 122 public JpaTransactionManager() { 123 } 124 125 129 public JpaTransactionManager(EntityManagerFactory emf) { 130 this.entityManagerFactory = emf; 131 afterPropertiesSet(); 132 } 133 134 137 public void setEntityManagerFactory(EntityManagerFactory emf) { 138 this.entityManagerFactory = emf; 139 } 140 141 144 public EntityManagerFactory getEntityManagerFactory() { 145 return this.entityManagerFactory; 146 } 147 148 155 public void setJpaProperties(Properties jpaProperties) { 156 CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap); 157 } 158 159 165 public void setJpaPropertyMap(Map jpaProperties) { 166 if (jpaProperties != null) { 167 this.jpaPropertyMap.putAll(jpaProperties); 168 } 169 } 170 171 176 public Map getJpaPropertyMap() { 177 return this.jpaPropertyMap; 178 } 179 180 204 public void setDataSource(DataSource dataSource) { 205 if (dataSource instanceof TransactionAwareDataSourceProxy) { 206 this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); 210 } 211 else { 212 this.dataSource = dataSource; 213 } 214 } 215 216 219 public DataSource getDataSource() { 220 return this.dataSource; 221 } 222 223 234 public void setJpaDialect(JpaDialect jpaDialect) { 235 this.jpaDialect = (jpaDialect != null ? jpaDialect : new DefaultJpaDialect()); 236 } 237 238 241 public JpaDialect getJpaDialect() { 242 return this.jpaDialect; 243 } 244 245 250 public void afterPropertiesSet() { 251 if (getEntityManagerFactory() == null) { 252 throw new IllegalArgumentException ("entityManagerFactory is required"); 253 } 254 if (getEntityManagerFactory() instanceof EntityManagerFactoryInfo) { 255 EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) getEntityManagerFactory(); 256 DataSource dataSource = emfInfo.getDataSource(); 257 if (dataSource != null) { 258 setDataSource(dataSource); 259 } 260 JpaDialect jpaDialect = emfInfo.getJpaDialect(); 261 if (jpaDialect != null) { 262 setJpaDialect(jpaDialect); 263 } 264 } 265 } 266 267 268 public Object getResourceFactory() { 269 return getEntityManagerFactory(); 270 } 271 272 protected Object doGetTransaction() { 273 JpaTransactionObject txObject = new JpaTransactionObject(); 274 txObject.setSavepointAllowed(isNestedTransactionAllowed()); 275 276 EntityManagerHolder emHolder = (EntityManagerHolder) 277 TransactionSynchronizationManager.getResource(getEntityManagerFactory()); 278 if (emHolder != null) { 279 if (logger.isDebugEnabled()) { 280 logger.debug("Found thread-bound EntityManager [" + 281 emHolder.getEntityManager() + "] for JPA transaction"); 282 } 283 txObject.setEntityManagerHolder(emHolder, false); 284 } 285 286 if (getDataSource() != null) { 287 ConnectionHolder conHolder = (ConnectionHolder) 288 TransactionSynchronizationManager.getResource(getDataSource()); 289 txObject.setConnectionHolder(conHolder); 290 } 291 292 return txObject; 293 } 294 295 protected boolean isExistingTransaction(Object transaction) { 296 return ((JpaTransactionObject) transaction).hasTransaction(); 297 } 298 299 protected void doBegin(Object transaction, TransactionDefinition definition) { 300 JpaTransactionObject txObject = (JpaTransactionObject) transaction; 301 302 if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) { 303 throw new IllegalTransactionStateException( 304 "Pre-bound JDBC Connection found! JpaTransactionManager does not support " + 305 "running within DataSourceTransactionManager if told to manage the DataSource itself. " + 306 "It is recommended to use a single JpaTransactionManager for all transactions " + 307 "on a single DataSource, no matter whether JPA or JDBC access."); 308 } 309 310 EntityManager em = null; 311 312 try { 313 if (txObject.getEntityManagerHolder() == null || 314 txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) { 315 EntityManager newEm = createEntityManagerForTransaction(); 316 if (logger.isDebugEnabled()) { 317 logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction"); 318 } 319 txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true); 320 } 321 322 txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true); 323 em = txObject.getEntityManagerHolder().getEntityManager(); 324 325 Object transactionData = getJpaDialect().beginTransaction(em, definition); 327 txObject.setTransactionData(transactionData); 328 329 int timeout = determineTimeout(definition); 331 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { 332 txObject.getEntityManagerHolder().setTimeoutInSeconds(timeout); 333 } 334 335 if (getDataSource() != null) { 337 ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly()); 338 if (conHandle != null) { 339 ConnectionHolder conHolder = new ConnectionHolder(conHandle); 340 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { 341 conHolder.setTimeoutInSeconds(timeout); 342 } 343 if (logger.isDebugEnabled()) { 344 logger.debug("Exposing JPA transaction as JDBC transaction [" + conHolder.getConnectionHandle() + "]"); 345 } 346 TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); 347 txObject.setConnectionHolder(conHolder); 348 } 349 else { 350 if (logger.isDebugEnabled()) { 351 logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because JpaDialect [" + 352 getJpaDialect() + "] does not support JDBC Connection retrieval"); 353 } 354 } 355 } 356 357 if (txObject.isNewEntityManagerHolder()) { 359 TransactionSynchronizationManager.bindResource( 360 getEntityManagerFactory(), txObject.getEntityManagerHolder()); 361 } 362 } 363 364 catch (TransactionException ex) { 365 if (em != null) { 366 em.close(); 367 } 368 throw ex; 369 } 370 catch (Exception ex) { 371 if (em != null) { 372 em.close(); 373 } 374 throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex); 375 } 376 } 377 378 385 protected EntityManager createEntityManagerForTransaction() { 386 EntityManagerFactory emf = getEntityManagerFactory(); 387 if (emf instanceof EntityManagerFactoryInfo) { 388 emf = ((EntityManagerFactoryInfo) emf).getNativeEntityManagerFactory(); 389 } 390 Map properties = getJpaPropertyMap(); 391 return (!CollectionUtils.isEmpty(properties) ? 392 emf.createEntityManager(properties) : emf.createEntityManager()); 393 } 394 395 protected Object doSuspend(Object transaction) { 396 JpaTransactionObject txObject = (JpaTransactionObject) transaction; 397 txObject.setEntityManagerHolder(null, false); 398 EntityManagerHolder entityManagerHolder = (EntityManagerHolder) 399 TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); 400 txObject.setConnectionHolder(null); 401 ConnectionHolder connectionHolder = null; 402 if (getDataSource() != null) { 403 connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource()); 404 } 405 return new SuspendedResourcesHolder(entityManagerHolder, connectionHolder); 406 } 407 408 protected void doResume(Object transaction, Object suspendedResources) { 409 SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources; 410 TransactionSynchronizationManager.bindResource( 411 getEntityManagerFactory(), resourcesHolder.getEntityManagerHolder()); 412 if (getDataSource() != null) { 413 TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder()); 414 } 415 } 416 417 421 protected boolean shouldCommitOnGlobalRollbackOnly() { 422 return true; 423 } 424 425 protected void doCommit(DefaultTransactionStatus status) { 426 JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction(); 427 if (status.isDebug()) { 428 logger.debug("Committing JPA transaction on EntityManager [" + 429 txObject.getEntityManagerHolder().getEntityManager() + "]"); 430 } 431 try { 432 EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction(); 433 tx.commit(); 434 } 435 catch (RollbackException ex) { 436 throw new UnexpectedRollbackException( 437 "JPA transaction unexpectedly rolled back (maybe marked rollback-only after a failed operation)", ex); 438 } 439 catch (RuntimeException rawException) { 440 throw DataAccessUtils.translateIfNecessary(rawException, getJpaDialect()); 442 } 443 } 444 445 protected void doRollback(DefaultTransactionStatus status) { 446 JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction(); 447 if (status.isDebug()) { 448 logger.debug("Rolling back JPA transaction on EntityManager [" + 449 txObject.getEntityManagerHolder().getEntityManager() + "]"); 450 } 451 try { 452 EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction(); 453 if (tx.isActive()) { 454 tx.rollback(); 455 } 456 } 457 catch (PersistenceException ex) { 458 throw new TransactionSystemException("Could not roll back JPA transaction", ex); 459 } 460 finally { 461 if (!txObject.isNewEntityManagerHolder()) { 462 txObject.getEntityManagerHolder().getEntityManager().clear(); 465 } 466 } 467 } 468 469 protected void doSetRollbackOnly(DefaultTransactionStatus status) { 470 JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction(); 471 if (status.isDebug()) { 472 logger.debug("Setting JPA transaction on EntityManager [" + 473 txObject.getEntityManagerHolder().getEntityManager() + "] rollback-only"); 474 } 475 txObject.setRollbackOnly(); 476 } 477 478 protected void doCleanupAfterCompletion(Object transaction) { 479 JpaTransactionObject txObject = (JpaTransactionObject) transaction; 480 481 if (txObject.isNewEntityManagerHolder()) { 483 TransactionSynchronizationManager.unbindResource(getEntityManagerFactory()); 484 } 485 txObject.getEntityManagerHolder().clear(); 486 487 if (txObject.hasConnectionHolder()) { 489 TransactionSynchronizationManager.unbindResource(getDataSource()); 490 try { 491 getJpaDialect().releaseJdbcConnection(txObject.getConnectionHolder().getConnectionHandle(), 492 txObject.getEntityManagerHolder().getEntityManager()); 493 } 494 catch (Exception ex) { 495 logger.error("Could not close JDBC connection after transaction", ex); 497 } 498 } 499 500 getJpaDialect().cleanupTransaction(txObject.getTransactionData()); 501 502 if (txObject.isNewEntityManagerHolder()) { 504 EntityManager em = txObject.getEntityManagerHolder().getEntityManager(); 505 if (logger.isDebugEnabled()) { 506 logger.debug("Closing JPA EntityManager [" + em + "] after transaction"); 507 } 508 em.close(); 509 } 510 else { 511 logger.debug("Not closing pre-bound JPA EntityManager after transaction"); 512 } 513 } 514 515 516 525 private static class JpaTransactionObject extends JdbcTransactionObjectSupport { 526 527 private EntityManagerHolder entityManagerHolder; 528 529 private boolean newEntityManagerHolder; 530 531 private Object transactionData; 532 533 public void setEntityManagerHolder( 534 EntityManagerHolder entityManagerHolder, boolean newEntityManagerHolder) { 535 this.entityManagerHolder = entityManagerHolder; 536 this.newEntityManagerHolder = newEntityManagerHolder; 537 } 538 539 public EntityManagerHolder getEntityManagerHolder() { 540 return this.entityManagerHolder; 541 } 542 543 public boolean isNewEntityManagerHolder() { 544 return this.newEntityManagerHolder; 545 } 546 547 public boolean hasTransaction() { 548 return (this.entityManagerHolder != null && 549 this.entityManagerHolder.getEntityManager() != null && 550 this.entityManagerHolder.getEntityManager().getTransaction().isActive()); 551 } 552 553 public void setTransactionData(Object transactionData) { 554 this.transactionData = transactionData; 555 } 556 557 public Object getTransactionData() { 558 return this.transactionData; 559 } 560 561 public void setRollbackOnly() { 562 EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction(); 563 if (tx.isActive()) { 564 tx.setRollbackOnly(); 565 } 566 if (hasConnectionHolder()) { 567 getConnectionHolder().setRollbackOnly(); 568 } 569 } 570 571 public boolean isRollbackOnly() { 572 EntityTransaction tx = this.entityManagerHolder.getEntityManager().getTransaction(); 573 return tx.getRollbackOnly(); 574 } 575 } 576 577 578 582 private static class SuspendedResourcesHolder { 583 584 private final EntityManagerHolder entityManagerHolder; 585 586 private final ConnectionHolder connectionHolder; 587 588 private SuspendedResourcesHolder(EntityManagerHolder emHolder, ConnectionHolder conHolder) { 589 this.entityManagerHolder = emHolder; 590 this.connectionHolder = conHolder; 591 } 592 593 private EntityManagerHolder getEntityManagerHolder() { 594 return this.entityManagerHolder; 595 } 596 597 private ConnectionHolder getConnectionHolder() { 598 return this.connectionHolder; 599 } 600 } 601 602 } 603 | Popular Tags |