Open Source Repository

Home /spring/spring-orm-3.0.5 | Repository Home



org/springframework/orm/jpa/JpaTemplate.java
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.orm.jpa;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Helper class that allows for writing JPA data access code in the same style
 * as with Spring's well-known JdoTemplate and HibernateTemplate classes.
 * Automatically converts PersistenceExceptions into Spring DataAccessExceptions,
 * following the <code>org.springframework.dao</code> exception hierarchy.
 *
 <p>The central method is of this template is "execute", supporting JPA access code
 * implementing the {@link JpaCallback} interface. It provides JPA EntityManager
 * handling such that neither the JpaCallback implementation nor the calling code
 * needs to explicitly care about retrieving/closing EntityManagers, or handling
 * JPA lifecycle exceptions.
 *
 <p>Can be used within a service implementation via direct instantiation with
 * a EntityManagerFactory reference, or get prepared in an application context
 * and given to services as bean reference. Note: The EntityManagerFactory should
 * always be configured as bean in the application context, in the first case
 * given to the service directly, in the second case to the prepared template.
 *
 <p><b>NOTE: JpaTemplate mainly exists as a sibling of JdoTemplate and
 * HibernateTemplate, offering the same style for people used to it. For newly
 * started projects, consider adopting the standard JPA style of coding data
 * access objects instead, based on a "shared EntityManager" reference injected
 * via a Spring bean definition or the JPA PersistenceContext annotation.</b>
 * (Using Spring's SharedEntityManagerBean / PersistenceAnnotationBeanPostProcessor,
 * or using a direct JNDI lookup for an EntityManager on a Java EE 5 server.)
 *
 <p>JpaTemplate can be considered as direct alternative to working with the
 * native JPA EntityManager API (through a shared EntityManager reference,
 * as outlined above). The major advantage is its automatic conversion to
 * DataAccessExceptions; the major disadvantage is that it introduces
 * another thin layer on top of the native JPA API. Note that exception
 * translation can also be achieved through AOP advice; check out
 {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}.
 *
 <p>{@link LocalContainerEntityManagerFactoryBean} is the preferred way of
 * obtaining a reference to an EntityManagerFactory, at least outside of a full
 * Java EE 5 environment. The Spring application context will manage its lifecycle,
 * initializing and shutting down the factory as part of the application.
 * Within a Java EE 5 environment, you will typically work with a server-managed
 * EntityManagerFactory that is exposed via JNDI, obtained through Spring's
 {@link org.springframework.jndi.JndiObjectFactoryBean}.
 *
 @author Juergen Hoeller
 @since 2.0
 @see #setEntityManagerFactory
 @see JpaCallback
 @see javax.persistence.EntityManager
 @see LocalEntityManagerFactoryBean
 @see LocalContainerEntityManagerFactoryBean
 @see JpaTransactionManager
 @see org.springframework.transaction.jta.JtaTransactionManager
 @see org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
 @see org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
 */
public class JpaTemplate extends JpaAccessor implements JpaOperations {

  private boolean exposeNativeEntityManager = false;


  /**
   * Create a new JpaTemplate instance.
   */
  public JpaTemplate() {
  }

  /**
   * Create a new JpaTemplate instance.
   @param emf EntityManagerFactory to create EntityManagers
   */
  public JpaTemplate(EntityManagerFactory emf) {
    setEntityManagerFactory(emf);
    afterPropertiesSet();
  }

  /**
   * Create a new JpaTemplate instance.
   @param em EntityManager to use
   */
  public JpaTemplate(EntityManager em) {
    setEntityManager(em);
    afterPropertiesSet();
  }


  /**
   * Set whether to expose the native JPA EntityManager to JpaCallback
   * code. Default is "false": a EntityManager proxy will be returned,
   * suppressing <code>close</code> calls and automatically applying transaction
   * timeouts (if any).
   <p>As there is often a need to cast to a provider-specific EntityManager
   * class in DAOs that use the JPA 1.0 API, for JPA 2.0 previews and other
   * provider-specific functionality, the exposed proxy implements all interfaces
   * implemented by the original EntityManager. If this is not sufficient,
   * turn this flag to "true".
   @see JpaCallback
   @see javax.persistence.EntityManager
   */
  public void setExposeNativeEntityManager(boolean exposeNativeEntityManager) {
    this.exposeNativeEntityManager = exposeNativeEntityManager;
  }

  /**
   * Return whether to expose the native JPA EntityManager to JpaCallback
   * code, or rather an EntityManager proxy.
   */
  public boolean isExposeNativeEntityManager() {
    return this.exposeNativeEntityManager;
  }


