From dd4db60f922e8668a9c068c630d42e16a4e31243 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 25 Jul 2011 12:26:27 +0000 Subject: [PATCH] JpaTransactionManager etc can find EntityManagerFactory by "persistenceUnitName" property now, falling back to retrieval of a unique EntityManagerFactory bean by type (analogous to @PersistenceUnit / @PersistenceContext) git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4771 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../orm/jpa/EntityManagerFactoryAccessor.java | 46 +++++++++- .../orm/jpa/EntityManagerFactoryUtils.java | 35 ++++---- .../orm/jpa/JpaTransactionManager.java | 49 ++++++++++- .../OpenEntityManagerInViewFilter.java | 84 ++++++++++++++----- .../jpa/support/SharedEntityManagerBean.java | 6 +- 5 files changed, 177 insertions(+), 43 deletions(-) diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java index c99bc788682..005b6c1c6a1 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2011 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. @@ -19,13 +19,16 @@ package org.springframework.orm.jpa; import java.util.HashMap; import java.util.Map; import java.util.Properties; - import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -40,13 +43,15 @@ import org.springframework.util.CollectionUtils; * @see JpaAccessor * @see EntityManagerFactoryUtils */ -public abstract class EntityManagerFactoryAccessor { +public abstract class EntityManagerFactoryAccessor implements BeanFactoryAware { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); private EntityManagerFactory entityManagerFactory; + private String persistenceUnitName; + private final Map jpaPropertyMap = new HashMap(); @@ -68,6 +73,25 @@ public abstract class EntityManagerFactoryAccessor { return this.entityManagerFactory; } + /** + * Set the name of the persistence unit to access the EntityManagerFactory for. + *

This is an alternative to specifying the EntityManagerFactory by direct reference, + * resolving it by its persistence unit name instead. If no EntityManagerFactory and + * no persistence unit name have been specified, a default EntityManagerFactory will + * be retrieved through finding a single unique bean of type EntityManagerFactory. + * @see #setEntityManagerFactory + */ + public void setPersistenceUnitName(String persistenceUnitName) { + this.persistenceUnitName = persistenceUnitName; + } + + /** + * Return the name of the persistence unit to access the EntityManagerFactory for, if any. + */ + public String getPersistenceUnitName() { + return this.persistenceUnitName; + } + /** * Specify JPA properties, to be passed into * EntityManagerFactory.createEntityManager(Map) (if any). @@ -100,6 +124,22 @@ public abstract class EntityManagerFactoryAccessor { return this.jpaPropertyMap; } + /** + * Retrieves an EntityManagerFactory by persistence unit name, if none set explicitly. + * Falls back to a default EntityManagerFactory bean if no persistence unit specified. + * @see #setPersistenceUnitName + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (getEntityManagerFactory() == null) { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalStateException("Cannot retrieve EntityManagerFactory by persistence unit name " + + "in a non-listable BeanFactory: " + beanFactory); + } + ListableBeanFactory lbf = (ListableBeanFactory) beanFactory; + setEntityManagerFactory(EntityManagerFactoryUtils.findEntityManagerFactory(lbf, getPersistenceUnitName())); + } + } + /** * Obtain a new EntityManager from this accessor's EntityManagerFactory. diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java index 3e88b7178d6..8e5fc87af56 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -46,6 +46,7 @@ import org.springframework.transaction.support.ResourceHolderSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** * Helper class featuring methods for JPA EntityManager handling, @@ -80,7 +81,8 @@ public abstract class EntityManagerFactoryUtils { * the persistence unit name will be matched against the Spring bean name, * assuming that the EntityManagerFactory bean names follow that convention. * @param beanFactory the ListableBeanFactory to search - * @param unitName the name of the persistence unit (never empty) + * @param unitName the name of the persistence unit (may be null or empty, + * in which case a single bean of type EntityManagerFactory will be searched for) * @return the EntityManagerFactory * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory in the context * @see EntityManagerFactoryInfo#getPersistenceUnitName() @@ -89,22 +91,25 @@ public abstract class EntityManagerFactoryUtils { ListableBeanFactory beanFactory, String unitName) throws NoSuchBeanDefinitionException { Assert.notNull(beanFactory, "ListableBeanFactory must not be null"); - Assert.hasLength(unitName, "Unit name must not be empty"); - - // See whether we can find an EntityManagerFactory with matching persistence unit name. - String[] candidateNames = - BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, EntityManagerFactory.class); - for (String candidateName : candidateNames) { - EntityManagerFactory emf = (EntityManagerFactory) beanFactory.getBean(candidateName); - if (emf instanceof EntityManagerFactoryInfo) { - if (unitName.equals(((EntityManagerFactoryInfo) emf).getPersistenceUnitName())) { - return emf; + if (StringUtils.hasLength(unitName)) { + // See whether we can find an EntityManagerFactory with matching persistence unit name. + String[] candidateNames = + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, EntityManagerFactory.class); + for (String candidateName : candidateNames) { + EntityManagerFactory emf = (EntityManagerFactory) beanFactory.getBean(candidateName); + if (emf instanceof EntityManagerFactoryInfo) { + if (unitName.equals(((EntityManagerFactoryInfo) emf).getPersistenceUnitName())) { + return emf; + } } } + // No matching persistence unit found - simply take the EntityManagerFactory + // with the persistence unit name as bean name (by convention). + return beanFactory.getBean(unitName, EntityManagerFactory.class); + } + else { + return BeanFactoryUtils.beanOfType(beanFactory, EntityManagerFactory.class); } - // No matching persistence unit found - simply take the EntityManagerFactory - // with the persistence unit name as bean name (by convention). - return beanFactory.getBean(unitName, EntityManagerFactory.class); } /** diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index 94e92183184..efd2ef1a649 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -26,7 +26,11 @@ import javax.persistence.PersistenceException; import javax.persistence.RollbackException; import javax.sql.DataSource; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.datasource.ConnectionHandle; @@ -105,10 +109,12 @@ import org.springframework.util.CollectionUtils; * @see org.springframework.transaction.jta.JtaTransactionManager */ public class JpaTransactionManager extends AbstractPlatformTransactionManager - implements ResourceTransactionManager, InitializingBean { + implements ResourceTransactionManager, BeanFactoryAware, InitializingBean { private EntityManagerFactory entityManagerFactory; + private String persistenceUnitName; + private final Map jpaPropertyMap = new HashMap(); private DataSource dataSource; @@ -138,6 +144,10 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager /** * Set the EntityManagerFactory that this instance should manage transactions for. + *

Alternatively, specify the persistence unit name of the target EntityManagerFactory. + * By default, a default EntityManagerFactory will be retrieved through finding a + * single unique bean of type EntityManagerFactory in the containing BeanFactory. + * @see #setPersistenceUnitName */ public void setEntityManagerFactory(EntityManagerFactory emf) { this.entityManagerFactory = emf; @@ -150,6 +160,25 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager return this.entityManagerFactory; } + /** + * Set the name of the persistence unit to manage transactions for. + *

This is an alternative to specifying the EntityManagerFactory by direct reference, + * resolving it by its persistence unit name instead. If no EntityManagerFactory and + * no persistence unit name have been specified, a default EntityManagerFactory will + * be retrieved through finding a single unique bean of type EntityManagerFactory. + * @see #setEntityManagerFactory + */ + public void setPersistenceUnitName(String persistenceUnitName) { + this.persistenceUnitName = persistenceUnitName; + } + + /** + * Return the name of the persistence unit to manage transactions for, if any. + */ + public String getPersistenceUnitName() { + return this.persistenceUnitName; + } + /** * Specify JPA properties, to be passed into * EntityManagerFactory.createEntityManager(Map) (if any). @@ -247,6 +276,22 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager return this.jpaDialect; } + /** + * Retrieves an EntityManagerFactory by persistence unit name, if none set explicitly. + * Falls back to a default EntityManagerFactory bean if no persistence unit specified. + * @see #setPersistenceUnitName + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (getEntityManagerFactory() == null) { + if (!(beanFactory instanceof ListableBeanFactory)) { + throw new IllegalStateException("Cannot retrieve EntityManagerFactory by persistence unit name " + + "in a non-listable BeanFactory: " + beanFactory); + } + ListableBeanFactory lbf = (ListableBeanFactory) beanFactory; + setEntityManagerFactory(EntityManagerFactoryUtils.findEntityManagerFactory(lbf, getPersistenceUnitName())); + } + } + /** * Eagerly initialize the JPA dialect, creating a default one * for the specified EntityManagerFactory if none set. @@ -254,7 +299,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager */ public void afterPropertiesSet() { if (getEntityManagerFactory() == null) { - throw new IllegalArgumentException("Property 'entityManagerFactory' is required"); + throw new IllegalArgumentException("'entityManagerFactory' or 'persistenceUnitName' is required"); } if (getEntityManagerFactory() instanceof EntityManagerFactoryInfo) { EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) getEntityManagerFactory(); diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java index 3732ebb3d2a..895ef6573d0 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -17,7 +17,6 @@ package org.springframework.orm.jpa.support; import java.io.IOException; - import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; @@ -27,9 +26,10 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.dao.DataAccessResourceFailureException; -import org.springframework.orm.jpa.EntityManagerHolder; import org.springframework.orm.jpa.EntityManagerFactoryUtils; +import org.springframework.orm.jpa.EntityManagerHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -47,10 +47,10 @@ import org.springframework.web.filter.OncePerRequestFilter; * as for non-transactional read-only execution. * *

Looks up the EntityManagerFactory in Spring's root web application context. - * Supports a "entityManagerFactoryBeanName" filter init-param in web.xml; - * the default bean name is "entityManagerFactory". Looks up the EntityManagerFactory - * on each request, to avoid initialization order issues (when using ContextLoaderServlet, - * the root application context will get initialized after this filter). + * Supports an "entityManagerFactoryBeanName" filter init-param in web.xml; + * the default bean name is "entityManagerFactory". As an alternative, the + * "persistenceUnitName" init-param allows for retrieval by logical unit name + * (as specified in persistence.xml). * * @author Juergen Hoeller * @since 2.0 @@ -63,15 +63,28 @@ import org.springframework.web.filter.OncePerRequestFilter; */ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { - public static final String DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory"; + /** + * Default EntityManagerFactory bean name: "entityManagerFactory". + * Only applies when no "persistenceUnitName" param has been specified. + * @see #setEntityManagerFactoryBeanName + * @see #setPersistenceUnitName + */ + public static final String DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory"; - private String entityManagerFactoryBeanName = DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME; + private String entityManagerFactoryBeanName; + + private String persistenceUnitName; + + private volatile EntityManagerFactory entityManagerFactory; /** * Set the bean name of the EntityManagerFactory to fetch from Spring's - * root application context. Default is "entityManagerFactory". - * @see #DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME + * root application context. + *

Default is "entityManagerFactory". Note that this default only applies + * when no "persistenceUnitName" param has been specified. + * @see #setPersistenceUnitName + * @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME */ public void setEntityManagerFactoryBeanName(String entityManagerFactoryBeanName) { this.entityManagerFactoryBeanName = entityManagerFactoryBeanName; @@ -85,6 +98,27 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { return this.entityManagerFactoryBeanName; } + /** + * Set the name of the persistence unit to access the EntityManagerFactory for. + *

This is an alternative to specifying the EntityManagerFactory by bean name, + * resolving it by its persistence unit name instead. If no bean name and no persistence + * unit name have been specified, we'll check whether a bean exists for the default + * bean name "entityManagerFactory"; if not, a default EntityManagerFactory will + * be retrieved through finding a single unique bean of type EntityManagerFactory. + * @see #setEntityManagerFactoryBeanName + * @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME + */ + public void setPersistenceUnitName(String persistenceUnitName) { + this.persistenceUnitName = persistenceUnitName; + } + + /** + * Return the name of the persistence unit to access the EntityManagerFactory for, if any. + */ + protected String getPersistenceUnitName() { + return this.persistenceUnitName; + } + @Override protected void doFilterInternal( @@ -126,29 +160,39 @@ public class OpenEntityManagerInViewFilter extends OncePerRequestFilter { /** * Look up the EntityManagerFactory that this filter should use, * taking the current HTTP request as argument. - *

Default implementation delegates to the lookupEntityManagerFactory - * without arguments. + *

The default implementation delegates to the lookupEntityManagerFactory + * without arguments, caching the EntityManagerFactory reference once obtained. * @return the EntityManagerFactory to use * @see #lookupEntityManagerFactory() */ protected EntityManagerFactory lookupEntityManagerFactory(HttpServletRequest request) { - return lookupEntityManagerFactory(); + if (this.entityManagerFactory == null) { + this.entityManagerFactory = lookupEntityManagerFactory(); + } + return this.entityManagerFactory; } /** * Look up the EntityManagerFactory that this filter should use. - * The default implementation looks for a bean with the specified name + *

The default implementation looks for a bean with the specified name * in Spring's root application context. * @return the EntityManagerFactory to use * @see #getEntityManagerFactoryBeanName */ protected EntityManagerFactory lookupEntityManagerFactory() { - if (logger.isDebugEnabled()) { - logger.debug("Using EntityManagerFactory '" + getEntityManagerFactoryBeanName() + - "' for OpenEntityManagerInViewFilter"); - } WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - return wac.getBean(getEntityManagerFactoryBeanName(), EntityManagerFactory.class); + String emfBeanName = getEntityManagerFactoryBeanName(); + String puName = getPersistenceUnitName(); + if (StringUtils.hasLength(emfBeanName)) { + return wac.getBean(emfBeanName, EntityManagerFactory.class); + } + else if (!StringUtils.hasLength(puName) && wac.containsBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME)) { + return wac.getBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME, EntityManagerFactory.class); + } + else { + // Includes fallback search for single EntityManagerFactory bean by type. + return EntityManagerFactoryUtils.findEntityManagerFactory(wac, puName); + } } /** diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java index d2289c53289..72e483997f1 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/support/SharedEntityManagerBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -67,7 +67,7 @@ public class SharedEntityManagerBean extends EntityManagerFactoryAccessor * @see javax.persistence.EntityManager */ public void setEntityManagerInterface(Class entityManagerInterface) { - Assert.notNull(entityManagerInterface, "entityManagerInterface must not be null"); + Assert.notNull(entityManagerInterface, "'entityManagerInterface' must not be null"); Assert.isAssignable(EntityManager.class, entityManagerInterface); this.entityManagerInterface = entityManagerInterface; } @@ -76,7 +76,7 @@ public class SharedEntityManagerBean extends EntityManagerFactoryAccessor public final void afterPropertiesSet() { EntityManagerFactory emf = getEntityManagerFactory(); if (emf == null) { - throw new IllegalArgumentException("entityManagerFactory is required"); + throw new IllegalArgumentException("'entityManagerFactory' or 'persistenceUnitName' is required"); } Class[] ifcs = null; if (emf instanceof EntityManagerFactoryInfo) {