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
Class> targetClass = clazz;
do {
final List
// 处理注解字段字段
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 的代理类,都是线程安全的。
参考
SpringDataJPA+Hibernate框架源码剖析(六)