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}