文章目录

  • 背景
  • SessionImpl VS SharedEntityManagerCreator$SharedEntityManagerInvocationHandler
  • Qualifier匹配注入
  • 结论

我们项目中使用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 SessionImplextends AbstractSessionImplimplements 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#postProcessBeanFactorypublic 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 firstif (qualifier != null) {actualValue = qualifier.getAttribute(attributeName);}if (actualValue == null) {// Fall back on bean definition attributeactualValue = 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) matchcontinue;}if (actualValue == null && qualifier != null) {// Fall back on default, but only if the qualifier is presentactualValue = 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官方配置参考。

Spring-Data-JPA EntityManager 从一次线上BUG探究Autowired和PersistenceContext的区别相关推荐

  1. Spring Boot文档阅读笔记-使用Spring Data JPA连接多源数据库(MySQL和Oracle)

    下面这个小项目展示了如何连接2个数据库,一个是Oracle,一个是MySQL. 关键的Maven依赖: <dependency><groupId>org.springframe ...

  2. 从Spring Data JPA访问EntityManager

    Spring Data JPA允许您通过使用Repository接口来快速开发数据访问层. 有时,您需要从Spring Data JPA访问EntityManager. 这篇文章向您展示了如何访问En ...

  3. Spring Data JPA 从入门到精通~EntityManager介绍

    EntityManager 介绍 我们前面已经无数次提到了,JPA 的默认 Repository 的实现类是 SimpleJpaRepository,而里面的具体实现就是调用的 EntityManag ...

  4. Spring Data JPA 原理与实战第二天 掌握Repoitory和DQM

    02 Spring Data Common 之 Repoitory 如何全面掌握? 通过上一课时,我们知道了 Spring Data 对整个数据操作做了很好的封装,其中 Spring Data Com ...

  5. Spring Data JPA 实战

    课程介绍 <Spring Data JPA 实战>内容是基于作者学习和工作中实践的总结和升华,有一句经典的话:"现在的开发人员是站在巨人的肩上,弯道超车".因现在框架越 ...

  6. Spring Boot 、Spring Data JPA、Hibernate集成

    ###什么是JPA JPA是用于管理Java EE 和Java SE环境中的持久化,以及对象/关系映射的JAVA API 最新规范为"JSR 338:Java Persistence 2.1 ...

  7. Spring Data JPA

    1.    概述 Spring JPA通过为用户统一创建和销毁EntityManager,进行事务管理,简化JPA的配置等使用户的开发更加简便. Spring Data JPA是在Spring JPA ...

  8. spring data jpa从入门到精通_Spring Data JPA的简单入门

    前言 spring data JPA是spring团队打造的sping生态全家桶的一部分,本身内核使用的是hibernate核心源码,用来作为了解java持久层框架基本构成的样本是再好不过的选择.最近 ...

  9. Spring Boot 2.x基础教程:Spring Data JPA的多数据源配置

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 上一篇我们介绍了在使用JdbcTemplate来做数据访 ...

最新文章

  1. liunx上安装nacos
  2. 学界丨北大清华合力打造通用人工智能实验班,朱松纯教授领衔
  3. Thinkpad SL400 issue
  4. Leetcode 96. 不同的二叉搜索树 解题思路及C++实现
  5. linux2.6内核分析,linux2.6内核分析——LRU链表
  6. 在ES6类中绑定事件
  7. C++ 实验2:函数重载、函数模板、简单类的定义和实现
  8. DOM-window下的常用子对象-location-刷新页面
  9. 关于微星主板安装ubuntu16.04系统连不上网。ifconfig-a 只显示 lo的
  10. win10计算机不显示usb,win10插入U盘不显示怎么办_解决win10u盘插电脑上不显示的办法...
  11. 中国保险行业市场现状及发展空间分析
  12. Foxmail上Gmail打不开登录不了邮箱最新解决方法
  13. 剑侠世界3怎么快速起号?
  14. 易语言注册机接码平台对接
  15. java赵云主角兵器谱游戏_赵云赵子龙的外号有哪些?赵云的武器是什么 赵
  16. 【SQL面试】WHERE 1=1 到底是啥意思?
  17. Mulesoft自学教程(含文档,AnypointStudio开发工具资料)
  18. 跨考408计算机学科专业基础综合,考研北京航空航天大学计算机学科专业基础综合(408)重难点解析.doc...
  19. OpenPose(一):根据关键点生成置信图(Confidence Map)
  20. 可视化大屏设计_最高效的大屏展示不只是酷炫

热门文章

  1. 2020-11-3(安卓开发入门)
  2. WEB基础与前端开发--课程表页面的设计
  3. 让2010成为我的新纪元
  4. 大型语言模型综述(二)
  5. 【PTA】到底是不是太胖了
  6. ECMAScript 2016(ES7) 的新特性总结
  7. 计算机数据采集处理系统使用方法,数据采集与处理系统的要求
  8. 兴趣专业测试软件,霍兰德职业兴趣测试 在线工具
  9. 【FFT】快速傅里叶变换
  10. Java 笔记-抽象类,接口