11.2.1 配置实体管理器工厂

简单来讲,基于 JPA 的应用程序需要使用 EntityManagerFactory 的实现类来获取 EntityManager 实例。JPA 定义了两种类型的实体管理器:

  • 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在 Java EE 容器中的独立应用程序。

  • 容器管理类型(Container-managed):实体管理器由 Java EE 创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或 JNDI 来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适用于 Java EE 容器,在这种情况下会希望在 persistence.xml 指定的 JPA 配置之外保持一些自己对 JPA 的控制。

以上的两种实体管理器实现了同一个 EntityManager 接口。关键的区别不在于 EntityManager 本身,而是在于 EntityManager 的创建和管理方式。应用程序管理类型的 EntityManager 是由 EntityManagerFactory 创建的,而后者是通过 PersistenceProvider 的 createEntityManagerFactory() 方法得到的。与此相对,容器管理类型的 EntityManagerFactory 是通过 PersistenceProvider 的 createContainerEntityManagerFactory() 方法获得的。

这对想使用 JPA 的 Spring 开发者来说又意味着什么呢?其实这并没太大的关系。不管你希望使用哪种 EntityManagerFactory,Spring 都会负责管理 EntityManager。如果你使用的是应用程序管理类型的实体管理器,Spring 承担了应用程序的角色并以透明的方式处理 EntityManager。在容器管理的场景下,Spring 会担当容器的角色。

这两种实体管理器工厂分别由对应的 Spring 工厂 Bean 创建:

  • LocalEntityManagerFactoryBean 生成应用程序管理类型的 EntityManagerFactory;

  • LocalContainerEntityManagerFactoryBean 生成容器管理类型的 EntityManagerFactory。

需要说明的是,选择应用程序管理类型的还是容器管理类型的 EntityManager-Factory,对于基于 Spring 的应用程序来讲是完全透明的。当组合使用 Spring 和 JPA 时,处理 EntityManagerFactory 的复杂细节被隐藏了起来,数据访问代码只需关注它们的真正目标即可,也就是数据访问。

应用程序管理类型和容器管理类型的实体管理器工厂之间唯一值得关注的区别是在 Spring 应用上下文中如何进行配置。让我们先看看如何在 Spring 中配置应用程序管理类型的 LocalEntityManagerFactoryBean,然后再看看如何配置容器管理类型的 LocalContainerEntityManagerFactoryBean。

配置应用程序管理类型的 JPA

对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置信息来源于一个名为 persistence.xml 的配置文件。这个文件必须位于类路径下的 META-INF 目录下。

persistence.xml 的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。简单来讲,persistence.xml 列出了一个或多个的持久化类以及一些其他的配置如数据源和基于 XML 的配置文件。如下是一个典型的 persistence.xml 文件,它是用于 Spittr 应用程序的:

<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence">
  <persistence-unit name="spitterPU">
    <class>com.habuma.spittr.domain.Spitter</class>
    <class>com.habuma.spittr.domain.Spittle</class>
    <properties>
      <property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver" />
      <property name="toplink.jdbc.url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter" />
      <property name="toplink.jdbc.user" value="sa" />
      <property name="toplink.jdbc.password" value="" />
    </properties>
  </persistence-unit>
</persistence>

因为在 persistence.xml 文件中包含了大量的配置信息,所以在 Spring 中需要配置的就很少了。可以通过以下的 @Bean 注解方法在 Spring 中声明 LocalEntityManagerFactoryBean:

@Bean
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
  LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean();
  emfb.setPersistenceUnitName("spitterPU");
  return emfb;
}

赋给 persistenceUnitName 属性的值就是 persistence.xml 中持久化单元的名称。

创建应用程序管理类型的 EntityManagerFactory 都是在 persistence.xml 中进行的,而这正是应用程序管理的本意。在应用程序管理的场景下(不考虑 Spring 时),完全由应用程序本身来负责获取 EntityManagerFactory,这是通过 JPA 实现的 PersistenceProvider 做到的。如果每次请求 EntityManagerFactory 时都需要定义持久化单元,那代码将会迅速膨胀。通过将其配置在 persistence.xml 中,JPA 就能够在这个特定的位置查找持久化单元定义了。

但借助于 Spring 对 JPA 的支持,我们不再需要直接处理 PersistenceProvider 了。因此,再将配置信息放在 persistence.xml 中就显得不那么明智了。实际上,这样做妨碍了我们在 Spring 中配置 EntityManagerFactory(如果不是这样的话,我们可以提供一个 Spring 配置的数据源)。

鉴于以上的原因,让我们关注一下容器管理的 JPA:

使用容器管理类型的 JPA

容器管理的 JPA 采取了一个不同的方式。当运行在容器中时,可以使用容器(在我们的场景下是 Spring)提供的信息来生成 EntityManagerFactory。

