天天看點

Spring-Data-JPA EntityManager注入疑問探究

文章目錄

      • 背景
      • SessionImpl VS SharedEntityManagerCreator$SharedEntityManagerInvocationHandler
      • Qualifier比對注入
      • 結論
我們項目中使用JPA多資料源并注入entityManager使用,偶發性的出現Connection is closed異常,并且一旦出現該異常程式無法使用資料庫連接配接池特性重建程式連接配接,可以确認的是資料庫伺服器為了出于性能考慮會主動中斷長時間不使用的連接配接,但整個異常出現的原因,解決辦法,以及探究過程中的疑問,都将在下文中一一探究。

背景

在Spring Data JPA中,我們知道entityManager是我們操作資料庫的重要工具,不僅可以直接對entity進行crud等基礎操作,也可以通過createNativeQuery等方法直接使用原生SQL語句操作資料庫。

在我們使用entityManager的時候可以通過@PersistenceContext(unitName=‘xxxentityManagerFactory’)或者@[email protected](‘xxxEntityManagerFactory’)方式注入entityManager對象:

// serviceClass.java
    @Autowired
    @Qualifier("springEntityManagerFactory")
    private EntityManager entityManager1;

    @Autowired
    @Qualifier("springJpaEntityManager")
    private EntityManager entityManager2;

    @PersistenceContext(unitName="springEntityManager")
    private EntityManager entityManager3;
           

其中對應的資料源配置如下:

//DataSourceConfig.java
    @Bean(name = "springEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean springEntityManagerFactory(@Qualifier("dataSource") DataSource masterDataSource) {
        HashMap<String, Object> properties = new HashMap<String, Object>(16);
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setDataSource(masterDataSource);
        ...
        entityManager.setPersistenceUnitName("springEntityManager");
        entityManager.setJpaPropertyMap(properties);
        return entityManager;
    }
    @Bean("springJpaEntityManager")
    public EntityManager entityManager(@Qualifier("springEntityManagerFactory") LocalContainerEntityManagerFactoryBean springEntityManagerFactory){
        return springEntityManagerFactory.getObject().createEntityManager();
    }
           

因為Qualifier注解一般都是通過beanName比對,而我們注入entityManager的時候卻可以指向一個entityManagerFactoryBean來實作注入,對此大感困惑;除此之外我也查閱了一些網絡部落格說entityManager是線程不安全的,是以應該使用@PersistenceContext來注入,是以我有了以下幾個疑問:

  • PersistenceContext和Autowired注入的em是否有差別?是否真的線程不安全?
  • Qualifier注解為什麼可以指向entityManagerFactory實作entityManager的注入?

接下來我們就帶着這兩個疑問去探秘一下entityManager的注入過程(本次使用的架構版本為Spring Data Jpa2.3.2+Spring 5.2.8):

SessionImpl VS SharedEntityManagerCreator$SharedEntityManagerInvocationHandler

在上述配置中,我們通過在serviceClass中打斷點發現注入的entityManager1和entityManager3都是SharedEntityManagerCreator的内部類SharedEntityManagerInvocationHandler的一個代理類執行個體,它們指向了同一個entityManagerFacotry,而entityManger2則是SessionImpl的代理執行個體,我們分别檢視org.springframework.orm.jpa.SharedEntityManagerCreato和org.hibernate.internal.SessionImpl的源碼發現了奧妙所在,截取的核心代碼段如下:

//org.springframework.orm.jpa.SharedEntityManagerCreator.SharedEntityManagerInvocationHandler#invoke
	/**
	 * Invocation handler that delegates all calls to the current
	 * transactional EntityManager, if any; else, it will fall back
	 * to a newly created EntityManager per operation.
	 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ......
    EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager(
					this.targetFactory, this.properties, this.synchronizedWithTransaction);
    ......
			try {
				Object result = method.invoke(target, args);
				if (result instanceof Query) {
					Query query = (Query) result;
					if (isNewEm) {
						Class<?>[] ifcs = cachedQueryInterfaces.computeIfAbsent(query.getClass(), key ->
								ClassUtils.getAllInterfacesForClass(key, this.proxyClassLoader));
						result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
								new DeferredQueryInvocationHandler(query, target));
						isNewEm = false;
					}
					else {
						EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
					}
				}
				return result;
			}
			catch (InvocationTargetException ex) {
				throw ex.getTargetException();
			}
			finally {
				if (isNewEm) {
					EntityManagerFactoryUtils.closeEntityManager(target);
				}
			}
	}
           
//org.hibernate.internal.SessionImpl JavaDoc
/**
 * Concrete implementation of a Session.
 * <p/>
 * Exposes two interfaces:<ul>
 * <li>{@link org.hibernate.Session} to the application</li>
 * <li>{@link org.hibernate.engine.spi.SessionImplementor} to other Hibernate components (SPI)</li>
 * </ul>
 * <p/>
 * This class is not thread-safe.
 *
 * @author Gavin King
 * @author Steve Ebersole
 * @author Brett Meyer
 * @author Chris Cranford
 * @author Sanne Grinovero
 */
