1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.orm.jpa;
18
19 import java.util.Map;
20
21 import javax.persistence.EntityExistsException;
22 import javax.persistence.EntityManager;
23 import javax.persistence.EntityManagerFactory;
24 import javax.persistence.EntityNotFoundException;
25 import javax.persistence.NoResultException;
26 import javax.persistence.NonUniqueResultException;
27 import javax.persistence.OptimisticLockException;
28 import javax.persistence.PersistenceException;
29 import javax.persistence.TransactionRequiredException;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33
34 import org.springframework.beans.factory.BeanFactoryUtils;
35 import org.springframework.beans.factory.ListableBeanFactory;
36 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
37 import org.springframework.core.Ordered;
38 import org.springframework.dao.DataAccessException;
39 import org.springframework.dao.DataAccessResourceFailureException;
40 import org.springframework.dao.DataIntegrityViolationException;
41 import org.springframework.dao.EmptyResultDataAccessException;
42 import org.springframework.dao.IncorrectResultSizeDataAccessException;
43 import org.springframework.dao.InvalidDataAccessApiUsageException;
44 import org.springframework.jdbc.datasource.DataSourceUtils;
45 import org.springframework.transaction.support.ResourceHolder;
46 import org.springframework.transaction.support.ResourceHolderSynchronization;
47 import org.springframework.transaction.support.TransactionSynchronizationManager;
48 import org.springframework.util.Assert;
49 import org.springframework.util.CollectionUtils;
50
51 /**
52 * Helper class featuring methods for JPA EntityManager handling,
53 * allowing for reuse of EntityManager instances within transactions.
54 * Also provides support for exception translation.
55 *
56 * <p>Mainly intended for internal use within the framework.
57 *
58 * @author Juergen Hoeller
59 * @since 2.0
60 */
61 public abstract class EntityManagerFactoryUtils {
62
63 /**
64 * Order value for TransactionSynchronization objects that clean up JPA
65 * EntityManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
66 * to execute EntityManager cleanup before JDBC Connection cleanup, if any.
67 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
68 */
69 public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER =
70 DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
71
72 private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class);
73
74
75 /**
76 * Find an EntityManagerFactory with the given name in the given
77 * Spring application context (represented as ListableBeanFactory).
78 * <p>The specified unit name will be matched against the configured
79 * peristence unit, provided that a discovered EntityManagerFactory
80 * implements the {@link EntityManagerFactoryInfo} interface. If not,
81 * the persistence unit name will be matched against the Spring bean name,
82 * assuming that the EntityManagerFactory bean names follow that convention.
83 * @param beanFactory the ListableBeanFactory to search
84 * @param unitName the name of the persistence unit (never empty)
85 * @return the EntityManagerFactory
86 * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory in the context
87 * @see EntityManagerFactoryInfo#getPersistenceUnitName()
88 */
89 public static EntityManagerFactory findEntityManagerFactory(
90 ListableBeanFactory beanFactory, String unitName) throws NoSuchBeanDefinitionException {
91
92 Assert.notNull(beanFactory, "ListableBeanFactory must not be null");
93 Assert.hasLength(unitName, "Unit name must not be empty");
94
95 // See whether we can find an EntityManagerFactory with matching persistence unit name.
96 String[] candidateNames =
97 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, EntityManagerFactory.class);
98 for (String candidateName : candidateNames) {
99 EntityManagerFactory emf = (EntityManagerFactory) beanFactory.getBean(candidateName);
100 if (emf instanceof EntityManagerFactoryInfo) {
101 if (unitName.equals(((EntityManagerFactoryInfo) emf).getPersistenceUnitName())) {
102 return emf;
103 }
104 }
105 }
106 // No matching persistence unit found - simply take the EntityManagerFactory
107 // with the persistence unit name as bean name (by convention).
108 return (EntityManagerFactory) beanFactory.getBean(unitName, EntityManagerFactory.class);
109 }
110
111 /**
112 * Obtain a JPA EntityManager from the given factory. Is aware of a
113 * corresponding EntityManager bound to the current thread,
114 * for example when using JpaTransactionManager.
115 * <p>Note: Will return <code>null</code> if no thread-bound EntityManager found!
116 * @param emf EntityManagerFactory to create the EntityManager with
117 * @return the EntityManager, or <code>null</code> if none found
118 * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained
119 * @see JpaTransactionManager
120 */
121 public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf)
122 throws DataAccessResourceFailureException {
123
124 return getTransactionalEntityManager(emf, null);
125 }
126
127 /**
128 * Obtain a JPA EntityManager from the given factory. Is aware of a
129 * corresponding EntityManager bound to the current thread,
130 * for example when using JpaTransactionManager.
131 * <p>Note: Will return <code>null</code> if no thread-bound EntityManager found!
132 * @param emf EntityManagerFactory to create the EntityManager with
133 * @param properties the properties to be passed into the <code>createEntityManager</code>
134 * call (may be <code>null</code>)
135 * @return the EntityManager, or <code>null</code> if none found
136 * @throws DataAccessResourceFailureException if the EntityManager couldn't be obtained
137 * @see JpaTransactionManager
138 */
139 public static EntityManager getTransactionalEntityManager(EntityManagerFactory emf, Map properties)
140 throws DataAccessResourceFailureException {
141 try {
142 return doGetTransactionalEntityManager(emf, properties);
143 }
144 catch (PersistenceException ex) {
145 throw new DataAccessResourceFailureException("Could not obtain JPA EntityManager", ex);
146 }
147 }
148
149 /**
150 * Obtain a JPA EntityManager from the given factory. Is aware of a
151 * corresponding EntityManager bound to the current thread,
152 * for example when using JpaTransactionManager.
153 * <p>Same as <code>getEntityManager</code>, but throwing the original PersistenceException.
154 * @param emf EntityManagerFactory to create the EntityManager with
155 * @param properties the properties to be passed into the <code>createEntityManager</code>
156 * call (may be <code>null</code>)
157 * @return the EntityManager, or <code>null</code> if none found
158 * @throws javax.persistence.PersistenceException if the EntityManager couldn't be created
159 * @see #getTransactionalEntityManager(javax.persistence.EntityManagerFactory)
160 * @see JpaTransactionManager
161 */
162 public static EntityManager doGetTransactionalEntityManager(
163 EntityManagerFactory emf, Map properties) throws PersistenceException {
164
165 Assert.notNull(emf, "No EntityManagerFactory specified");
166
167 EntityManagerHolder emHolder =
168 (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
169 if (emHolder != null) {
170 if (!emHolder.isSynchronizedWithTransaction() &&
171 TransactionSynchronizationManager.isSynchronizationActive()) {
172 // Try to explicitly synchronize the EntityManager itself
173 // with an ongoing JTA transaction, if any.
174 try {
175 emHolder.getEntityManager().joinTransaction();
176 }
177 catch (TransactionRequiredException ex) {
178 logger.debug("Could not join JTA transaction because none was active", ex);
179 }
180 Object transactionData = prepareTransaction(emHolder.getEntityManager(), emf);
181 TransactionSynchronizationManager.registerSynchronization(
182 new EntityManagerSynchronization(emHolder, emf, transactionData, false));
183 emHolder.setSynchronizedWithTransaction(true);
184 }
185 return emHolder.getEntityManager();
186 }
187
188 if (!TransactionSynchronizationManager.isSynchronizationActive()) {
189 // Indicate that we can't obtain a transactional EntityManager.
190 return null;
191 }
192
193 // Create a new EntityManager for use within the current transaction.
194 logger.debug("Opening JPA EntityManager");
195 EntityManager em =
196 (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
197
198 if (TransactionSynchronizationManager.isSynchronizationActive()) {
199 logger.debug("Registering transaction synchronization for JPA EntityManager");
200 // Use same EntityManager for further JPA actions within the transaction.
201 // Thread object will get removed by synchronization at transaction completion.
202 emHolder = new EntityManagerHolder(em);
203 Object transactionData = prepareTransaction(em, emf);
204 TransactionSynchronizationManager.registerSynchronization(
205 new EntityManagerSynchronization(emHolder, emf, transactionData, true));
206 emHolder.setSynchronizedWithTransaction(true);
207 TransactionSynchronizationManager.bindResource(emf, emHolder);
208 }
209
210 return em;
211 }
212
213 /**
214 * Prepare a transaction on the given EntityManager, if possible.
215 * @param em the EntityManager to prepare
216 * @param emf the EntityManagerFactory that the EntityManager has been created with
217 * @return an arbitrary object that holds transaction data, if any
218 * (to be passed into cleanupTransaction)
219 * @see JpaDialect#prepareTransaction
220 */
221 private static Object prepareTransaction(EntityManager em, EntityManagerFactory emf) {
222 if (emf instanceof EntityManagerFactoryInfo) {
223 EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
224 JpaDialect jpaDialect = emfInfo.getJpaDialect();
225 if (jpaDialect != null) {
226 return jpaDialect.prepareTransaction(em,
227 TransactionSynchronizationManager.isCurrentTransactionReadOnly(),
228 TransactionSynchronizationManager.getCurrentTransactionName());
229 }
230 }
231 return null;
232 }
233
234 /**
235 * Prepare a transaction on the given EntityManager, if possible.
236 * @param transactionData arbitrary object that holds transaction data, if any
237 * (as returned by prepareTransaction)
238 * @param emf the EntityManagerFactory that the EntityManager has been created with
239 * @see JpaDialect#cleanupTransaction
240 */
241 private static void cleanupTransaction(Object transactionData, EntityManagerFactory emf) {
242 if (emf instanceof EntityManagerFactoryInfo) {
243 EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf;
244 JpaDialect jpaDialect = emfInfo.getJpaDialect();
245 if (jpaDialect != null) {
246 jpaDialect.cleanupTransaction(transactionData);
247 }
248 }
249 }
250
251 /**
252 * Convert the given runtime exception to an appropriate exception from the
253 * <code>org.springframework.dao</code> hierarchy.
254 * Return null if no translation is appropriate: any other exception may
255 * have resulted from user code, and should not be translated.
256 * <p>The most important cases like object not found or optimistic locking
257 * failure are covered here. For more fine-granular conversion, JpaAccessor and
258 * JpaTransactionManager support sophisticated translation of exceptions via a
259 * JpaDialect.
260 * @param ex runtime exception that occured
261 * @return the corresponding DataAccessException instance,
262 * or <code>null</code> if the exception should not be translated
263 */
264 public static DataAccessException convertJpaAccessExceptionIfPossible(RuntimeException ex) {
265 // Following the JPA specification, a persistence provider can also
266 // throw these two exceptions, besides PersistenceException.
267 if (ex instanceof IllegalStateException) {
268 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
269 }
270 if (ex instanceof IllegalArgumentException) {
271 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
272 }
273
274 // Check for well-known PersistenceException subclasses.
275 if (ex instanceof EntityNotFoundException) {
276 return new JpaObjectRetrievalFailureException((EntityNotFoundException) ex);
277 }
278 if (ex instanceof NoResultException) {
279 return new EmptyResultDataAccessException(ex.getMessage(), 1);
280 }
281 if (ex instanceof NonUniqueResultException) {
282 return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1);
283 }
284 if (ex instanceof OptimisticLockException) {
285 return new JpaOptimisticLockingFailureException((OptimisticLockException) ex);
286 }
287 if (ex instanceof EntityExistsException) {
288 return new DataIntegrityViolationException(ex.getMessage(), ex);
289 }
290 if (ex instanceof TransactionRequiredException) {
291 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
292 }
293
294 // If we have another kind of PersistenceException, throw it.
295 if (ex instanceof PersistenceException) {
296 return new JpaSystemException((PersistenceException) ex);
297 }
298
299 // If we get here, we have an exception that resulted from user code,
300 // rather than the persistence provider, so we return null to indicate
301 // that translation should not occur.
302 return null;
303 }
304
305 /**
306 * Close the given JPA EntityManager,
307 * catching and logging any cleanup exceptions thrown.
308 * @param em the JPA EntityManager to close (may be <code>null</code>)
309 * @see javax.persistence.EntityManager#close()
310 */
311 public static void closeEntityManager(EntityManager em) {
312 if (em != null) {
313 logger.debug("Closing JPA EntityManager");
314 try {
315 em.close();
316 }
317 catch (PersistenceException ex) {
318 logger.debug("Could not close JPA EntityManager", ex);
319 }
320 catch (Throwable ex) {
321 logger.debug("Unexpected exception on closing JPA EntityManager", ex);
322 }
323 }
324 }
325
326
327 /**
328 * Callback for resource cleanup at the end of a non-JPA transaction
329 * (e.g. when participating in a JtaTransactionManager transaction).
330 * @see org.springframework.transaction.jta.JtaTransactionManager
331 */
332 private static class EntityManagerSynchronization extends ResourceHolderSynchronization implements Ordered {
333
334 private final Object transactionData;
335
336 private final boolean newEntityManager;
337
338 public EntityManagerSynchronization(
339 EntityManagerHolder emHolder, EntityManagerFactory emf, Object transactionData, boolean newEntityManager) {
340 super(emHolder, emf);
341 this.transactionData = transactionData;
342 this.newEntityManager = newEntityManager;
343 }
344
345 public int getOrder() {
346 return ENTITY_MANAGER_SYNCHRONIZATION_ORDER;
347 }
348
349 protected boolean shouldUnbindAtCompletion() {
350 return this.newEntityManager;
351 }
352
353 protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
354 closeEntityManager(((EntityManagerHolder) resourceHolder).getEntityManager());
355 }
356
357 protected void cleanupResource(ResourceHolder resourceHolder, Object resourceKey, boolean committed) {
358 if (!committed) {
359 // Clear all pending inserts/updates/deletes in the EntityManager.
360 // Necessary for pre-bound EntityManagers, to avoid inconsistent state.
361 ((EntityManagerHolder) resourceHolder).getEntityManager().clear();
362 }
363 cleanupTransaction(this.transactionData, (EntityManagerFactory) resourceKey);
364 }
365 }
366
367 }