Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » orm » jpa » [javadoc | source]
    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   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » orm » jpa » [javadoc | source]