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.lang.reflect.InvocationHandler;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import javax.persistence.EntityManager;
028: import javax.persistence.EntityManagerFactory;
029: import javax.persistence.PersistenceException;
030: import javax.persistence.Query;
031:
032: import org.springframework.dao.DataAccessException;
033: import org.springframework.dao.InvalidDataAccessApiUsageException;
034: import org.springframework.util.Assert;
035: import org.springframework.util.ClassUtils;
036:
037: /**
038: * Helper class that allows for writing JPA data access code in the same style
039: * as with Spring's well-known JdoTemplate and HibernateTemplate classes.
040: * Automatically converts PersistenceExceptions into Spring DataAccessExceptions,
041: * following the <code>org.springframework.dao</code> exception hierarchy.
042: *
043: * <p><b>NOTE: JpaTemplate mainly exists as a sibling of JdoTemplate and
044: * HibernateTemplate, to offer the same style for people used to it. For newly
045: * started projects, consider adopting the standard JPA style of coding data
046: * access objects instead, based on a "shared EntityManager" reference injected
047: * via a Spring bean definition or the JPA PersistenceContext annotation.</b>
048: * (Using Spring's SharedEntityManagerBean / PersistenceAnnotationBeanPostProcessor,
049: * or using a direct JNDI lookup for an EntityManager on a Java EE 5 server.)
050: *
051: * <p>The central method is of this template is "execute", supporting JPA access code
052: * implementing the {@link JpaCallback} interface. It provides JPA EntityManager
053: * handling such that neither the JpaCallback implementation nor the calling code
054: * needs to explicitly care about retrieving/closing EntityManagers, or handling
055: * JPA lifecycle exceptions.
056: *
057: * <p>Can be used within a service implementation via direct instantiation with
058: * a EntityManagerFactory reference, or get prepared in an application context
059: * and given to services as bean reference. Note: The EntityManagerFactory should
060: * always be configured as bean in the application context, in the first case
061: * given to the service directly, in the second case to the prepared template.
062: *
063: * <p>JpaTemplate can be considered as direct alternative to working with the
064: * raw JPA EntityManager API (through a shared EntityManager reference,
065: * as outlined above). The major advantage is its automatic conversion to
066: * DataAccessExceptions, the major disadvantage is that it introduces
067: * another thin layer on top of the target API.
068: *
069: * <p>Note that even if {@link JpaTransactionManager} is used for transaction
070: * demarcation in higher-level services, all those services above the data
071: * access layer don't need to be JPA-aware. Setting such a special
072: * PlatformTransactionManager is a configuration issue: For example,
073: * switching to JTA is just a matter of Spring configuration (use
074: * JtaTransactionManager instead) that does not affect application code.
075: *
076: * <p>{@link LocalContainerEntityManagerFactoryBean} is the preferred way of
077: * obtaining a reference to an EntityManagerFactory, at least outside of a full
078: * Java EE 5 environment. The Spring application context will manage its lifecycle,
079: * initializing and shutting down the factory as part of the application.
080: * Within a Java EE 5 environment, you will typically work with a server-managed
081: * EntityManagerFactory that is exposed via JNDI, obtained through Spring's
082: * {@link org.springframework.jndi.JndiObjectFactoryBean}.
083: *
084: * @author Juergen Hoeller
085: * @since 2.0
086: * @see org.springframework.orm.jdo.JdoTemplate
087: * @see org.springframework.orm.hibernate3.HibernateTemplate
088: * @see org.springframework.orm.jpa.support.SharedEntityManagerBean
089: * @see org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
090: * @see #setEntityManagerFactory
091: * @see #execute(JpaCallback)
092: * @see javax.persistence.EntityManager
093: * @see LocalEntityManagerFactoryBean
094: * @see LocalContainerEntityManagerFactoryBean
095: * @see org.springframework.jndi.JndiObjectFactoryBean
096: */
097: public class JpaTemplate extends JpaAccessor implements JpaOperations {
098:
099: private boolean exposeNativeEntityManager = false;
100:
101: /**
102: * Create a new JpaTemplate instance.
103: */
104: public JpaTemplate() {
105: }
106:
107: /**
108: * Create a new JpaTemplate instance.
109: * @param emf EntityManagerFactory to create EntityManagers
110: */
111: public JpaTemplate(EntityManagerFactory emf) {
112: setEntityManagerFactory(emf);
113: afterPropertiesSet();
114: }
115:
116: /**
117: * Create a new JpaTemplate instance.
118: * @param em EntityManager to use
119: */
120: public JpaTemplate(EntityManager em) {
121: setEntityManager(em);
122: afterPropertiesSet();
123: }
124:
125: /**
126: * Set whether to expose the native JPA EntityManager to JpaCallback
127: * code. Default is "false": a EntityManager proxy will be returned,
128: * suppressing <code>close</code> calls and automatically applying transaction
129: * timeouts (if any).
130: * <p>As there is often a need to cast to a provider-specific EntityManager
131: * class in DAOs that use the JPA 1.0 API, for JPA 2.0 previews and other
132: * provider-specific functionality, the exposed proxy implements all interfaces
133: * implemented by the original EntityManager. If this is not sufficient,
134: * turn this flag to "true".
135: * @see JpaCallback
136: * @see javax.persistence.EntityManager
137: */
138: public void setExposeNativeEntityManager(
139: boolean exposeNativeEntityManager) {
140: this .exposeNativeEntityManager = exposeNativeEntityManager;
141: }
142:
143: /**
144: * Return whether to expose the native JPA EntityManager to JpaCallback
145: * code, or rather an EntityManager proxy.
146: */
147: public boolean isExposeNativeEntityManager() {
148: return this .exposeNativeEntityManager;
149: }
150:
151: public Object execute(JpaCallback action)
152: throws DataAccessException {
153: return execute(action, isExposeNativeEntityManager());
154: }
155:
156: public List executeFind(JpaCallback action)
157: throws DataAccessException {
158: Object result = execute(action, isExposeNativeEntityManager());
159: if (!(result instanceof List)) {
160: throw new InvalidDataAccessApiUsageException(
161: "Result object returned from JpaCallback isn't a List: ["
162: + result + "]");
163: }
164: return (List) result;
165: }
166:
167: /**
168: * Execute the action specified by the given action object within a
169: * EntityManager.
170: * @param action callback object that specifies the JPA action
171: * @param exposeNativeEntityManager whether to expose the native
172: * JPA entity manager to callback code
173: * @return a result object returned by the action, or <code>null</code>
174: * @throws org.springframework.dao.DataAccessException in case of JPA errors
175: */
176: public Object execute(JpaCallback action,
177: boolean exposeNativeEntityManager)
178: throws DataAccessException {
179: Assert.notNull(action, "Callback object must not be null");
180:
181: EntityManager em = getEntityManager();
182: boolean isNewEm = false;
183: if (em == null) {
184: em = getTransactionalEntityManager();
185: if (em == null) {
186: logger
187: .debug("Creating new EntityManager for JpaTemplate execution");
188: em = createEntityManager();
189: isNewEm = true;
190: }
191: }
192:
193: try {
194: EntityManager emToExpose = (exposeNativeEntityManager ? em
195: : createEntityManagerProxy(em));
196: Object result = action.doInJpa(emToExpose);
197: flushIfNecessary(em, !isNewEm);
198: return result;
199: } catch (RuntimeException ex) {
200: throw translateIfNecessary(ex);
201: } finally {
202: if (isNewEm) {
203: logger
204: .debug("Closing new EntityManager after JPA template execution");
205: em.close();
206: }
207: }
208: }
209:
210: /**
211: * Create a close-suppressing proxy for the given JPA EntityManager.
212: * The proxy also prepares returned JPA Query objects.
213: * @param em the JPA EntityManager to create a proxy for
214: * @return the EntityManager proxy, implementing all interfaces
215: * implemented by the passed-in EntityManager object (that is,
216: * also implementing all provider-specific extension interfaces)
217: * @see javax.persistence.EntityManager#close
218: */
219: protected EntityManager createEntityManagerProxy(EntityManager em) {
220: Class[] ifcs = ClassUtils.getAllInterfaces(em);
221: return (EntityManager) Proxy.newProxyInstance(getClass()
222: .getClassLoader(), ifcs,
223: new CloseSuppressingInvocationHandler(em));
224: }
225:
226: //-------------------------------------------------------------------------
227: // Convenience methods for load, save, delete
228: //-------------------------------------------------------------------------
229:
230: public <T> T find(final Class<T> entityClass, final Object id)
231: throws DataAccessException {
232: return (T) execute(new JpaCallback() {
233: public Object doInJpa(EntityManager em)
234: throws PersistenceException {
235: return em.find(entityClass, id);
236: }
237: }, true);
238: }
239:
240: public <T> T getReference(final Class<T> entityClass,
241: final Object id) throws DataAccessException {
242: return (T) execute(new JpaCallback() {
243: public Object doInJpa(EntityManager em)
244: throws PersistenceException {
245: return em.getReference(entityClass, id);
246: }
247: }, true);
248: }
249:
250: public boolean contains(final Object entity)
251: throws DataAccessException {
252: Boolean result = (Boolean) execute(new JpaCallback() {
253: public Object doInJpa(EntityManager em)
254: throws PersistenceException {
255: return new Boolean(em.contains(entity));
256: }
257: }, true);
258: return result.booleanValue();
259: }
260:
261: public void refresh(final Object entity) throws DataAccessException {
262: execute(new JpaCallback() {
263: public Object doInJpa(EntityManager em)
264: throws PersistenceException {
265: em.refresh(entity);
266: return null;
267: }
268: }, true);
269: }
270:
271: public void persist(final Object entity) throws DataAccessException {
272: execute(new JpaCallback() {
273: public Object doInJpa(EntityManager em)
274: throws PersistenceException {
275: em.persist(entity);
276: return null;
277: }
278: }, true);
279: }
280:
281: public <T> T merge(final T entity) throws DataAccessException {
282: return (T) execute(new JpaCallback() {
283: public Object doInJpa(EntityManager em)
284: throws PersistenceException {
285: return em.merge(entity);
286: }
287: }, true);
288: }
289:
290: public void remove(final Object entity) throws DataAccessException {
291: execute(new JpaCallback() {
292: public Object doInJpa(EntityManager em)
293: throws PersistenceException {
294: em.remove(entity);
295: return null;
296: }
297: }, true);
298: }
299:
300: public void flush() throws DataAccessException {
301: execute(new JpaCallback() {
302: public Object doInJpa(EntityManager em)
303: throws PersistenceException {
304: em.flush();
305: return null;
306: }
307: }, true);
308: }
309:
310: //-------------------------------------------------------------------------
311: // Convenience finder methods
312: //-------------------------------------------------------------------------
313:
314: public List find(String queryString) throws DataAccessException {
315: return find(queryString, (Object[]) null);
316: }
317:
318: public List find(final String queryString, final Object... values)
319: throws DataAccessException {
320: return executeFind(new JpaCallback() {
321: public Object doInJpa(EntityManager em)
322: throws PersistenceException {
323: Query queryObject = em.createQuery(queryString);
324: if (values != null) {
325: for (int i = 0; i < values.length; i++) {
326: queryObject.setParameter(i + 1, values[i]);
327: }
328: }
329: return queryObject.getResultList();
330: }
331: });
332: }
333:
334: public List findByNamedParams(final String queryString,
335: final Map<String, ? extends Object> params)
336: throws DataAccessException {
337: return executeFind(new JpaCallback() {
338: public Object doInJpa(EntityManager em)
339: throws PersistenceException {
340: Query queryObject = em.createQuery(queryString);
341: if (params != null) {
342: for (Iterator it = params.entrySet().iterator(); it
343: .hasNext();) {
344: Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it
345: .next();
346: queryObject.setParameter(entry.getKey(), entry
347: .getValue());
348: }
349: }
350: return queryObject.getResultList();
351: }
352: });
353: }
354:
355: public List findByNamedQuery(String queryName)
356: throws DataAccessException {
357: return findByNamedQuery(queryName, (Object[]) null);
358: }
359:
360: public List findByNamedQuery(final String queryName,
361: final Object... values) throws DataAccessException {
362: return executeFind(new JpaCallback() {
363: public Object doInJpa(EntityManager em)
364: throws PersistenceException {
365: Query queryObject = em.createNamedQuery(queryName);
366: if (values != null) {
367: for (int i = 0; i < values.length; i++) {
368: queryObject.setParameter(i + 1, values[i]);
369: }
370: }
371: return queryObject.getResultList();
372: }
373: });
374: }
375:
376: public List findByNamedQueryAndNamedParams(final String queryName,
377: final Map<String, ? extends Object> params)
378: throws DataAccessException {
379:
380: return executeFind(new JpaCallback() {
381: public Object doInJpa(EntityManager em)
382: throws PersistenceException {
383: Query queryObject = em.createNamedQuery(queryName);
384: if (params != null) {
385: for (Iterator it = params.entrySet().iterator(); it
386: .hasNext();) {
387: Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it
388: .next();
389: queryObject.setParameter(entry.getKey(), entry
390: .getValue());
391: }
392: }
393: return queryObject.getResultList();
394: }
395: });
396: }
397:
398: /**
399: * Invocation handler that suppresses close calls on JPA EntityManagers.
400: * Also prepares returned Query and Criteria objects.
401: * @see javax.persistence.EntityManager#close
402: */
403: private class CloseSuppressingInvocationHandler implements
404: InvocationHandler {
405:
406: private final EntityManager target;
407:
408: public CloseSuppressingInvocationHandler(EntityManager target) {
409: this .target = target;
410: }
411:
412: public Object invoke(Object proxy, Method method, Object[] args)
413: throws Throwable {
414: // Invocation on EntityManager interface (or provider-specific extension) coming in...
415:
416: if (method.getName().equals("equals")) {
417: // Only consider equal when proxies are identical.
418: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
419: } else if (method.getName().equals("hashCode")) {
420: // Use hashCode of EntityManager proxy.
421: return new Integer(hashCode());
422: } else if (method.getName().equals("close")) {
423: // Handle close method: suppress, not valid.
424: return null;
425: }
426:
427: // Invoke method on target EntityManager.
428: try {
429: return method.invoke(this .target, args);
430: } catch (InvocationTargetException ex) {
431: throw ex.getTargetException();
432: }
433: }
434: }
435:
436: }
|