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.lang.reflect.InvocationHandler;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23 import java.util.List;
24 import java.util.Map;
25
26 import javax.persistence.EntityManager;
27 import javax.persistence.EntityManagerFactory;
28 import javax.persistence.PersistenceException;
29 import javax.persistence.Query;
30
31 import org.springframework.dao.DataAccessException;
32 import org.springframework.dao.InvalidDataAccessApiUsageException;
33 import org.springframework.util.Assert;
34 import org.springframework.util.ClassUtils;
35
36 /**
37 * Helper class that allows for writing JPA data access code in the same style
38 * as with Spring's well-known JdoTemplate and HibernateTemplate classes.
39 * Automatically converts PersistenceExceptions into Spring DataAccessExceptions,
40 * following the <code>org.springframework.dao</code> exception hierarchy.
41 *
42 * <p>The central method is of this template is "execute", supporting JPA access code
43 * implementing the {@link JpaCallback} interface. It provides JPA EntityManager
44 * handling such that neither the JpaCallback implementation nor the calling code
45 * needs to explicitly care about retrieving/closing EntityManagers, or handling
46 * JPA lifecycle exceptions.
47 *
48 * <p>Can be used within a service implementation via direct instantiation with
49 * a EntityManagerFactory reference, or get prepared in an application context
50 * and given to services as bean reference. Note: The EntityManagerFactory should
51 * always be configured as bean in the application context, in the first case
52 * given to the service directly, in the second case to the prepared template.
53 *
54 * <p><b>NOTE: JpaTemplate mainly exists as a sibling of JdoTemplate and
55 * HibernateTemplate, offering the same style for people used to it. For newly
56 * started projects, consider adopting the standard JPA style of coding data
57 * access objects instead, based on a "shared EntityManager" reference injected
58 * via a Spring bean definition or the JPA PersistenceContext annotation.</b>
59 * (Using Spring's SharedEntityManagerBean / PersistenceAnnotationBeanPostProcessor,
60 * or using a direct JNDI lookup for an EntityManager on a Java EE 5 server.)
61 *
62 * <p>JpaTemplate can be considered as direct alternative to working with the
63 * native JPA EntityManager API (through a shared EntityManager reference,
64 * as outlined above). The major advantage is its automatic conversion to
65 * DataAccessExceptions; the major disadvantage is that it introduces
66 * another thin layer on top of the native JPA API. Note that exception
67 * translation can also be achieved through AOP advice; check out
68 * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}.
69 *
70 * <p>{@link LocalContainerEntityManagerFactoryBean} is the preferred way of
71 * obtaining a reference to an EntityManagerFactory, at least outside of a full
72 * Java EE 5 environment. The Spring application context will manage its lifecycle,
73 * initializing and shutting down the factory as part of the application.
74 * Within a Java EE 5 environment, you will typically work with a server-managed
75 * EntityManagerFactory that is exposed via JNDI, obtained through Spring's
76 * {@link org.springframework.jndi.JndiObjectFactoryBean}.
77 *
78 * @author Juergen Hoeller
79 * @since 2.0
80 * @see #setEntityManagerFactory
81 * @see JpaCallback
82 * @see javax.persistence.EntityManager
83 * @see LocalEntityManagerFactoryBean
84 * @see LocalContainerEntityManagerFactoryBean
85 * @see JpaTransactionManager
86 * @see org.springframework.transaction.jta.JtaTransactionManager
87 * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
88 * @see org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
89 */
90 public class JpaTemplate extends JpaAccessor implements JpaOperations {
91
92 private boolean exposeNativeEntityManager = false;
93
94
95 /**
96 * Create a new JpaTemplate instance.
97 */
98 public JpaTemplate() {
99 }
100
101 /**
102 * Create a new JpaTemplate instance.
103 * @param emf EntityManagerFactory to create EntityManagers
104 */
105 public JpaTemplate(EntityManagerFactory emf) {
106 setEntityManagerFactory(emf);
107 afterPropertiesSet();
108 }
109
110 /**
111 * Create a new JpaTemplate instance.
112 * @param em EntityManager to use
113 */
114 public JpaTemplate(EntityManager em) {
115 setEntityManager(em);
116 afterPropertiesSet();
117 }
118
119
120 /**
121 * Set whether to expose the native JPA EntityManager to JpaCallback
122 * code. Default is "false": a EntityManager proxy will be returned,
123 * suppressing <code>close</code> calls and automatically applying transaction
124 * timeouts (if any).
125 * <p>As there is often a need to cast to a provider-specific EntityManager
126 * class in DAOs that use the JPA 1.0 API, for JPA 2.0 previews and other
127 * provider-specific functionality, the exposed proxy implements all interfaces
128 * implemented by the original EntityManager. If this is not sufficient,
129 * turn this flag to "true".
130 * @see JpaCallback
131 * @see javax.persistence.EntityManager
132 */
133 public void setExposeNativeEntityManager(boolean exposeNativeEntityManager) {
134 this.exposeNativeEntityManager = exposeNativeEntityManager;
135 }
136
137 /**
138 * Return whether to expose the native JPA EntityManager to JpaCallback
139 * code, or rather an EntityManager proxy.
140 */
141 public boolean isExposeNativeEntityManager() {
142 return this.exposeNativeEntityManager;
143 }
144
145
146 public Object execute(JpaCallback action) throws DataAccessException {
147 return execute(action, isExposeNativeEntityManager());
148 }
149
150 public List executeFind(JpaCallback action) throws DataAccessException {
151 Object result = execute(action, isExposeNativeEntityManager());
152 if (!(result instanceof List)) {
153 throw new InvalidDataAccessApiUsageException(
154 "Result object returned from JpaCallback isn't a List: [" + result + "]");
155 }
156 return (List) result;
157 }
158
159 /**
160 * Execute the action specified by the given action object within a
161 * EntityManager.
162 * @param action callback object that specifies the JPA action
163 * @param exposeNativeEntityManager whether to expose the native
164 * JPA entity manager to callback code
165 * @return a result object returned by the action, or <code>null</code>
166 * @throws org.springframework.dao.DataAccessException in case of JPA errors
167 */
168 public Object execute(JpaCallback action, boolean exposeNativeEntityManager) throws DataAccessException {
169 Assert.notNull(action, "Callback object must not be null");
170
171 EntityManager em = getEntityManager();
172 boolean isNewEm = false;
173 if (em == null) {
174 em = getTransactionalEntityManager();
175 if (em == null) {
176 logger.debug("Creating new EntityManager for JpaTemplate execution");
177 em = createEntityManager();
178 isNewEm = true;
179 }
180 }
181
182 try {
183 EntityManager emToExpose = (exposeNativeEntityManager ? em : createEntityManagerProxy(em));
184 Object result = action.doInJpa(emToExpose);
185 flushIfNecessary(em, !isNewEm);
186 return result;
187 }
188 catch (RuntimeException ex) {
189 throw translateIfNecessary(ex);
190 }
191 finally {
192 if (isNewEm) {
193 logger.debug("Closing new EntityManager after JPA template execution");
194 EntityManagerFactoryUtils.closeEntityManager(em);
195 }
196 }
197 }
198
199 /**
200 * Create a close-suppressing proxy for the given JPA EntityManager.
201 * The proxy also prepares returned JPA Query objects.
202 * @param em the JPA EntityManager to create a proxy for
203 * @return the EntityManager proxy, implementing all interfaces
204 * implemented by the passed-in EntityManager object (that is,
205 * also implementing all provider-specific extension interfaces)
206 * @see javax.persistence.EntityManager#close
207 */
208 protected EntityManager createEntityManagerProxy(EntityManager em) {
209 Class[] ifcs = null;
210 EntityManagerFactory emf = getEntityManagerFactory();
211 if (emf instanceof EntityManagerFactoryInfo) {
212 Class entityManagerInterface = ((EntityManagerFactoryInfo) emf).getEntityManagerInterface();
213 if (entityManagerInterface != null) {
214 ifcs = new Class[] {entityManagerInterface};
215 }
216 }
217 if (ifcs == null) {
218 ifcs = ClassUtils.getAllInterfacesForClass(em.getClass());
219 }
220 return (EntityManager) Proxy.newProxyInstance(
221 em.getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(em));
222 }
223
224
225 //-------------------------------------------------------------------------
226 // Convenience methods for load, save, delete
227 //-------------------------------------------------------------------------
228
229 @SuppressWarnings("unchecked")
230 public <T> T find(final Class<T> entityClass, final Object id) throws DataAccessException {
231 return (T) execute(new JpaCallback() {
232 public Object doInJpa(EntityManager em) throws PersistenceException {
233 return em.find(entityClass, id);
234 }
235 }, true);
236 }
237
238 @SuppressWarnings("unchecked")
239 public <T> T getReference(final Class<T> entityClass, final Object id) throws DataAccessException {
240 return (T) execute(new JpaCallback() {
241 public Object doInJpa(EntityManager em) throws PersistenceException {
242 return em.getReference(entityClass, id);
243 }
244 }, true);
245 }
246
247 public boolean contains(final Object entity) throws DataAccessException {
248 Boolean result = (Boolean) execute(new JpaCallback() {
249 public Object doInJpa(EntityManager em) throws PersistenceException {
250 return new Boolean(em.contains(entity));
251 }
252 }, true);
253 return result.booleanValue();
254 }
255
256 public void refresh(final Object entity) throws DataAccessException {
257 execute(new JpaCallback() {
258 public Object doInJpa(EntityManager em) throws PersistenceException {
259 em.refresh(entity);
260 return null;
261 }
262 }, true);
263 }
264
265 public void persist(final Object entity) throws DataAccessException {
266 execute(new JpaCallback() {
267 public Object doInJpa(EntityManager em) throws PersistenceException {
268 em.persist(entity);
269 return null;
270 }
271 }, true);
272 }
273
274 @SuppressWarnings("unchecked")
275 public <T> T merge(final T entity) throws DataAccessException {
276 return (T) execute(new JpaCallback() {
277 public Object doInJpa(EntityManager em) throws PersistenceException {
278 return em.merge(entity);
279 }
280 }, true);
281 }
282
283 public void remove(final Object entity) throws DataAccessException {
284 execute(new JpaCallback() {
285 public Object doInJpa(EntityManager em) throws PersistenceException {
286 em.remove(entity);
287 return null;
288 }
289 }, true);
290 }
291
292 public void flush() throws DataAccessException {
293 execute(new JpaCallback() {
294 public Object doInJpa(EntityManager em) throws PersistenceException {
295 em.flush();
296 return null;
297 }
298 }, true);
299 }
300
301
302 //-------------------------------------------------------------------------
303 // Convenience finder methods
304 //-------------------------------------------------------------------------
305
306 public List find(String queryString) throws DataAccessException {
307 return find(queryString, (Object[]) null);
308 }
309
310 public List find(final String queryString, final Object... values) throws DataAccessException {
311 return executeFind(new JpaCallback() {
312 public Object doInJpa(EntityManager em) throws PersistenceException {
313 Query queryObject = em.createQuery(queryString);
314 if (values != null) {
315 for (int i = 0; i < values.length; i++) {
316 queryObject.setParameter(i + 1, values[i]);
317 }
318 }
319 return queryObject.getResultList();
320 }
321 });
322 }
323
324 public List findByNamedParams(final String queryString, final Map<String, ?> params) throws DataAccessException {
325 return executeFind(new JpaCallback() {
326 public Object doInJpa(EntityManager em) throws PersistenceException {
327 Query queryObject = em.createQuery(queryString);
328 if (params != null) {
329 for (Map.Entry<String, ?> entry : params.entrySet()) {
330 queryObject.setParameter(entry.getKey(), entry.getValue());
331 }
332 }
333 return queryObject.getResultList();
334 }
335 });
336 }
337
338 public List findByNamedQuery(String queryName) throws DataAccessException {
339 return findByNamedQuery(queryName, (Object[]) null);
340 }
341
342 public List findByNamedQuery(final String queryName, final Object... values) throws DataAccessException {
343 return executeFind(new JpaCallback() {
344 public Object doInJpa(EntityManager em) throws PersistenceException {
345 Query queryObject = em.createNamedQuery(queryName);
346 if (values != null) {
347 for (int i = 0; i < values.length; i++) {
348 queryObject.setParameter(i + 1, values[i]);
349 }
350 }
351 return queryObject.getResultList();
352 }
353 });
354 }
355
356 public List findByNamedQueryAndNamedParams(final String queryName, final Map<String, ?> params)
357 throws DataAccessException {
358
359 return executeFind(new JpaCallback() {
360 public Object doInJpa(EntityManager em) throws PersistenceException {
361 Query queryObject = em.createNamedQuery(queryName);
362 if (params != null) {
363 for (Map.Entry<String, ?> entry : params.entrySet()) {
364 queryObject.setParameter(entry.getKey(), entry.getValue());
365 }
366 }
367 return queryObject.getResultList();
368 }
369 });
370 }
371
372
373 /**
374 * Invocation handler that suppresses close calls on JPA EntityManagers.
375 * Also prepares returned Query and Criteria objects.
376 * @see javax.persistence.EntityManager#close
377 */
378 private class CloseSuppressingInvocationHandler implements InvocationHandler {
379
380 private final EntityManager target;
381
382 public CloseSuppressingInvocationHandler(EntityManager target) {
383 this.target = target;
384 }
385
386 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
387 // Invocation on EntityManager interface (or provider-specific extension) coming in...
388
389 if (method.getName().equals("equals")) {
390 // Only consider equal when proxies are identical.
391 return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
392 }
393 else if (method.getName().equals("hashCode")) {
394 // Use hashCode of EntityManager proxy.
395 return new Integer(System.identityHashCode(proxy));
396 }
397 else if (method.getName().equals("close")) {
398 // Handle close method: suppress, not valid.
399 return null;
400 }
401
402 // Invoke method on target EntityManager.
403 try {
404 return method.invoke(this.target, args);
405 }
406 catch (InvocationTargetException ex) {
407 throw ex.getTargetException();
408 }
409 }
410 }
411
412 }