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