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}