001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.orm.jpa;
018:
019: import java.util.Map;
020:
021: import javax.persistence.EntityExistsException;
022: import javax.persistence.EntityManager;
023: import javax.persistence.EntityManagerFactory;
024: import javax.persistence.EntityNotFoundException;
025: import javax.persistence.NoResultException;
026: import javax.persistence.NonUniqueResultException;
027: import javax.persistence.OptimisticLockException;
028: import javax.persistence.PersistenceException;
029: import javax.persistence.TransactionRequiredException;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033:
034: import org.springframework.beans.factory.BeanFactoryUtils;
035: import org.springframework.beans.factory.ListableBeanFactory;
036: import org.springframework.beans.factory.NoSuchBeanDefinitionException;
037: import org.springframework.dao.DataAccessException;
038: import org.springframework.dao.DataAccessResourceFailureException;
039: import org.springframework.dao.DataIntegrityViolationException;
040: import org.springframework.dao.EmptyResultDataAccessException;
041: import org.springframework.dao.IncorrectResultSizeDataAccessException;
042: import org.springframework.dao.InvalidDataAccessApiUsageException;
043: import org.springframework.jdbc.datasource.DataSourceUtils;
044: import org.springframework.transaction.support.TransactionSynchronizationAdapter;
045: import org.springframework.transaction.support.TransactionSynchronizationManager;
046: import org.springframework.util.Assert;
047: import org.springframework.util.CollectionUtils;
048:
049: /**
050: * Helper class featuring methods for JPA EntityManager handling,
051: * allowing for reuse of EntityManager instances within transactions.
052: * Also provides support for exception translation.
053: *
054: * <p>Mainly intended for internal use within the framework.
055: *
056: * @author Juergen Hoeller
057: * @since 2.0
058: */
059: public abstract class EntityManagerFactoryUtils {
060:
061: /**
062: * Order value for TransactionSynchronization objects that clean up JPA
063: * EntityManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
064: * to execute EntityManager cleanup before JDBC Connection cleanup, if any.
065: * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
066: */
067: public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER = DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
068:
069: private static final Log logger = LogFactory
070: .getLog(EntityManagerFactoryUtils.class);
071:
072: /**
073: * Find an EntityManagerFactory with the given name in the given
074: * Spring application context (represented as ListableBeanFactory).
075: * <p>The specified unit name will be matched against the configured
076: * peristence unit, provided that a discovered EntityManagerFactory
077: * implements the {@link EntityManagerFactoryInfo} interface. If not,
078: * the persistence unit name will be matched against the Spring bean name,
079: * assuming that the EntityManagerFactory bean names follow that convention.
080: * @param beanFactory the ListableBeanFactory to search
081: * @param unitName the name of the persistence unit (never empty)
082: * @return the EntityManagerFactory
083: * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory in the context
084: * @see EntityManagerFactoryInfo#getPersistenceUnitName()
085: */
086: public static EntityManagerFactory findEntityManagerFactory(
087: ListableBeanFactory beanFactory, String unitName)
088: throws NoSuchBeanDefinitionException {
089:
090: Assert.notNull(beanFactory,
091: "ListableBeanFactory must not be null");
092: Assert.hasLength(unitName, "Unit name must not be empty");
093:
094: // See whether we can find an EntityManagerFactory with matching persistence unit name.
095: String[] candidateNames = BeanFactoryUtils
096: .beanNamesForTypeIncludingAncestors(beanFactory,
097: EntityManagerFactory.class);
098: for (String candidateName : candidateNames) {
099: EntityManagerFactory emf = (EntityManagerFactory) beanFactory
100: .getBean(candidateName);
101: if (emf instanceof EntityManagerFactoryInfo) {
102: if (unitName.equals(((EntityManagerFactoryInfo) emf)
103: .getPersistenceUnitName())) {
104: return emf;
105: }
106: }
107: }
108: // No matching persistence unit found - simply take the EntityManagerFactory
109: // with the persistence unit name as bean name (by convention).
110: return (EntityManagerFactory) beanFactory.getBean(unitName,
111: EntityManagerFactory.class);
112: }
113:
114: /**
115: * Obtain a JPA EntityManager from the given factory. Is aware of a
116: * corresponding EntityManager bound to the current thread,
117: * for example when using JpaTransactionManager.
118: * <p>Note: Will return <code>null</code> if no thread-bound EntityManager found!
119: * @param emf EntityManagerFactory to create the EntityManager with
120: * @return the EntityManager, or <code>null</code> if none found
121: * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained
122: * @see JpaTransactionManager
123: */
124: public static EntityManager getTransactionalEntityManager(
125: EntityManagerFactory emf)
126: throws DataAccessResourceFailureException {
127:
128: return getTransactionalEntityManager(emf, null);
129: }
130:
131: /**
132: * Obtain a JPA EntityManager from the given factory. Is aware of a
133: * corresponding EntityManager bound to the current thread,
134: * for example when using JpaTransactionManager.
135: * <p>Note: Will return <code>null</code> if no thread-bound EntityManager found!
136: * @param emf EntityManagerFactory to create the EntityManager with
137: * @param properties the properties to be passed into the <code>createEntityManager</code>
138: * call (may be <code>null</code>)
139: * @return the EntityManager, or <code>null</code> if none found
140: * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained
141: * @see JpaTransactionManager
142: */
143: public static EntityManager getTransactionalEntityManager(
144: EntityManagerFactory emf, Map properties)
145: throws DataAccessResourceFailureException {
146: try {
147: return doGetTransactionalEntityManager(emf, properties);
148: } catch (PersistenceException ex) {
149: throw new DataAccessResourceFailureException(
150: "Could not obtain JPA EntityManager", ex);
151: }
152: }
153:
154: /**
155: * Obtain a JPA EntityManager from the given factory. Is aware of a
156: * corresponding EntityManager bound to the current thread,
157: * for example when using JpaTransactionManager.
158: * <p>Same as <code>getEntityManager</code>, but throwing the original PersistenceException.
159: * @param emf EntityManagerFactory to create the EntityManager with
160: * @param properties the properties to be passed into the <code>createEntityManager</code>
161: * call (may be <code>null</code>)
162: * @return the EntityManager, or <code>null</code> if none found
163: * @throws javax.persistence.PersistenceException if the EntityManager couldn't be created
164: * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory)
165: * @see JpaTransactionManager
166: */
167: public static EntityManager doGetTransactionalEntityManager(
168: EntityManagerFactory emf, Map properties)
169: throws PersistenceException {
170:
171: Assert.notNull(emf, "No EntityManagerFactory specified");
172:
173: EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
174: .getResource(emf);
175: if (emHolder != null) {
176: if (!emHolder.isSynchronizedWithTransaction()
177: && TransactionSynchronizationManager
178: .isSynchronizationActive()) {
179: // Try to explicitly synchronize the EntityManager itself
180: // with an ongoing JTA transaction, if any.
181: try {
182: emHolder.getEntityManager().joinTransaction();
183: } catch (TransactionRequiredException ex) {
184: logger
185: .debug(
186: "Could not join JTA transaction because none was active",
187: ex);
188: }
189: Object transactionData = prepareTransaction(emHolder
190: .getEntityManager(), emf);
191: TransactionSynchronizationManager
192: .registerSynchronization(new EntityManagerSynchronization(
193: emHolder, emf, transactionData, false));
194: emHolder.setSynchronizedWithTransaction(true);
195: }
196: return emHolder.getEntityManager();
197: }
198:
199: if (!TransactionSynchronizationManager
200: .isSynchronizationActive()) {
201: // Indicate that we can't obtain a transactional EntityManager.
202: return null;
203: }
204:
205: // Create a new EntityManager for use within the current transaction.
206: logger.debug("Opening JPA EntityManager");
207: EntityManager em = (!CollectionUtils.isEmpty(properties) ? emf
208: .createEntityManager(properties) : emf
209: .createEntityManager());
210:
211: if (TransactionSynchronizationManager.isSynchronizationActive()) {
212: logger
213: .debug("Registering transaction synchronization for JPA EntityManager");
214: // Use same EntityManager for further JPA actions within the transaction.
215: // Thread object will get removed by synchronization at transaction completion.
216: emHolder = new EntityManagerHolder(em);
217: Object transactionData = prepareTransaction(em, emf);
218: TransactionSynchronizationManager
219: .registerSynchronization(new EntityManagerSynchronization(
220: emHolder, emf, transactionData, true));
221: emHolder.setSynchronizedWithTransaction(true);
222: TransactionSynchronizationManager.bindResource(emf,
223: emHolder);
224: }
225:
226: return em;
227: }
228:
229: /**
230: * Prepare a transaction on the given EntityManager, if possible.
231: * @param em the EntityManager to prepare
232: * @param emf the EntityManagerFactory that the EntityManager has been created with
233: * @return an arbitrary object that holds transaction data, if any
234: * (to be passed into cleanupTransaction)
235: * @see JpaDialect#prepareTransaction
236: */
237: private static Object prepareTransaction(EntityManager em,
238: EntityManagerFactory emf) {
239: if (emf instanceof EntityManagerFactoryInfo) {
240: EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
241: JpaDialect jpaDialect = emfInfo.getJpaDialect();
242: if (jpaDialect != null) {
243: return jpaDialect.prepareTransaction(em,
244: TransactionSynchronizationManager
245: .isCurrentTransactionReadOnly(),
246: TransactionSynchronizationManager
247: .getCurrentTransactionName());
248: }
249: }
250: return null;
251: }
252:
253: /**
254: * Prepare a transaction on the given EntityManager, if possible.
255: * @param transactionData arbitrary object that holds transaction data, if any
256: * (as returned by prepareTransaction)
257: * @param emf the EntityManagerFactory that the EntityManager has been created with
258: * @see JpaDialect#cleanupTransaction
259: */
260: private static void cleanupTransaction(Object transactionData,
261: EntityManagerFactory emf) {
262: if (emf instanceof EntityManagerFactoryInfo) {
263: EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
264: JpaDialect jpaDialect = emfInfo.getJpaDialect();
265: if (jpaDialect != null) {
266: jpaDialect.cleanupTransaction(transactionData);
267: }
268: }
269: }
270:
271: /**
272: * Convert the given runtime exception to an appropriate exception from the
273: * <code>org.springframework.dao</code> hierarchy.
274: * Return null if no translation is appropriate: any other exception may
275: * have resulted from user code, and should not be translated.
276: * <p>The most important cases like object not found or optimistic locking
277: * failure are covered here. For more fine-granular conversion, JpaAccessor and
278: * JpaTransactionManager support sophisticated translation of exceptions via a
279: * JpaDialect.
280: * @param ex runtime exception that occured
281: * @return the corresponding DataAccessException instance,
282: * or <code>null</code> if the exception should not be translated
283: */
284: public static DataAccessException convertJpaAccessExceptionIfPossible(
285: RuntimeException ex) {
286: // Following the JPA specification, a persistence provider can also
287: // throw these two exceptions, besides PersistenceException.
288: if (ex instanceof IllegalStateException) {
289: return new InvalidDataAccessApiUsageException(ex
290: .getMessage(), ex);
291: }
292: if (ex instanceof IllegalArgumentException) {
293: return new InvalidDataAccessApiUsageException(ex
294: .getMessage(), ex);
295: }
296:
297: // Check for well-known PersistenceException subclasses.
298: if (ex instanceof EntityNotFoundException) {
299: return new JpaObjectRetrievalFailureException(
300: (EntityNotFoundException) ex);
301: }
302: if (ex instanceof NoResultException) {
303: return new EmptyResultDataAccessException(ex.getMessage(),
304: 1);
305: }
306: if (ex instanceof NonUniqueResultException) {
307: return new IncorrectResultSizeDataAccessException(ex
308: .getMessage(), 1);
309: }
310: if (ex instanceof OptimisticLockException) {
311: return new JpaOptimisticLockingFailureException(
312: (OptimisticLockException) ex);
313: }
314: if (ex instanceof EntityExistsException) {
315: return new DataIntegrityViolationException(ex.getMessage(),
316: ex);
317: }
318: if (ex instanceof TransactionRequiredException) {
319: return new InvalidDataAccessApiUsageException(ex
320: .getMessage(), ex);
321: }
322:
323: // If we have another kind of PersistenceException, throw it.
324: if (ex instanceof PersistenceException) {
325: return new JpaSystemException((PersistenceException) ex);
326: }
327:
328: // If we get here, we have an exception that resulted from user code,
329: // rather than the persistence provider, so we return null to indicate
330: // that translation should not occur.
331: return null;
332: }
333:
334: /**
335: * Callback for resource cleanup at the end of a non-JPA transaction
336: * (e.g. when participating in a JtaTransactionManager transaction).
337: * @see org.springframework.transaction.jta.JtaTransactionManager
338: */
339: private static class EntityManagerSynchronization extends
340: TransactionSynchronizationAdapter {
341:
342: private final EntityManagerHolder entityManagerHolder;
343:
344: private final EntityManagerFactory entityManagerFactory;
345:
346: private final Object transactionData;
347:
348: private final boolean newEntityManager;
349:
350: private boolean holderActive = true;
351:
352: public EntityManagerSynchronization(
353: EntityManagerHolder emHolder, EntityManagerFactory emf,
354: Object transactionData, boolean newEntityManager) {
355: this .entityManagerHolder = emHolder;
356: this .entityManagerFactory = emf;
357: this .transactionData = transactionData;
358: this .newEntityManager = newEntityManager;
359: }
360:
361: public int getOrder() {
362: return ENTITY_MANAGER_SYNCHRONIZATION_ORDER;
363: }
364:
365: public void suspend() {
366: if (this .holderActive) {
367: TransactionSynchronizationManager
368: .unbindResource(this .entityManagerFactory);
369: }
370: }
371:
372: public void resume() {
373: if (this .holderActive) {
374: TransactionSynchronizationManager.bindResource(
375: this .entityManagerFactory,
376: this .entityManagerHolder);
377: }
378: }
379:
380: public void beforeCompletion() {
381: if (this .newEntityManager) {
382: TransactionSynchronizationManager
383: .unbindResource(this .entityManagerFactory);
384: this .holderActive = false;
385: this .entityManagerHolder.getEntityManager().close();
386: }
387: }
388:
389: public void afterCompletion(int status) {
390: if (!this .newEntityManager) {
391: if (status != STATUS_COMMITTED) {
392: // Clear all pending inserts/updates/deletes in the EntityManager.
393: // Necessary for pre-bound EntityManagers, to avoid inconsistent state.
394: this .entityManagerHolder.getEntityManager().clear();
395: }
396: cleanupTransaction(this .transactionData,
397: this .entityManagerFactory);
398: }
399: this .entityManagerHolder
400: .setSynchronizedWithTransaction(false);
401: }
402: }
403:
404: }
|