你可以将数据源信息配置在 Spring 应用上下文中,而不是在 persistence.xml 中了。例如,如下的 @Bean 注解方法声明了在 Spring 中如何使用 LocalContainer-EntityManagerFactoryBean 来配置容器管理类型的 JPA:

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
    DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
  LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
  emfb.setDataSource(dataSource);
  emfb.setJpaVendorAdapter(jpaVendorAdapter);
  return emfb;
}

这里,我们使用了 Spring 配置的数据源来设置 dataSource 属性。任何 javax.sql.DataSource 的实现都是可以的。尽管数据源还可以在 persistence.xml 中进行配置,但是这个属性指定的数据源具有更高的优先级。

jpaVendorAdapter 属性用于指明所使用的是哪一个厂商的 JPA 实现。Spring 提供了多个 JPA 厂商适配器:

  • EclipseLinkJpaVendorAdapter

  • HibernateJpaVendorAdapter

  • OpenJpaVendorAdapter

  • TopLinkJpaVendorAdapter(在Spring 3.1版本中,已经将其废弃了)

在本例中,我们使用 Hibernate 作为 JPA 实现,所以将其配置为 Hibernate-JpaVendorAdapter:

@Bean
public JpaVendorAdapter jpaVendorAdapter() {
  HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
  adapter.setDatabase("HSQL");
  adapter.setShowSql(true);
  adapter.setGenerateDdl(false);
  adapter.setDatabasePlatform("org.hibernet.dialect.HSQLDialect");
  return adapter;
}

有多个属性需要设置到厂商适配器上,但是最重要的是 database 属性,在上面我们设置了要使用的数据库是 Hypersonic。这个属性支持的其他值如表 11.1 所示。

数据库平台

属性 database 的值

IBM DB2

DB2

Apache Derby

DERBY

H2

H2

Hypersonic

HSQL

Informix

INFORMIX

MySQL

MYSQL

Oracle

ORACLE

PostgresQL

POSTGRESQL

Microsoft SQL Server

SQLSERVER

Sybase

SYBASE

一些特定的动态持久化功能需要对持久化类按照指令(instrumentation)进行修改才能支持。在属性延迟加载(只在它们被实际访问时才从数据库中获取)的对象中,必须要包含知道如何查询未加载数据的代码。一些框架使用动态代理实现延迟加载,而有一些框架像 JDO,则是在编译时执行类指令。

选择哪一种实体管理器工厂主要取决于如何使用它。但是,下面的小技巧可能会让你更加倾向于使用 LocalContainerEntityManagerFactoryBean。

persistence.xml 文件的主要作用就在于识别持久化单元中的实体类。但是从 Spring 3.1 开始,我们能够在 LocalContainerEntityManagerFactoryBean 中直接设置 packagesToScan 属性:

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
    DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
  LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
  emfb.setDataSource(dataSource);
  emfb.setJpaVendorAdapter(jpaVendorAdapter);
  emfb.setPackagesToScan("com.habuma.spittr.domain");
  return emfb;
}

在这个配置中,LocalContainerEntityManagerFactoryBean 会扫描 com.habuma .spittr.domain 包,查找带有 @Entity 注解的类。因此,没有必要在 persistence.xml 文件中进行声明了。同时,因为 DataSource 也是注入到 LocalContainerEntity-ManagerFactoryBean 中的,所以也没有必要在 persistence.xml 文件中配置数据库信息了。那么结论就是,persistence.xml 文件完全没有必要存在了!你尽可以将其删除,让 LocalContainerEntityManagerFactoryBean 来处理这些事情。

从 JNDI 获取实体管理器工厂

还有一件需要注意的事项,如果将 Spring 应用程序部署在应用服务器中,EntityManagerFactory 可能已经创建好了并且位于 JNDI 中等待查询使用。在这种情况下,可以使用 Spring jee 命名空间下的 <jee:jndi-lookup> 元素来获取对 EntityManagerFactory 的引用:

<jee:jndi-lookup id="emf" jndi-name="persistence/spitterPU" />

我们也可以使用如下的 Java 配置来获取 EntityManagerFactory:

@Bean
public JndiObjectFactoryBean entityManagerFactory() {
  JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
  jndiObjectFB.setJndiName("jdbc/SpittrDS");
  return jndiObjectFB;
}

尽管这种方法没有返回 EntityManagerFactory,但是它的结果就是一个 EntityManagerFactoryBean。这是因为它所返回的 JndiObjectFactoryBean 是 FactoryBean 接口的实现,它能够创建 EntityManagerFactory。

不管你采用何种方式得到 EntityManagerFactory,一旦得到这样的对象,接下来就可以编写 Repository 了。让我们开始吧。

Last updated