本文基于 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 服务器至少要实现下面两个功能:

  1. 监听端口,处理底层数据流,实现一些协议(例如 http)。
  2. 接收请求,处理业务逻辑,响应请求。

显然第一个功能非常复杂但也很通用,如果每一个程序都自己实现一遍就太麻烦了,所以由 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
EXTENDEDExtendedEntityManager
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 参数的不同,注入 SharedEntityManagerCreatorExtendedEntityManagerCreator 创建出的代理对象。

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 的代理类,都是线程安全的。

参考

Last modification:October 14, 2023