文章目录
我们项目中使用JPA多数据源并注入entityManager使用,偶发性的出现Connection is closed异常,并且一旦出现该异常程序无法使用数据库连接池特性重建程序连接,可以确认的是数据库服务器出于性能和安全考虑会定时主动中断部分数据库连接,但整个异常出现的原因,解决办法,以及探究过程中的疑问,都将在下文中一一探究。
背景
在Spring Data JPA中,我们知道entityManager是我们操作数据库的重要工具,不仅可以直接对entity进行crud等基础操作,也可以通过createNativeQuery等方法直接使用原生SQL语句操作数据库。
在我们使用entityManager的时候可以通过@PersistenceContext(unitName=‘xxxentityManagerFactory’)或者@Autowired+@Qualifier(‘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官方配置参考。