KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > orm > jpa > JpaTemplate


1 /*
2  * Copyright 2002-2007 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 JavaDoc;
20 import java.lang.reflect.InvocationTargetException JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.lang.reflect.Proxy JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26
27 import javax.persistence.EntityManager;
28 import javax.persistence.EntityManagerFactory;
29 import javax.persistence.PersistenceException;
30 import javax.persistence.Query;
31
32 import org.springframework.dao.DataAccessException;
33 import org.springframework.dao.InvalidDataAccessApiUsageException;
34 import org.springframework.util.Assert;
35 import org.springframework.util.ClassUtils;
36
37 /**
38  * Helper class that allows for writing JPA data access code in the same style
39  * as with Spring's well-known JdoTemplate and HibernateTemplate classes.
40  * Automatically converts PersistenceExceptions into Spring DataAccessExceptions,
41  * following the <code>org.springframework.dao</code> exception hierarchy.
42  *
43  * <p><b>NOTE: JpaTemplate mainly exists as a sibling of JdoTemplate and
44  * HibernateTemplate, to offer the same style for people used to it. For newly
45  * started projects, consider adopting the standard JPA style of coding data
46  * access objects instead, based on a "shared EntityManager" reference injected
47  * via a Spring bean definition or the JPA PersistenceContext annotation.</b>
48  * (Using Spring's SharedEntityManagerBean / PersistenceAnnotationBeanPostProcessor,
49  * or using a direct JNDI lookup for an EntityManager on a Java EE 5 server.)
50  *
51  * <p>The central method is of this template is "execute", supporting JPA access code
52  * implementing the {@link JpaCallback} interface. It provides JPA EntityManager
53  * handling such that neither the JpaCallback implementation nor the calling code
54  * needs to explicitly care about retrieving/closing EntityManagers, or handling
55  * JPA lifecycle exceptions.
56  *
57  * <p>Can be used within a service implementation via direct instantiation with
58  * a EntityManagerFactory reference, or get prepared in an application context
59  * and given to services as bean reference. Note: The EntityManagerFactory should
60  * always be configured as bean in the application context, in the first case
61  * given to the service directly, in the second case to the prepared template.
62  *
63  * <p>JpaTemplate can be considered as direct alternative to working with the
64  * raw JPA EntityManager API (through a shared EntityManager reference,
65  * as outlined above). The major advantage is its automatic conversion to
66  * DataAccessExceptions, the major disadvantage is that it introduces
67  * another thin layer on top of the target API.
68  *
69  * <p>Note that even if {@link JpaTransactionManager} is used for transaction
70  * demarcation in higher-level services, all those services above the data
71  * access layer don't need to be JPA-aware. Setting such a special
72  * PlatformTransactionManager is a configuration issue: For example,
73  * switching to JTA is just a matter of Spring configuration (use
74  * JtaTransactionManager instead) that does not affect application code.
75  *
76  * <p>{@link LocalContainerEntityManagerFactoryBean} is the preferred way of
77  * obtaining a reference to an EntityManagerFactory, at least outside of a full
78  * Java EE 5 environment. The Spring application context will manage its lifecycle,
79  * initializing and shutting down the factory as part of the application.
80  * Within a Java EE 5 environment, you will typically work with a server-managed
81  * EntityManagerFactory that is exposed via JNDI, obtained through Spring's
82  * {@link org.springframework.jndi.JndiObjectFactoryBean}.
83  *
84  * @author Juergen Hoeller
85  * @since 2.0
86  * @see org.springframework.orm.jdo.JdoTemplate
87  * @see org.springframework.orm.hibernate3.HibernateTemplate
88  * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
89  * @see org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
90  * @see #setEntityManagerFactory
91  * @see #execute(JpaCallback)
92  * @see javax.persistence.EntityManager
93  * @see LocalEntityManagerFactoryBean
94  * @see LocalContainerEntityManagerFactoryBean
95  * @see org.springframework.jndi.JndiObjectFactoryBean
96  */

