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