001/* 002 * Copyright 2002-2017 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.lang.reflect.Method; 020import java.util.Map; 021import javax.persistence.EntityExistsException; 022import javax.persistence.EntityManager; 023import javax.persistence.EntityManagerFactory; 024import javax.persistence.EntityNotFoundException; 025import javax.persistence.LockTimeoutException; 026import javax.persistence.NoResultException; 027import javax.persistence.NonUniqueResultException; 028import javax.persistence.OptimisticLockException; 029import javax.persistence.PersistenceException; 030import javax.persistence.PessimisticLockException; 031import javax.persistence.Query; 032import javax.persistence.QueryTimeoutException; 033import javax.persistence.TransactionRequiredException; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037 038import org.springframework.beans.factory.BeanFactoryUtils; 039import org.springframework.beans.factory.ListableBeanFactory; 040import org.springframework.beans.factory.NoSuchBeanDefinitionException; 041import org.springframework.core.Ordered; 042import org.springframework.dao.CannotAcquireLockException; 043import org.springframework.dao.DataAccessException; 044import org.springframework.dao.DataAccessResourceFailureException; 045import org.springframework.dao.DataIntegrityViolationException; 046import org.springframework.dao.EmptyResultDataAccessException; 047import org.springframework.dao.IncorrectResultSizeDataAccessException; 048import org.springframework.dao.InvalidDataAccessApiUsageException; 049import org.springframework.dao.PessimisticLockingFailureException; 050import org.springframework.jdbc.datasource.DataSourceUtils; 051import org.springframework.transaction.support.ResourceHolderSynchronization; 052import org.springframework.transaction.support.TransactionSynchronizationManager; 053import org.springframework.util.Assert; 054import org.springframework.util.ClassUtils; 055import org.springframework.util.CollectionUtils; 056import org.springframework.util.ReflectionUtils; 057import org.springframework.util.StringUtils; 058 059/** 060 * Helper class featuring methods for JPA EntityManager handling, 061 * allowing for reuse of EntityManager instances within transactions. 062 * Also provides support for exception translation. 063 * 064 * <p>Mainly intended for internal use within the framework. 065 * 066 * @author Juergen Hoeller 067 * @since 2.0 068 */ 069@SuppressWarnings("unchecked") 070public abstract class EntityManagerFactoryUtils { 071 072 /** 073 * Order value for TransactionSynchronization objects that clean up JPA 074 * EntityManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100 075 * to execute EntityManager cleanup before JDBC Connection cleanup, if any. 076 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER 077 */ 078 public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER = 079 DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100; 080 081 private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class); 082 083 084 private static Method createEntityManagerWithSynchronizationTypeMethod; 085 086 private static Object synchronizationTypeUnsynchronized; 087 088 static { 089 try { 090 @SuppressWarnings( "rawtypes" ) 091 Class<Enum> synchronizationTypeClass = (Class<Enum>) ClassUtils.forName( 092 "javax.persistence.SynchronizationType", EntityManagerFactoryUtils.class.getClassLoader()); 093 createEntityManagerWithSynchronizationTypeMethod = EntityManagerFactory.class.getMethod( 094 "createEntityManager", synchronizationTypeClass, Map.class); 095 synchronizationTypeUnsynchronized = Enum.valueOf(synchronizationTypeClass, "UNSYNCHRONIZED"); 096 } 097 catch (Exception ex) { 098 // No JPA 2.1 API available 099 createEntityManagerWithSynchronizationTypeMethod = null; 100 } 101 } 102 103 104 /** 105 * Find an EntityManagerFactory with the given name in the given 106 * Spring application context (represented as ListableBeanFactory). 107 * <p>The specified unit name will be matched against the configured 108 * persistence unit, provided that a discovered EntityManagerFactory 109 * implements the {@link EntityManagerFactoryInfo} interface. If not, 110 * the persistence unit name will be matched against the Spring bean name, 111 * assuming that the EntityManagerFactory bean names follow that convention. 112 * <p>If no unit name has been given, this method will search for a default 113 * EntityManagerFactory through {@link ListableBeanFactory#getBean(Class)}. 114 * @param beanFactory the ListableBeanFactory to search 115 * @param unitName the name of the persistence unit (may be {@code null} or empty, 116 * in which case a single bean of type EntityManagerFactory will be searched for) 117 * @return the EntityManagerFactory 118 * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory in the context 119 * @see EntityManagerFactoryInfo#getPersistenceUnitName() 120 */ 121 public static EntityManagerFactory findEntityManagerFactory( 122 ListableBeanFactory beanFactory, String unitName) throws NoSuchBeanDefinitionException { 123 124 Assert.notNull(beanFactory, "ListableBeanFactory must not be null"); 125 if (StringUtils.hasLength(unitName)) { 126 // See whether we can find an EntityManagerFactory with matching persistence unit name. 127 String[] candidateNames = 128 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, EntityManagerFactory.class); 129 for (String candidateName : candidateNames) { 130 EntityManagerFactory emf = (EntityManagerFactory) beanFactory.getBean(candidateName); 131 if (emf instanceof EntityManagerFactoryInfo) { 132 if (unitName.equals(((EntityManagerFactoryInfo) emf).getPersistenceUnitName())) { 133 return emf; 134 } 135 } 136 } 137 // No matching persistence unit found - simply take the EntityManagerFactory 138 // with the persistence unit name as bean name (by convention). 139 return beanFactory.getBean(unitName, EntityManagerFactory.class); 140 } 141 else { 142 // Find unique EntityManagerFactory bean in the context, falling back to parent contexts. 143 return beanFactory.getBean(EntityManagerFactory.class); 144 } 145 } 146 147 /** 148 * Obtain a JPA EntityManager from the given factory. Is aware of a corresponding 149 * EntityManager bound to the current thread, e.g. when using JpaTransactionManager. 150 * <p>Note: Will return {@code null} if no thread-bound EntityManager found! 151 * @param emf the EntityManagerFactory to create the EntityManager with 152 * @return the EntityManager, or {@code null} if none found 153 * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained 154 * @see JpaTransactionManager 155 */ 156 public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf) 157 throws DataAccessResourceFailureException { 158 159 return getTransactionalEntityManager(emf, null); 160 } 161 162 /** 163 * Obtain a JPA EntityManager from the given factory. Is aware of a corresponding 164 * EntityManager bound to the current thread, e.g. when using JpaTransactionManager. 165 * <p>Note: Will return {@code null} if no thread-bound EntityManager found! 166 * @param emf the EntityManagerFactory to create the EntityManager with 167 * @param properties the properties to be passed into the {@code createEntityManager} 168 * call (may be {@code null}) 169 * @return the EntityManager, or {@code null} if none found 170 * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained 171 * @see JpaTransactionManager 172 */ 173 public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf, Map<?, ?> properties) 174 throws DataAccessResourceFailureException { 175 try { 176 return doGetTransactionalEntityManager(emf, properties, true); 177 } 178 catch (PersistenceException ex) { 179 throw new DataAccessResourceFailureException("Could not obtain JPA EntityManager", ex); 180 } 181 } 182 183 /** 184 * Obtain a JPA EntityManager from the given factory. Is aware of a corresponding 185 * EntityManager bound to the current thread, e.g. when using JpaTransactionManager. 186 * <p>Same as {@code getEntityManager}, but throwing the original PersistenceException. 187 * @param emf the EntityManagerFactory to create the EntityManager with 188 * @param properties the properties to be passed into the {@code createEntityManager} 189 * call (may be {@code null}) 190 * @return the EntityManager, or {@code null} if none found 191 * @throws javax.persistence.PersistenceException if the EntityManager couldn't be created 192 * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory) 193 * @see JpaTransactionManager 194 */ 195 public static EntityManager doGetTransactionalEntityManager(EntityManagerFactory emf, Map<?, ?> properties) 196 throws PersistenceException { 197 198 return doGetTransactionalEntityManager(emf, properties, true); 199 } 200 201 /** 202 * Obtain a JPA EntityManager from the given factory. Is aware of a corresponding 203 * EntityManager bound to the current thread, e.g. when using JpaTransactionManager. 204 * <p>Same as {@code getEntityManager}, but throwing the original PersistenceException. 205 * @param emf the EntityManagerFactory to create the EntityManager with 206 * @param properties the properties to be passed into the {@code createEntityManager} 207 * call (may be {@code null}) 208 * @param synchronizedWithTransaction whether to automatically join ongoing 209 * transactions (according to the JPA 2.1 SynchronizationType rules) 210 * @return the EntityManager, or {@code null} if none found 211 * @throws javax.persistence.PersistenceException if the EntityManager couldn't be created 212 * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory) 213 * @see JpaTransactionManager 214 */ 215 public static EntityManager doGetTransactionalEntityManager( 216 EntityManagerFactory emf, Map<?, ?> properties, boolean synchronizedWithTransaction) throws PersistenceException { 217 218 Assert.notNull(emf, "No EntityManagerFactory specified"); 219 220 EntityManagerHolder emHolder = 221 (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf); 222 if (emHolder != null) { 223 if (synchronizedWithTransaction) { 224 if (!emHolder.isSynchronizedWithTransaction()) { 225 if (TransactionSynchronizationManager.isActualTransactionActive()) { 226 // Try to explicitly synchronize the EntityManager itself 227 // with an ongoing JTA transaction, if any. 228 try { 229 emHolder.getEntityManager().joinTransaction(); 230 } 231 catch (TransactionRequiredException ex) { 232 logger.debug("Could not join transaction because none was actually active", ex); 233 } 234 } 235 if (TransactionSynchronizationManager.isSynchronizationActive()) { 236 Object transactionData = prepareTransaction(emHolder.getEntityManager(), emf); 237 TransactionSynchronizationManager.registerSynchronization( 238 new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, false)); 239 emHolder.setSynchronizedWithTransaction(true); 240 } 241 } 242 // Use holder's reference count to track synchronizedWithTransaction access. 243 // isOpen() check used below to find out about it. 244 emHolder.requested(); 245 return emHolder.getEntityManager(); 246 } 247 else { 248 // unsynchronized EntityManager demanded 249 if (emHolder.isTransactionActive() && !emHolder.isOpen()) { 250 if (!TransactionSynchronizationManager.isSynchronizationActive()) { 251 return null; 252 } 253 // EntityManagerHolder with an active transaction coming from JpaTransactionManager, 254 // with no synchronized EntityManager having been requested by application code before. 255 // Unbind in order to register a new unsynchronized EntityManager instead. 256 TransactionSynchronizationManager.unbindResource(emf); 257 } 258 else { 259 // Either a previously bound unsynchronized EntityManager, or the application 260 // has requested a synchronized EntityManager before and therefore upgraded 261 // this transaction's EntityManager to synchronized before. 262 return emHolder.getEntityManager(); 263 } 264 } 265 } 266 else if (!TransactionSynchronizationManager.isSynchronizationActive()) { 267 // Indicate that we can't obtain a transactional EntityManager. 268 return null; 269 } 270 271 // Create a new EntityManager for use within the current transaction. 272 logger.debug("Opening JPA EntityManager"); 273 EntityManager em = null; 274 if (!synchronizedWithTransaction && createEntityManagerWithSynchronizationTypeMethod != null) { 275 try { 276 em = (EntityManager) ReflectionUtils.invokeMethod(createEntityManagerWithSynchronizationTypeMethod, 277 emf, synchronizationTypeUnsynchronized, properties); 278 } 279 catch (AbstractMethodError err) { 280 // JPA 2.1 API available but method not actually implemented in persistence provider: 281 // falling back to regular createEntityManager method. 282 } 283 } 284 if (em == null) { 285 em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager()); 286 } 287 288 try { 289 // Use same EntityManager for further JPA operations within the transaction. 290 // Thread-bound object will get removed by synchronization at transaction completion. 291 emHolder = new EntityManagerHolder(em); 292 if (synchronizedWithTransaction) { 293 Object transactionData = prepareTransaction(em, emf); 294 TransactionSynchronizationManager.registerSynchronization( 295 new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, true)); 296 emHolder.setSynchronizedWithTransaction(true); 297 } 298 else { 299 // Unsynchronized - just scope it for the transaction, as demanded by the JPA 2.1 spec... 300 TransactionSynchronizationManager.registerSynchronization( 301 new TransactionScopedEntityManagerSynchronization(emHolder, emf)); 302 } 303 TransactionSynchronizationManager.bindResource(emf, emHolder); 304 } 305 catch (RuntimeException ex) { 306 // Unexpected exception from external delegation call -> close EntityManager and rethrow. 307 closeEntityManager(em); 308 throw ex; 309 } 310 311 return em; 312 } 313 314 /** 315 * Prepare a transaction on the given EntityManager, if possible. 316 * @param em the EntityManager to prepare 317 * @param emf the EntityManagerFactory that the EntityManager has been created with 318 * @return an arbitrary object that holds transaction data, if any 319 * (to be passed into cleanupTransaction) 320 * @see JpaDialect#prepareTransaction 321 */ 322 private static Object prepareTransaction(EntityManager em, EntityManagerFactory emf) { 323 if (emf instanceof EntityManagerFactoryInfo) { 324 EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; 325 JpaDialect jpaDialect = emfInfo.getJpaDialect(); 326 if (jpaDialect != null) { 327 return jpaDialect.prepareTransaction(em, 328 TransactionSynchronizationManager.isCurrentTransactionReadOnly(), 329 TransactionSynchronizationManager.getCurrentTransactionName()); 330 } 331 } 332 return null; 333 } 334 335 /** 336 * Prepare a transaction on the given EntityManager, if possible. 337 * @param transactionData arbitrary object that holds transaction data, if any 338 * (as returned by prepareTransaction) 339 * @param emf the EntityManagerFactory that the EntityManager has been created with 340 * @see JpaDialect#cleanupTransaction 341 */ 342 private static void cleanupTransaction(Object transactionData, EntityManagerFactory emf) { 343 if (emf instanceof EntityManagerFactoryInfo) { 344 EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; 345 JpaDialect jpaDialect = emfInfo.getJpaDialect(); 346 if (jpaDialect != null) { 347 jpaDialect.cleanupTransaction(transactionData); 348 } 349 } 350 } 351 352 /** 353 * Apply the current transaction timeout, if any, to the given JPA Query object. 354 * <p>This method sets the JPA 2.0 query hint "javax.persistence.query.timeout" accordingly. 355 * @param query the JPA Query object 356 * @param emf the JPA EntityManagerFactory that the Query was created for 357 */ 358 public static void applyTransactionTimeout(Query query, EntityManagerFactory emf) { 359 EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf); 360 if (emHolder != null && emHolder.hasTimeout()) { 361 int timeoutValue = (int) emHolder.getTimeToLiveInMillis(); 362 try { 363 query.setHint("javax.persistence.query.timeout", timeoutValue); 364 } 365 catch (IllegalArgumentException ex) { 366 // oh well, at least we tried... 367 } 368 } 369 } 370 371 /** 372 * Convert the given runtime exception to an appropriate exception from the 373 * {@code org.springframework.dao} hierarchy. 374 * Return null if no translation is appropriate: any other exception may 375 * have resulted from user code, and should not be translated. 376 * <p>The most important cases like object not found or optimistic locking failure 377 * are covered here. For more fine-granular conversion, JpaTransactionManager etc 378 * support sophisticated translation of exceptions via a JpaDialect. 379 * @param ex runtime exception that occurred 380 * @return the corresponding DataAccessException instance, 381 * or {@code null} if the exception should not be translated 382 */ 383 public static DataAccessException convertJpaAccessExceptionIfPossible(RuntimeException ex) { 384 // Following the JPA specification, a persistence provider can also 385 // throw these two exceptions, besides PersistenceException. 386 if (ex instanceof IllegalStateException) { 387 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 388 } 389 if (ex instanceof IllegalArgumentException) { 390 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 391 } 392 393 // Check for well-known PersistenceException subclasses. 394 if (ex instanceof EntityNotFoundException) { 395 return new JpaObjectRetrievalFailureException((EntityNotFoundException) ex); 396 } 397 if (ex instanceof NoResultException) { 398 return new EmptyResultDataAccessException(ex.getMessage(), 1, ex); 399 } 400 if (ex instanceof NonUniqueResultException) { 401 return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex); 402 } 403 if (ex instanceof QueryTimeoutException) { 404 return new org.springframework.dao.QueryTimeoutException(ex.getMessage(), ex); 405 } 406 if (ex instanceof LockTimeoutException) { 407 return new CannotAcquireLockException(ex.getMessage(), ex); 408 } 409 if (ex instanceof PessimisticLockException) { 410 return new PessimisticLockingFailureException(ex.getMessage(), ex); 411 } 412 if (ex instanceof OptimisticLockException) { 413 return new JpaOptimisticLockingFailureException((OptimisticLockException) ex); 414 } 415 if (ex instanceof EntityExistsException) { 416 return new DataIntegrityViolationException(ex.getMessage(), ex); 417 } 418 if (ex instanceof TransactionRequiredException) { 419 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 420 } 421 422 // If we have another kind of PersistenceException, throw it. 423 if (ex instanceof PersistenceException) { 424 return new JpaSystemException(ex); 425 } 426 427 // If we get here, we have an exception that resulted from user code, 428 // rather than the persistence provider, so we return null to indicate 429 // that translation should not occur. 430 return null; 431 } 432 433 /** 434 * Close the given JPA EntityManager, 435 * catching and logging any cleanup exceptions thrown. 436 * @param em the JPA EntityManager to close (may be {@code null}) 437 * @see javax.persistence.EntityManager#close() 438 */ 439 public static void closeEntityManager(EntityManager em) { 440 if (em != null) { 441 try { 442 if (em.isOpen()) { 443 em.close(); 444 } 445 } 446 catch (PersistenceException ex) { 447 logger.debug("Could not close JPA EntityManager", ex); 448 } 449 catch (Throwable ex) { 450 logger.debug("Unexpected exception on closing JPA EntityManager", ex); 451 } 452 } 453 } 454 455 456 /** 457 * Callback for resource cleanup at the end of a non-JPA transaction 458 * (e.g. when participating in a JtaTransactionManager transaction), 459 * fully synchronized with the ongoing transaction. 460 * @see org.springframework.transaction.jta.JtaTransactionManager 461 */ 462 private static class TransactionalEntityManagerSynchronization 463 extends ResourceHolderSynchronization<EntityManagerHolder, EntityManagerFactory> 464 implements Ordered { 465 466 private final Object transactionData; 467 468 private final JpaDialect jpaDialect; 469 470 private final boolean newEntityManager; 471 472 public TransactionalEntityManagerSynchronization( 473 EntityManagerHolder emHolder, EntityManagerFactory emf, Object txData, boolean newEm) { 474 475 super(emHolder, emf); 476 this.transactionData = txData; 477 this.jpaDialect = (emf instanceof EntityManagerFactoryInfo ? 478 ((EntityManagerFactoryInfo) emf).getJpaDialect() : null); 479 this.newEntityManager = newEm; 480 } 481 482 @Override 483 public int getOrder() { 484 return ENTITY_MANAGER_SYNCHRONIZATION_ORDER; 485 } 486 487 @Override 488 protected void flushResource(EntityManagerHolder resourceHolder) { 489 EntityManager em = resourceHolder.getEntityManager(); 490 if (em instanceof EntityManagerProxy) { 491 EntityManager target = ((EntityManagerProxy) em).getTargetEntityManager(); 492 if (TransactionSynchronizationManager.hasResource(target)) { 493 // ExtendedEntityManagerSynchronization active after joinTransaction() call: 494 // flush synchronization already registered. 495 return; 496 } 497 } 498 try { 499 em.flush(); 500 } 501 catch (RuntimeException ex) { 502 DataAccessException dae; 503 if (this.jpaDialect != null) { 504 dae = this.jpaDialect.translateExceptionIfPossible(ex); 505 } 506 else { 507 dae = convertJpaAccessExceptionIfPossible(ex); 508 } 509 throw (dae != null ? dae : ex); 510 } 511 } 512 513 @Override 514 protected boolean shouldUnbindAtCompletion() { 515 return this.newEntityManager; 516 } 517 518 @Override 519 protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) { 520 closeEntityManager(resourceHolder.getEntityManager()); 521 } 522 523 @Override 524 protected void cleanupResource( 525 EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey, boolean committed) { 526 527 if (!committed) { 528 // Clear all pending inserts/updates/deletes in the EntityManager. 529 // Necessary for pre-bound EntityManagers, to avoid inconsistent state. 530 resourceHolder.getEntityManager().clear(); 531 } 532 cleanupTransaction(this.transactionData, resourceKey); 533 } 534 } 535 536 537 /** 538 * Minimal callback that just closes the EntityManager at the end of the transaction. 539 */ 540 private static class TransactionScopedEntityManagerSynchronization 541 extends ResourceHolderSynchronization<EntityManagerHolder, EntityManagerFactory> 542 implements Ordered { 543 544 public TransactionScopedEntityManagerSynchronization(EntityManagerHolder emHolder, EntityManagerFactory emf) { 545 super(emHolder, emf); 546 } 547 548 @Override 549 public int getOrder() { 550 return ENTITY_MANAGER_SYNCHRONIZATION_ORDER + 1; 551 } 552 553 @Override 554 protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) { 555 closeEntityManager(resourceHolder.getEntityManager()); 556 } 557 } 558 559}