在 Spring 中正确注入 EntityManager
TL;DR: 保持默认设置,优先使用 @Autowired
,没有线程安全问题。
本文基于 Spring Boot 3.1.3,使用
io.spring.dependency-management
1.1.3 依赖管理。
注入注解之争
Spring 中最常用的注解恐怕非依赖注入 @Autowired
莫属了,默认情况下每个类 Spring 只会创建一个实例来节约资源,但若我们需要的是一个 EntityManager
对象,事情就稍微复杂了一点。即使在最简单的单个数据库场景下,也逃不过线程安全问题。根据 hibernate 的文档 EntityManager
默认不是线程安全的,因此许多视频教程与文本资料都说必须使用 @PersistenceContext
注入 EntityManager
从而每次获得不同的实例。
然而事实是大多数情况下 @PersistenceContext
不过是心里安慰罢了。我们习惯于把对象注入到成员变量,而「注入」这一行为只会发生一次。例如下面的代码:
@Service
public class UserService {
@PersistenceContext
private EntityManager em;
// ...
}
显然,整个程序中只会存在一个 UserService
实例,它的 em
也固定不变,而 UserService
很可能被多个线程使用,em
也就再次陷入危机。也有同学作了安慰用的封装,只要最终注入到了成员变量,大概率就不能解决这个问题。
Persistence Context
EntityManager
与 @Autowired
相比,@PersistenceContext
语义不是很清晰,在讨论解决办法之前我们先来搞清楚这个注解到底与 @Autowired
有什么区别。
@Autowired
大家应该很熟悉了,它的作用很直接:从 Spring IOC 容器中取出一个对象,默认 Spring 会保留实例,某次注入都是同一个对象。
@PersistenceContext
是 JPA (不是 Spring JPA)定义的注解,其名称来源于一个概念 「Persistence Context」。我们知道 JPA 强制使用一级缓存,主要表现在:
- 相同查询只执行一次。
- Entity 的修改删除不会实时提交到数据库。
这些缓存需要存储于某个地方,这就是 Persistence Context,它管理着所有的 Entity 实例。等等,这不就是 EntityManager
吗?!没错 EntityManager
是与 Persistence Context 交互的接口。更形象地说,Persistence Context 是设计/概念上的东西,EntityManager
是它的具体表现。注意不要搞混,EntityManager
是接口,它具象化了概念上定义的功能,但具体的代码逻辑还要看它的实现类。到此为止用 @PersistenceContext
注入 EntityManager
就合理了,毕竟他俩本来就是一个东西。
Container / Application Managed
EntityManager
作为数据库与应用之间的中间人,需要在适当的时候把更改同步到数据库、释放资源或执行其他操作,那谁来管理 EntityManager
呢?从宏观角度有两个选择:容器管理或应用管理。
容器指的是 JavaEE Container(最常见的是 Tomcat)。Spring 大大简化了 Java Web 开发,导致部分同学忽略了一些底层概念,实际上 Java Web 程序(包括 Spring Web)都是 Servlet。一个完整的 Web 服务器至少要实现下面两个功能:
- 监听端口,处理底层数据流,实现一些协议(例如 http)。
- 接收请求,处理业务逻辑,响应请求。
显然第一个功能非常复杂但也很通用,如果每一个程序都自己实现一遍就太麻烦了,所以由 Web Server 代劳。一个服务器上往往运行着多个业务的服务程序,把这些功能写在一个程序里肯定不是个好主意。于是 Sun 公司制定了 Servlet 规范,表现为 Java interface。任何实现了这个接口的程序都能被识别,拿到匹配的 Web 请求并响应。这样不同的业务就可以拆分到不同的 Servlet 程序里。现在有了一堆 Servlet,自然需要一个东西来管理它们,这就是容器 Container。
狭义上容器介于 Web Server 与 Servlet 之间,但往往容器本身也是一个 Web Server,尽管实际使用中我们倾向于外面套一个更专业的服务器,比如 Nginx。默认情况下容器采用单实例多线程的策略来管理 Servlet。即每个 Servlet 程序只启动一次,每有一个新请求就从线程池取一个线程来调用 Servlet。
随着 Spring Boot 和微服务的流行,多 Servlet 架构逐渐被遗忘。更常见的做法是一个服务器程序是一个 Servlet 捆绑一个 Tomcat。不同业务的请求由上层网关(至少是 Nginx 这种专业的 Web Server)分发到不同的后端,可能是 Tomcat + Servlet 也可能是其他程序。
@PersistenceContext
获取到的 EntityManager
实例默认是由容器管理的。通常容器的策略是一个线程一个 EntityManager
,和管理请求的策略类似,所以等效为每个请求有自己的 EntityManager
,其生命周期在 Servlet 返回后结束。但注意,并不是所有容器都实现了此功能,开源的 Tomcat 就没有实现。
除非我们参与一些大型、专业、古老的企业项目,否则使用的是应用管理的 EntityManager
。「应用」指的是整个 Servlet 程序,既包括我们自己写的代码,也包括 Spring 提供的功能。
Spring Data JPA 的魔法
@Autowired
注入了啥
@Autowired
注解由 Spring 核心的 AutowiredAnnotationBeanPostProcessor
处理,注入的值来源于 BeanFacotry
,也就是俗称的 Spring IoC 容器。它遵循先类型后名称的匹配顺序,所以要想知道注入的 EntityManager
到底是啥,就得看看容器中注册了啥。
默认情况
Spring Data JPA 的自动配置类 JpaRepositoriesAutoConfiguration
与手动加上注解 @EnableJpaRepositories
等效,它们都直接或间接导入(@Import
)了 JpaRepositoriesRegistrar
,它的父类 AbstractRepositoryConfigurationSourceSupport
实现了接口 ImportBeanDefinitionRegistrar
,顾名思义其中一个功能就是注册 BeanDefinition,具体逻辑代理给了 RepositoryConfigurationDelegate
并传入一个 RepositoryConfigurationExtension
参数,如下:
// AbstractRepositoryConfigurationSourceSupport
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(
getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment);
delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension());
}
这给 delegate 内部调用了 RepositoryConfigurationExtension.registerBeansForRoot()
,看名字就非常可疑,我们关注一下这里的实现类 JpaRepositoryConfigExtension
。
// JpaRepositoryConfigExtension
@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource config) {
super.registerBeansForRoot(registry, config);
registerSharedEntityMangerIfNotAlreadyRegistered(registry, config);
Object source = config.getSource();
registerLazyIfNotAlreadyRegistered(
() -> new RootBeanDefinition(EntityManagerBeanDefinitionRegistrarPostProcessor.class), registry,
// emBeanDefinitionRegistrarPostProcessor
EM_BEAN_DEFINITION_REGISTRAR_POST_PROCESSOR_BEAN_NAME, source);
// ...
}
小声低估:EmtityManager 那么重要的东西竟然缩写成了
em
还是个纯大写的常量名,差点没看到。
可以看到它注册了 EntityManagerBeanDefinitionRegistrarPostProcessor
并手动指定 BeanName 为缩写的常量。这个名字又臭又长以至于我连复制粘贴都嫌麻烦的 PostProcessor 的注释倒是很清晰地说明了它两个主要功能:
- 为每一个
EntityManagerFactory
注册一个对应的SharedEntityManagerCreator
,这样就能在构造函数中使用自动注入EntityManager
了。 (@PersistenceContext
不支持构造函数注入) - 把
EntityManagerFactory
的名字添加为 qualifier 以便在有多个EntityManagerFactory
实例的情况下也能正确注入。
添加 BeanDefinition 的核心代码如下:
// EntityManagerBeanDefinitionRegistrarPostProcessor
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// ...
for (EntityManagerFactoryBeanDefinition definition : getEntityManagerFactoryBeanDefinitions(beanFactory)) {
// ...
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition("org.springframework.orm.jpa.SharedEntityManagerCreator");
builder.setFactoryMethod("createSharedEntityManager");
builder.addConstructorArgReference(definition.getBeanName());
// ...
}
}
顺藤摸瓜,终于找到了万恶之源 SharedEntityManagerCreator
,赶紧来看一下它的 createSharedEntityManage()
方法,层层重载方法的调用后,果不其然是一个动态代理:
// SharedEntityManagerCreator.createSharedEntityManager()
return (EntityManager) Proxy.newProxyInstance(
(cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),
ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
需要注意的是 SharedEntityManagerCreator
本身没有实现 EntityManagerFactory
,它仅仅在 Spring 容器中用来生成 EntityManager
。这意味着使用依赖注入取得 EntityManagerFactory
实例不可能得到 SharedEntityManagerCreator
。
至此可以得出结论,默认情况下 @Autowired
注入的 EntityManager
是由 SharedEntityManagerInvocationHandler
处理的单例动态代理对象。
自定义情况
不少网络上的博客与问答都提到自己注册一个 Bean:
@Bean
public EntityManager entityManager(EntityManagerFactory entityManagerFactory){
return entityManagerFactory.createEntityManager();
}
上文刚刚强调过 SharedEntityManagerCreator
不是 EntityManagerFactory
,事实上这里的参数传入的是原生实现(Hibernate 环境下是 SessionFactoryImpl
)的动态代理,由 AbstractEntityManagerFactoryBean.ManagedEntityManagerFactoryInvocationHandler
处理。它调用 ExtendedEntityManagerCreator.createApplicationManagedEntityManager()
创建了原生 EntityManager
的动态代理作为 createEntityManager()
方法的返回值。下面 @PersistenceContext
章节做了详细分析。
在自定义 Bean 的情况下,@Autowired
注入的 EntityManager
是由 ExtendedEntityManagerCreator.ExtendedEntityManagerInvocationHandler
处理的单例动态代理对象。
@PersistenceContext
注入了啥
注解处理
上文提到 @PersistenceContext
是 Sun 制定的标准,用于从 Servlet Container 中取得实例,通常与线程绑定。但常见的 Servlet Container (tomcat) 并未实现管理 EntityManager
的功能,为什么 @PersistenceContext
依然可以正常工作呢?因为 Spring 也处理了此注解。
spring-orm
模块中有一个默认注册的类 org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
,它接管了 Spring 环境中 @PersistenceContext
注解的处理。默认从 Spring IoC 容器中取得 EntityManagerFactory
,经过配置也能从 JNDI 中获取来生成 EntityManager
。
我们关注它的核心方法 postProcessProperties()
:
// PersistenceAnnotationBeanPostProcessor
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findPersistenceMetadata(beanName, bean.getClass(), pvs);
metadata.inject(bean, beanName, pvs);
// exception handle...
return pvs;
}
并不复杂,注入的详情封装在 metadata 里了,那就看看怎么找到这个元数据的:
// PersistenceAnnotationBeanPostProcessor
private InjectionMetadata findPersistenceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// cache...
metadata = buildPersistenceMetadata(clazz);
return metadata;
}
原来是在缓存中找啊... 省略掉与缓存有关的代码后找到了真正的构建方法:
// PersistenceAnnotationBeanPostProcessor
private InjectionMetadata buildPersistenceMetadata(Class<?> clazz) {
// ...
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 处理注解字段字段
ReflectionUtils.doWithLocalFields(targetClass, field -> {
if (field.isAnnotationPresent(PersistenceContext.class) ||
field.isAnnotationPresent(PersistenceUnit.class)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("Persistence annotations are not supported on static fields");
}
currElements.add(new PersistenceElement(field, field, null));
}
});
// 处理注解的方法
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
// Omit some checks like above
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new PersistenceElement(method, bridgedMethod, pd));
});
elements.addAll(0, currElements);
// 继续处理父类
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return InjectionMetadata.forElements(elements, clazz);
}
代码比较长,其实逻辑不复杂:
- 外层
do...while
循环是为了处理父类。 - 内层两个循环分别是寻找带有
@PersistenceContext
或@PersistenceUnit
注解的字段与方法。找到后创建一个PersistenceElement
加入待处理列表。
实际执行注入的是 InjectionMetadata.InjectedElement.inject()
,也就是 PersistenceElement
的父类。其调用了 getResourceToInject()
获取要注入的值,我们看看子类的重写:
// PersistenceAnnotationBeanPostProcessor - PersistenceElement
@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
// Resolves to EntityManagerFactory or EntityManager.
if (this.type != null) {
return (this.type == PersistenceContextType.EXTENDED ?
resolveExtendedEntityManager(target, requestingBeanName) :
resolveEntityManager(requestingBeanName));
} else {
// OK, so we need an EntityManagerFactory...
return resolveEntityManagerFactory(requestingBeanName);
}
}
可以看到根据类型的不同,注入的值有三种可能性:
PersistenceContextType | 注入的值 | |
---|---|---|
TRANSACTION(默认值) | EntityManager | |
EXTENDED | ExtendedEntityManager | |
NULL (@PersistenceUnit ) |
EntityManagerFactory |
继续看一下各自的方法。(@PersistenceUnit
不是本文的重点先忽略)
TRANSACTION 对应的 resolveEntityManager()
不复杂,这里不放源码了,逻辑如下:
graph TB
em[Get em from JNDI]
emf1[Get em factory from JNDI]
emf2[Get em factory from Spring]
wrap[SharedEntityManagerCreator 创建代理 em]
r(Return)
em--OK-->r
em--NULL-->emf1
emf1--OK-->wrap
emf1--NULL-->emf2
emf2-->wrap
wrap-->r
因为默认未配置 JNDI 相关参数所以全是 NULL,最终从 Spring 容器中获取 EntityManagerFactory
,之后就和 @Autowired
的默认情况差不多,注入一个 Spring 代理的 EntityManager
。
EXTENDED 对应的是 resolveExtendedEntityManager()
,基本逻辑和 TRANSCATION 版本的差不多,但调用的是 ExtendedEntityManagerCreator
来创建 EntityManager
的动态代理。
EntityManagerFactory 来源
既然 Transaction 与 Extended 两种模式都用到了 Spring 容器中的 EntityManagerFactory
,那它是哪里来的呢?
自动配置类 JpaBaseConfiguration
中注册了 LocalContainerEntityManagerFactoryBean
:
// org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration
@Bean
@Primary
@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder,
PersistenceManagedTypes persistenceManagedTypes)
FactoryBean
是 Spring 中一个特殊的 Bean,它本身不被直接获取,而是通过实现 getObject()
方法生产对象供使用。这里的实现在父类 AbstractEntityManagerFactoryBean
中:
// AbstractEntityManagerFactoryBean
@Override
public Class<? extends EntityManagerFactory> getObjectType() {
return (this.entityManagerFactory != null ? this.entityManagerFactory.getClass() : EntityManagerFactory.class);
}
@Override
@Nullable
public EntityManagerFactory getObject() {
return this.entityManagerFactory;
}
可以看到它是生产工厂(EntityManagerFactory
)的工厂 ,仅仅返回一个成员变量。这个变量在 afterPropertiesSet()
中被赋值,这是 InitializingBean
接口的方法,用于 Bean 的初始化。当 Spring 设置好 Bean 的所有属性后,若其实现了这个接口则自动调用此方法。
// AbstractEntityManagerFactoryBean
@Override
public void afterPropertiesSet() throws PersistenceException {
// ...
AsyncTaskExecutor bootstrapExecutor = getBootstrapExecutor();
if (bootstrapExecutor != null) {
this.nativeEntityManagerFactoryFuture = bootstrapExecutor.submit(this::buildNativeEntityManagerFactory);
} else {
// buildNativeEntityManagerFactory() 内部调用了抽象函数 createNativeEntityManagerFactory()
this.nativeEntityManagerFactory = buildNativeEntityManagerFactory();
}
this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
}
抽象函数 createNativeEntityManagerFactory()
取得了原生的 EntityManagerFactory
对象保存在成员变量 nativeEntityManagerFactory
中,大部分情况下应该是 Hibernate 的 SessionFactoryImpl
。
createEntityManagerFactoryProxy()
顾名思义创建了一个原生工厂的的动态代理类,大部分的方法调用转给了 invokeProxyMethod()
处理。这个代理的主要目的是拦截 createEntityManager()
方法,从而返回一个由 Spring 管理的、线程安全的套娃 EntityManager
,如下:
// AbstractEntityManagerFactoryBean
Object invokeProxyMethod(Method method, @Nullable Object[] args) throws Throwable {
if (method.getDeclaringClass().isAssignableFrom(EntityManagerFactoryInfo.class)) {
return method.invoke(this, args);
}
else if (method.getName().equals("createEntityManager") && args != null && args.length > 0 &&
args[0] == SynchronizationType.SYNCHRONIZED) {
EntityManager rawEntityManager = (args.length > 1 ?
getNativeEntityManagerFactory().createEntityManager((Map<?, ?>) args[1]) :
getNativeEntityManagerFactory().createEntityManager());
postProcessEntityManager(rawEntityManager);
// 返回了套娃的 EntityManager
return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true);
}
// ...
Object retVal = method.invoke(getNativeEntityManagerFactory(), args);
if (retVal instanceof EntityManager rawEntityManager) {
// Any other createEntityManager variant - expecting non-synchronized semantics
postProcessEntityManager(rawEntityManager);
// 还是套娃的 EntityManager
retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, false);
}
return retVal;
}
虽然根据参数的不同具体行为略有区别,但本质都是通过 getNativeEntityManagerFactory().createEntityManager()
拿到原生的 EntityManager
,用 ExtendedEntityManagerCreator.createApplicationManagedEntityManager()
创建动态代理套娃一层返回。
小结
@Autowired
注入的 EntityManager
分为两种情况:
- 若采用默认配置则是
SharedEntityManagerCreator
搞出的动态代理。 - 若自己注册了 Bean 则且 Name 匹配则以设置的为准。具体来说因为太多同学人云亦云用容器中的
EntityManagerFactory
注册了EntityManager
的 bean,所以常见结果是注入了ExtendedEntityManagerCreator
搞出的动态代理。
虽然两种情况最终都是 Spring 创建的动态代理,但它们的代理逻辑不一样,不可以混为一谈。
Spring 内部处理了 @PersistenceContext
注解,因此即使 JavaEE Container 不支持也能正常得到 EntityManager
。 根据 type
参数的不同,注入 SharedEntityManagerCreator
或 ExtendedEntityManagerCreator
创建出的代理对象。
Spring 容器中的 EntityManagerFactory
是原生工厂的代理对象,它所生产的 EntityManager
也是原生版本的代理对象,由 ExtendedEntityManagerCreator
创建。
两种动态代理区别何在
分析到这,我们知道无论是 @Autowired
还是 @PersistenceContext
,典型场景下只可能得到两种对象:
SharedEntityManagerCreator
创建的动态代理(事务型 / transaction scope)ExtendedEntityManagerCreator
创建的动态代理(扩展型 / extended scope)
于是它俩的区别成为如何选用注解的核心问题。
事务型代理背后可能有多个 EntityManager
,代理的方法执行时从 TransactionSynchronizationManager
中获取。这可以保证同一个事务中的 EntityManager 共享同一个 Persistence Context,避免两个 @Transactional
方法嵌套调用,后者读不到前者修改的诡异问题。
扩展型代理在创建时就固定了背后的 EntityManager
,代理的方法执行时取得当前线程已经启动的事务并加入。但这不能保证它与已经启动事务的其他 EntityManager
共享 Persistence Context。在事务提交前,不同的 EntityManager 可能无法看见对方的修改。
在实际开发中,事务方法嵌套调用,且后者依赖前者更改的情况并不罕见。例如用户注册后可能调用其他可复用的方法初始化一些属性,如果此时使用扩展型代理可能修改时发现记录不存在。
总结
在不启用 JavaEE 那一套玩意,且保持默认设置的前提下,@Autowired
与 @PersistenceContext
没有什么区别,但前者支持构造器注入,因此更推荐使用。后者提供更多选项,例如可以显式设置需要 Extended Scope 的 EntityManager,在需要时可以使用,但应该不常用。
在绝大部分情况下都不要自作主张地手动注册 EntityManager
的 Bean,否则将导致意外地注入成扩展型的代理,进而出现非预期的变更不可见。
无论哪种注解,都无需担心线程安全问题,因为它们背后都是 Spring 的代理类,都是线程安全的。