97 public class JpaTemplate extends JpaAccessor implements JpaOperations {
98
99     private boolean exposeNativeEntityManager = false;
100
101
102     /**
103      * Create a new JpaTemplate instance.
104      */

105     public JpaTemplate() {
106     }
107
108     /**
109      * Create a new JpaTemplate instance.
110      * @param emf EntityManagerFactory to create EntityManagers
111      */

112     public JpaTemplate(EntityManagerFactory emf) {
113         setEntityManagerFactory(emf);
114         afterPropertiesSet();
115     }
116
117     /**
118      * Create a new JpaTemplate instance.
119      * @param em EntityManager to use
120      */

121     public JpaTemplate(EntityManager em) {
122         setEntityManager(em);
123         afterPropertiesSet();
124     }
125
126
127     /**
128      * Set whether to expose the native JPA EntityManager to JpaCallback
129      * code. Default is "false": a EntityManager proxy will be returned,
130      * suppressing <code>close</code> calls and automatically applying transaction
131      * timeouts (if any).
132      * <p>As there is often a need to cast to a provider-specific EntityManager
133      * class in DAOs that use the JPA 1.0 API, for JPA 2.0 previews and other
134      * provider-specific functionality, the exposed proxy implements all interfaces
135      * implemented by the original EntityManager. If this is not sufficient,
136      * turn this flag to "true".
137      * @see JpaCallback
138      * @see javax.persistence.EntityManager
139      */

140     public void setExposeNativeEntityManager(boolean exposeNativeEntityManager) {
141         this.exposeNativeEntityManager = exposeNativeEntityManager;
142     }
143
144     /**
145      * Return whether to expose the native JPA EntityManager to JpaCallback
146      * code, or rather an EntityManager proxy.
147      */

148     public boolean isExposeNativeEntityManager() {
149         return this.exposeNativeEntityManager;
150     }
151
152
153     public Object JavaDoc execute(JpaCallback action) throws DataAccessException {
154         return execute(action, isExposeNativeEntityManager());
155     }
156
157     public List JavaDoc executeFind(JpaCallback action) throws DataAccessException {
158         Object JavaDoc result = execute(action, isExposeNativeEntityManager());
159         if (!(result instanceof List JavaDoc)) {
160             throw new InvalidDataAccessApiUsageException(
161                     "Result object returned from JpaCallback isn't a List: [" + result + "]");
162         }
163         return (List JavaDoc) result;
164     }
165
166     /**
167      * Execute the action specified by the given action object within a
168      * EntityManager.
169      * @param action callback object that specifies the JPA action
170      * @param exposeNativeEntityManager whether to expose the native
171      * JPA entity manager to callback code
172      * @return a result object returned by the action, or <code>null</code>
173      * @throws org.springframework.dao.DataAccessException in case of JPA errors
174      */

175     public Object JavaDoc execute(JpaCallback action, boolean exposeNativeEntityManager) throws DataAccessException {
176         Assert.notNull(action, "Callback object must not be null");
177
178         EntityManager em = getEntityManager();
179         boolean isNewEm = false;
180         if (em == null) {
181             em = getTransactionalEntityManager();
182             if (em == null) {
183                 logger.debug("Creating new EntityManager for JpaTemplate execution");
184                 em = createEntityManager();
185                 isNewEm = true;
186             }
187         }
188
189         try {
190             EntityManager emToExpose = (exposeNativeEntityManager ? em : createEntityManagerProxy(em));
191             Object JavaDoc result = action.doInJpa(emToExpose);
192             flushIfNecessary(em, !isNewEm);
193             return result;
194         }
195         catch (RuntimeException JavaDoc ex) {
196             throw translateIfNecessary(ex);
197         }
198         finally {
199             if (isNewEm) {
200                 logger.debug("Closing new EntityManager after JPA template execution");
201                 em.close();
202             }
203         }
204     }
205
206     /**
207      * Create a close-suppressing proxy for the given JPA EntityManager.
208      * The proxy also prepares returned JPA Query objects.
209      * @param em the JPA EntityManager to create a proxy for
210      * @return the EntityManager proxy, implementing all interfaces
211      * implemented by the passed-in EntityManager object (that is,
212      * also implementing all provider-specific extension interfaces)
213      * @see javax.persistence.EntityManager#close
214      */

215     protected EntityManager createEntityManagerProxy(EntityManager em) {
216         Class JavaDoc[] ifcs = ClassUtils.getAllInterfaces(em);
217         return (EntityManager) Proxy.newProxyInstance(
218                 getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(em));
219     }
220
221
222     //-------------------------------------------------------------------------
223
// Convenience methods for load, save, delete
224
//-------------------------------------------------------------------------
225

226     public <T> T find(final Class JavaDoc<T> entityClass, final Object JavaDoc id) throws DataAccessException {
227         return (T) execute(new JpaCallback() {
228             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
229                 return em.find(entityClass, id);
230             }
231         }, true);
232     }
233
234     public <T> T getReference(final Class JavaDoc<T> entityClass, final Object JavaDoc id) throws DataAccessException {
235         return (T) execute(new JpaCallback() {
236             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
237                 return em.getReference(entityClass, id);
238             }
239         }, true);
240     }
241
242     public boolean contains(final Object JavaDoc entity) throws DataAccessException {
243         Boolean JavaDoc result = (Boolean JavaDoc) execute(new JpaCallback() {
244             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
245                 return new Boolean JavaDoc(em.contains(entity));
246             }
247         }, true);
248         return result.booleanValue();
249     }
250
251     public void refresh(final Object JavaDoc entity) throws DataAccessException {
252         execute(new JpaCallback() {
253             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
254                 em.refresh(entity);
255                 return null;
256             }
257         }, true);
258     }
259
260     public void persist(final Object JavaDoc entity) throws DataAccessException {
261         execute(new JpaCallback() {
262             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
263                 em.persist(entity);
264                 return null;
265             }
266         }, true);
267     }
268
269     public <T> T merge(final T entity) throws DataAccessException {
270         return (T) execute(new JpaCallback() {
271             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
272                 return em.merge(entity);
273             }
274         }, true);
275     }
276
277     public void remove(final Object JavaDoc entity) throws DataAccessException {
278         execute(new JpaCallback() {
279             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
280                 em.remove(entity);
281                 return null;
282             }
283         }, true);
284     }
285
286     public void flush() throws DataAccessException {
287         execute(new JpaCallback() {
288             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
289                 em.flush();
290                 return null;
291             }
292         }, true);
293     }
294
295
296     //-------------------------------------------------------------------------
297
// Convenience finder methods
298
//-------------------------------------------------------------------------
299

