Research on spring data JPA entitymanager injection query

Research on spring data JPA entitymanager injection query

background

In Spring Data JPA, we know that entityManager is an important tool for us to operate the database. It can not only directly perform crud and other basic operations on the entity, but also directly use native SQL statements to operate the database through methods such as createNativeQuery.

When using entityManager, we can inject entityManager objects through @ PersistenceContext(unitName = 'xxentitymanagerfactory') or @ Autowired+@Qualifier('xxentitymanagerfactory '):

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

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

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

The corresponding data source configuration is as follows:

    //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();
    }

Because the Qualifier annotation is usually matched by beanName, but when we inject entityManager, we can point to an entityManagerFactoryBean to realize the injection, which is very confused; In addition, I also looked up some Web blog entityManager is thread unsafe, so @ PersistenceContext should be used for injection. Therefore, I have the following questions:

  • Is there any difference between the em injected by PersistenceContext and Autowired? Is it really thread unsafe?
  • Why can the Qualifier annotation point to the entityManagerFactory to implement the injection of entityManager?

Next, let's take these two questions to explore the injection process of entityManager (the Framework version used this time is Spring Data Jpa2.3.2+Spring 5.2.8):

SessionImpl VS SharedEntityManagerCreator$SharedEntityManagerInvocationHandler

In the above configuration, we found that the injected entityManager1 and entityManager3 are a proxy class instance of the internal class SharedEntityManagerInvocationHandler of SharedEntityManagerCreator through the break point in the serviceClass. They point to the same entitymanagerfactory, and entityManger2 is a proxy instance of SessionImpl. Let's check org. Org springframework. orm. jpa. Sharedentitymanagercreato and org hibernate. internal. The source code of SessionImpl has found the mystery. The core code snippet is as follows:

//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 {
    ....
}

The entityManager object wrapped by SharedEntityManagerInvocationHandler is closely related to the current transaction, ensuring transaction safety and thread safety; The core of SessionImpl is the session interface, which only represents one session. It has even been indicated in its own JavaDoc that it is not thread safe.

Qualifier matching injection

Unlike @ Resource injection, @ Qualifier provides a matching rule for bean injection matching. The following classes play an important role in entityManager injection:

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

The core code is as follows:

//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();

			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 injects SharedEntityManagerCreator related information into all entitymanagerfactories that implement the entitymanagerfactory interface or inherit from the AbstractEntityManagerFactoryBean class by implementing the beanfactoryprocessor interface. It is essentially a bean information definition of entityManager type, The entityManager and entitymanagerfactory are associated through the addQualifier method of AbstractBeanDefinition.

//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;
	}

In the matching rule of qualifiernannotationautowirecandidateresolver, the qualifiers in AbstractBeanDefinition will be obtained for matching. If it hits, it can also be injected.

conclusion

  • In spring data JPA2 3.2+Spring 5.2. After exploring under version 8, it is found that there is no difference between the em injected by the two methods using the above configuration verification, and they are thread safe;

  • The qualifier annotation points to the entityManagerFactory, which can realize the injection of entityManager because the beanDefinition information of entityManager adds a qualifier matching rule during initialization and is associated with the entityManagerFactory.

  • When configuring data sources, do not configure entityManager objects. If necessary, you can configure them as follows:

    /**
     * It must not be injected through LocalContainerEntityManagerFactoryBean#getObject()#createEntityManager().
     */
    @Bean("springJpaEntityManager")
    public EntityManager entityManager(@Qualifier("springEntityManagerFactory") EntityManager entityManager) 	{
        return entityManager;
    }

Blood and tears lesson

  • In our actual project, we used the LocalContainerEntityManagerFactoryBean#getObject()#createEntityManager() method to inject entityManager, and inadvertently used the @ Autowired method to inject it into the business code. However, the database service will actively recycle the sessions that are not used for a period of time, resulting in the occasional connection is closed exception of entityManager, If SharedEntityManager is used, the database will obtain a connection from the DataSourcePool every time it operates. Returning it after use can solve this problem. This article is featured to record the cause and effect of Q & A.

Keywords: jpa

Added by dcallstar51 on Fri, 24 Dec 2021 15:21:17 +0200