public class SessionImpl
		extends AbstractSessionImpl
		implements EventSource, SessionImplementor, HibernateEntityManagerImplementor {
    ....
}
           

SharedEntityManagerInvocationHandler包裝後的entityManager對象和目前事務息息相關,保證了事務安全及線程安全;而SessionImpl的核心則是session接口,僅代表了一次session會話,甚至在其自身的JavaDoc中已經表明了不是線程安全的。

Qualifier比對注入

和@Resource注入不同,@Qualifier提供了一個比對規則用于bean的注入比對,關于entityManager的注入,主要有以下幾個類在其中起了重要作用:

  • org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor
  • org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver

核心代碼如下所示:

//org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor#postProcessBeanFactory
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

		if (!ConfigurableListableBeanFactory.class.isInstance(beanFactory)) {
			return;
		}

		ConfigurableListableBeanFactory factory = (ConfigurableListableBeanFactory) beanFactory;

		for (EntityManagerFactoryBeanDefinition definition : getEntityManagerFactoryBeanDefinitions(factory)) {

			BeanFactory definitionFactory = definition.getBeanFactory();

			if (!(definitionFactory instanceof BeanDefinitionRegistry)) {
				continue;
			}

			BeanDefinitionRegistry definitionRegistry = (BeanDefinitionRegistry) definitionFactory;

			BeanDefinitionBuilder builder = BeanDefinitionBuilder
					.rootBeanDefinition("org.springframework.orm.jpa.SharedEntityManagerCreator");
			builder.setFactoryMethod("createSharedEntityManager");
			builder.addConstructorArgReference(definition.getBeanName());

			AbstractBeanDefinition emBeanDefinition = builder.getRawBeanDefinition();
			//将entityManagerFactory名稱添加到SharedEntityManagerCreator的qualifer資訊中,用于@Qualifier注解比對
			emBeanDefinition.addQualifier(new AutowireCandidateQualifier(Qualifier.class, definition.getBeanName()));
			emBeanDefinition.setScope(definition.getBeanDefinition().getScope());
			emBeanDefinition.setSource(definition.getBeanDefinition().getSource());
			emBeanDefinition.setLazyInit(true);

			BeanDefinitionReaderUtils.registerWithGeneratedName(emBeanDefinition, definitionRegistry);
		}
	}
           

JPA通過實作BeanFactoryPostProcessor接口對所有實作了EntityManagerFactory接口或繼承自AbstractEntityManagerFactoryBean類的entityManagerFactory注入了SharedEntityManagerCreator相關的資訊,它本質上是一個entityManager類型的bean資訊定義,并通過AbstractBeanDefinition的addQualifier方法将該entityManager和entityManagerFatory進行了關聯。

//org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#checkQualifier
	/**
	 * Match the given qualifier annotation against the candidate bean definition.
	 */
	protected boolean checkQualifier(
			BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {

		Class<? extends Annotation> type = annotation.annotationType();
		RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();

		AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
		if (qualifier == null) {
			qualifier = bd.getQualifier(ClassUtils.getShortName(type));
		}
		......
		for (Map.Entry<String, Object> entry : attributes.entrySet()) {
			String attributeName = entry.getKey();
			Object expectedValue = entry.getValue();
			Object actualValue = null;
			// Check qualifier first
			if (qualifier != null) {
				actualValue = qualifier.getAttribute(attributeName);
			}
			if (actualValue == null) {
				// Fall back on bean definition attribute
				actualValue = bd.getAttribute(attributeName);
			}
			if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
					expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) {
				// Fall back on bean name (or alias) match
				continue;
			}
			if (actualValue == null && qualifier != null) {
				// Fall back on default, but only if the qualifier is present
				actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
			}
			if (actualValue != null) {
				actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
			}
			if (!expectedValue.equals(actualValue)) {
				return false;
			}
		}
		return true;
	}
           

在QualifierAnnotationAutowireCandidateResolver的比對規則中,會擷取AbstractBeanDefinition中的qualifiers來進行比對,如果命中,則也可以進行注入。

結論

  • 在Spring Data Jpa2.3.2+Spring 5.2.8版本下探究後發現使用上述配置驗證兩種方式注入的em沒有差別,都是線程安全的;
  • qualifier注解指向entityManagerFactory可以實作entityManager的注入是因為entityManager的beanDefinition資訊在初始化的時候添加了qualifier比對規則關聯了entityManagerFactory。
  • 在配置資料源的時候,不要配置entityManager對象,如果一定要配置可以如下方式配置:
/**
     * 切不可通過LocalContainerEntityManagerFactoryBean#getObject()#createEntityManager()方式注入。
     */
    @Bean("springJpaEntityManager")
    public EntityManager entityManager(@Qualifier("springEntityManagerFactory") EntityManager entityManager) 	{
        return entityManager;
    }
           
  • 搜尋引擎搜尋出來的技術文章品質參差不齊,架構配置盡量參考官方文檔,Spring Data JPA官方配置參考。

繼續閱讀