  public <T> T execute(JpaCallback<T> actionthrows DataAccessException {
    return execute(action, isExposeNativeEntityManager());
  }

  public List executeFind(JpaCallback<?> actionthrows DataAccessException {
    Object result = execute(action, isExposeNativeEntityManager());
    if (!(result instanceof List)) {
      throw new InvalidDataAccessApiUsageException(
          "Result object returned from JpaCallback isn't a List: [" + result + "]");
    }
    return (Listresult;
  }

  /**
   * Execute the action specified by the given action object within a
   * EntityManager.
   @param action callback object that specifies the JPA action
   @param exposeNativeEntityManager whether to expose the native
   * JPA entity manager to callback code
   @return a result object returned by the action, or <code>null</code>
   @throws org.springframework.dao.DataAccessException in case of JPA errors
   */
  public <T> T execute(JpaCallback<T> action, boolean exposeNativeEntityManagerthrows DataAccessException {
    Assert.notNull(action, "Callback object must not be null");

    EntityManager em = getEntityManager();
    boolean isNewEm = false;
    if (em == null) {
      em = getTransactionalEntityManager();
      if (em == null) {
        logger.debug("Creating new EntityManager for JpaTemplate execution");
        em = createEntityManager();
        isNewEm = true;
      }
    }

    try {
      EntityManager emToExpose = (exposeNativeEntityManager ? em : createEntityManagerProxy(em));
      T result = action.doInJpa(emToExpose);
      flushIfNecessary(em, !isNewEm);
      return result;
    }
    catch (RuntimeException ex) {
      throw translateIfNecessary(ex);
    }
    finally {
      if (isNewEm) {
        logger.debug("Closing new EntityManager after JPA template execution");
        EntityManagerFactoryUtils.closeEntityManager(em);
      }
    }
  }

  /**
   * Create a close-suppressing proxy for the given JPA EntityManager.
   * The proxy also prepares returned JPA Query objects.
   @param em the JPA EntityManager to create a proxy for
   @return the EntityManager proxy, implementing all interfaces
   * implemented by the passed-in EntityManager object (that is,
   * also implementing all provider-specific extension interfaces)
   @see javax.persistence.EntityManager#close
   */
  protected EntityManager createEntityManagerProxy(EntityManager em) {
    Class[] ifcs = null;
    EntityManagerFactory emf = getEntityManagerFactory();
    if (emf instanceof EntityManagerFactoryInfo) {
      Class entityManagerInterface = ((EntityManagerFactoryInfoemf).getEntityManagerInterface();
      if (entityManagerInterface != null) {
        ifcs = new Class[] {entityManagerInterface};
      }
    }
    if (ifcs == null) {
      ifcs = ClassUtils.getAllInterfacesForClass(em.getClass());
    }
    return (EntityManagerProxy.newProxyInstance(
        em.getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(em));
  }


  //-------------------------------------------------------------------------
  // Convenience methods for load, save, delete
  //-------------------------------------------------------------------------

  public <T> T find(final Class<T> entityClass, final Object idthrows DataAccessException {
    return execute(new JpaCallback<T>() {
      public T doInJpa(EntityManager emthrows PersistenceException {
        return em.find(entityClass, id);
      }
    }true);
  }

  public <T> T getReference(final Class<T> entityClass, final Object idthrows DataAccessException {
    return execute(new JpaCallback<T>() {
      public T doInJpa(EntityManager emthrows PersistenceException {
        return em.getReference(entityClass, id);
      }
    }true);
  }

  public boolean contains(final Object entitythrows DataAccessException {
    return execute(new JpaCallback<Boolean>() {
      public Boolean doInJpa(EntityManager emthrows PersistenceException {
        return em.contains(entity);
      }
    }true);
  }

  public void refresh(final Object entitythrows DataAccessException {
    execute(new JpaCallback<Object>() {
      public Object doInJpa(EntityManager emthrows PersistenceException {
        em.refresh(entity);
        return null;
      }
    }true);
  }

  public void persist(final Object entitythrows DataAccessException {
    execute(new JpaCallback<Object>() {
      public Object doInJpa(EntityManager emthrows PersistenceException {
        em.persist(entity);
        return null;
      }
    }true);
  }

  public <T> T merge(final T entitythrows DataAccessException {
    return execute(new JpaCallback<T>() {
      public T doInJpa(EntityManager emthrows PersistenceException {
        return em.merge(entity);
      }
    }true);
  }

  public void remove(final Object entitythrows DataAccessException {
    execute(new JpaCallback<Object>() {
      public Object doInJpa(EntityManager emthrows PersistenceException {
        em.remove(entity);
        return null;
      }
    }true);
  }

  public void flush() throws DataAccessException {
    execute(new JpaCallback<Object>() {
      public Object doInJpa(EntityManager emthrows PersistenceException {
        em.flush();
        return null;
      }
    }true);
  }


  //-------------------------------------------------------------------------
  // Convenience finder methods
  //-------------------------------------------------------------------------

  public List find(String queryStringthrows DataAccessException {
    return find(queryString, (Object[]) null);
  }

  public List find(final String queryString, final Object... valuesthrows DataAccessException {
    return execute(new JpaCallback<List>() {
      public List doInJpa(EntityManager emthrows PersistenceException {
        Query queryObject = em.createQuery(queryString);
        prepareQuery(queryObject);
        if (values != null) {
          for (int i = 0; i < values.length; i++) {
            queryObject.setParameter(i + 1, values[i]);
          }
        }
        return queryObject.getResultList();
      }
    });
  }

  public List findByNamedParams(final String queryString, final Map<String, ?> paramsthrows DataAccessException {
    return execute(new JpaCallback<List>() {
      public List doInJpa(EntityManager emthrows PersistenceException {
        Query queryObject = em.createQuery(queryString);
        prepareQuery(queryObject);
        if (params != null) {
          for (Map.Entry<String, ?> entry : params.entrySet()) {
            queryObject.setParameter(entry.getKey(), entry.getValue());
          }
        }
        return queryObject.getResultList();
      }
    });
  }

  public List findByNamedQuery(String queryNamethrows DataAccessException {
    return findByNamedQuery(queryName, (Object[]) null);
  }

  public List findByNamedQuery(final String queryName, final Object... valuesthrows DataAccessException {
    return execute(new JpaCallback<List>() {
      public List doInJpa(EntityManager emthrows PersistenceException {
        Query queryObject = em.createNamedQuery(queryName);
        prepareQuery(queryObject);
        if (values != null) {
          for (int i = 0; i < values.length; i++) {
            queryObject.setParameter(i + 1, values[i]);
          }
        }
        return queryObject.getResultList();
      }
    });
  }

  public List findByNamedQueryAndNamedParams(final String queryName, final Map<String, ?> params)
      throws DataAccessException {

    return execute(new JpaCallback<List>() {
      public List doInJpa(EntityManager emthrows PersistenceException {
        Query queryObject = em.createNamedQuery(queryName);
        prepareQuery(queryObject);
        if (params != null) {
          for (Map.Entry<String, ?> entry : params.entrySet()) {
            queryObject.setParameter(entry.getKey(), entry.getValue());
          }
        }
        return queryObject.getResultList();
      }
    });
  }


  /**
   * Prepare the given JPA query object. To be used within a JpaCallback.
   <p>Applies a transaction timeout, if any. If you don't use such timeouts,
   * the call is a no-op.
   <p>In general, prefer a proxied EntityManager instead, which will
   * automatically apply the transaction timeout (through the use of a special
   * EntityManager proxy). You need to set the "exposeNativeEntityManager"
   * property to "false" to activate this. Note that you won't be able to cast
   * to a provider-specific JPA EntityManager class anymore then.
   @param query the JPA query object
   @see JpaCallback#doInJpa
   @see EntityManagerFactoryUtils#applyTransactionTimeout
   @see #setExposeNativeEntityManager
   */
  public void prepareQuery(Query query) {
    EntityManagerFactory emf = getEntityManagerFactory();
    if (emf != null) {
      EntityManagerFactoryUtils.applyTransactionTimeout(query, getEntityManagerFactory());
    }
  }


  /**
   * Invocation handler that suppresses close calls on JPA EntityManagers.
   * Also prepares returned Query objects.
   @see javax.persistence.EntityManager#close()
   */
  private class CloseSuppressingInvocationHandler implements InvocationHandler {

    private final EntityManager target;

    public CloseSuppressingInvocationHandler(EntityManager target) {
      this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] argsthrows Throwable {
      // Invocation on EntityManager interface (or provider-specific extension) coming in...

      if (method.getName().equals("equals")) {
        // Only consider equal when proxies are identical.
        return (proxy == args[0]);
      }
      else if (method.getName().equals("hashCode")) {
        // Use hashCode of EntityManager proxy.
        return System.identityHashCode(proxy);
      }
      else if (method.getName().equals("close")) {
        // Handle close method: suppress, not valid.
        return null;
      }

      // Invoke method on target EntityManager.
      try {
        Object retVal = method.invoke(this.target, args);
        // If return value is a JPA Query object, apply transaction timeout.
        if (retVal instanceof Query) {
          prepareQuery(((QueryretVal));
        }
        return retVal;
      }
      catch (InvocationTargetException ex) {
        throw ex.getTargetException();
      }
    }
  }

}