300     public List JavaDoc find(String JavaDoc queryString) throws DataAccessException {
301         return find(queryString, (Object JavaDoc[]) null);
302     }
303
304     public List JavaDoc find(final String JavaDoc queryString, final Object JavaDoc... values) throws DataAccessException {
305         return executeFind(new JpaCallback() {
306             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
307                 Query queryObject = em.createQuery(queryString);
308                 if (values != null) {
309                     for (int i = 0; i < values.length; i++) {
310                         queryObject.setParameter(i + 1, values[i]);
311                     }
312                 }
313                 return queryObject.getResultList();
314             }
315         });
316     }
317
318     public List JavaDoc findByNamedParams(final String JavaDoc queryString, final Map JavaDoc<String JavaDoc,? extends Object JavaDoc> params) throws DataAccessException {
319         return executeFind(new JpaCallback() {
320             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
321                 Query queryObject = em.createQuery(queryString);
322                 if (params != null) {
323                     for (Iterator JavaDoc it = params.entrySet().iterator(); it.hasNext();) {
324                         Map.Entry JavaDoc<String JavaDoc, Object JavaDoc> entry = (Map.Entry JavaDoc<String JavaDoc, Object JavaDoc>) it.next();
325                         queryObject.setParameter(entry.getKey(), entry.getValue());
326                     }
327                 }
328                 return queryObject.getResultList();
329             }
330         });
331     }
332
333     public List JavaDoc findByNamedQuery(String JavaDoc queryName) throws DataAccessException {
334         return findByNamedQuery(queryName, (Object JavaDoc[]) null);
335     }
336
337     public List JavaDoc findByNamedQuery(final String JavaDoc queryName, final Object JavaDoc... values) throws DataAccessException {
338         return executeFind(new JpaCallback() {
339             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
340                 Query queryObject = em.createNamedQuery(queryName);
341                 if (values != null) {
342                     for (int i = 0; i < values.length; i++) {
343                         queryObject.setParameter(i + 1, values[i]);
344                     }
345                 }
346                 return queryObject.getResultList();
347             }
348         });
349     }
350
351     public List JavaDoc findByNamedQueryAndNamedParams(final String JavaDoc queryName, final Map JavaDoc<String JavaDoc, ? extends Object JavaDoc> params)
352             throws DataAccessException {
353
354         return executeFind(new JpaCallback() {
355             public Object JavaDoc doInJpa(EntityManager em) throws PersistenceException {
356                 Query queryObject = em.createNamedQuery(queryName);
357                 if (params != null) {
358                     for (Iterator JavaDoc it = params.entrySet().iterator(); it.hasNext();) {
359                         Map.Entry JavaDoc<String JavaDoc, Object JavaDoc> entry = (Map.Entry JavaDoc<String JavaDoc, Object JavaDoc>) it.next();
360                         queryObject.setParameter(entry.getKey(), entry.getValue());
361                     }
362                 }
363                 return queryObject.getResultList();
364             }
365         });
366     }
367
368
369     /**
370      * Invocation handler that suppresses close calls on JPA EntityManagers.
371      * Also prepares returned Query and Criteria objects.
372      * @see javax.persistence.EntityManager#close
373      */

374     private class CloseSuppressingInvocationHandler implements InvocationHandler JavaDoc {
375
376         private final EntityManager target;
377
378         public CloseSuppressingInvocationHandler(EntityManager target) {
379             this.target = target;
380         }
381
382         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
383             // Invocation on EntityManager interface (or provider-specific extension) coming in...
384

385             if (method.getName().equals("equals")) {
386                 // Only consider equal when proxies are identical.
387
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
388             }
389             else if (method.getName().equals("hashCode")) {
390                 // Use hashCode of EntityManager proxy.
391
return new Integer JavaDoc(hashCode());
392             }
393             else if (method.getName().equals("close")) {
394                 // Handle close method: suppress, not valid.
395
return null;
396             }
397
398             // Invoke method on target EntityManager.
399
try {
400                 return method.invoke(this.target, args);
401             }
402             catch (InvocationTargetException JavaDoc ex) {
403                 throw ex.getTargetException();
404             }
405         }
406     }
407
408 }
409
Popular Tags