20220507 6. Data Access - Object Relational Mapping (ORM) Data Access

前言

文档地址

相关依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Spring 对 ORM 的支持

Spring Framework 支持与 Java Persistence API (JPA) 的集成,并支持用于资源管理、数据访问对象 (DAO) 实现和事务策略的原生 Hibernate 。例如,对于 Hibernate ,有几个方便的 IoC 特性,可以解决许多典型的 Hibernate 集成问题。您可以通过依赖注入为 OR(对象关系)映射工具配置所有支持的功能。它们可以参与 Spring 的资源和事务管理,并且遵守 Spring 的通用事务和 DAO 异常层次结构。推荐的集成风格是针对普通 Hibernate 或 JPA API 对 DAO 进行编码。

当您创建数据访问应用程序时,Spring 为您选择的 ORM 层添加了重要的增强功能。您可以根据需要利用尽可能多的集成支持,并且应该将这种集成工作与在内部构建类似基础架构的成本和风险进行比较。无论采用何种技术,您都可以像使用库一样使用许多 ORM 支持,因为一切都被设计为一组可重用的 JavaBean。Spring IoC 容器中的 ORM 有助于配置和部署。因此,本节中的大多数示例都显示了 Spring 容器内的配置。

使用 Spring Framework 创建 ORM DAO 的好处包括:

  • 更容易测试。Spring 的 IoC 方式可以轻松更换 Hibernate SessionFactory 实例、JDBC DataSource 实例、事务管理器和映射对象实现的实现和配置位置。这反过来又使单独测试每段与持久层相关的代码变得更加容易。
  • 常见的数据访问异常。Spring 可以包装来自 ORM 工具的异常,将它们从专有异常(可能是检查异常)转换为公共运行时异常 DataAccessException 层次结构。此功能可让您仅在适当的层中处理大多数不可恢复的持久层异常,而无需烦人的样板捕获、抛出和异常声明。您仍然可以根据需要捕获和处理异常。请记住,JDBC 异常(包括特定于 DB 的方言)也转换为相同的层次结构,这意味着您可以在一致的编程模型中使用 JDBC 执行某些操作。
  • 通用资源管理。Spring 应用上下文可以处理 Hibernate SessionFactory 实例、JPA EntityManagerFactory 实例、JDBC DataSource 实例和其他相关资源的位置和配置。这使得这些值易于管理和更改。Spring 提供了对持久层资源的高效、简单和安全的处理。例如,使用 Hibernate 的相关代码一般都需要使用同一个 Hibernate Session 来保证效率和正确的事务处理。Spring 通过 Hibernate SessionFactory 公开当前 Session ,可以轻松地创建 Session 并将其透明地绑定到当前线程。因此,对于任何本地或 JTA 事务环境,Spring 解决了许多典型的 Hibernate 使用的长期问题。
  • 集成事务管理。您可以通过 @Transactional 注解或通过在 XML 配置文件中显式配置事务 AOP 通知,使用声明性的、面向切面的编程 (AOP) 样式的方法拦截器包装您的 ORM 代码 。在这两种情况下,事务语义和异常处理(回滚等)都会为您处理。如资源和事务管理中所述,你还可以切换各种事务管理器,而不会影响你的 ORM 相关代码。例如,您可以在本地事务和 JTA 之间交换,在这两种情况下都可以使用相同的完整服务(例如声明性事务)。此外,与 JDBC 相关的代码可以与用于执行 ORM 的代码在事务上完全集成。这对于不适合 ORM(例如批处理和 BLOB 流)但仍需要与 ORM 操作共享公共事务的数据访问很有用。

如需更全面的 ORM 支持,包括对 MongoDB 等替代数据库技术的支持,您可能需要查看 Spring Data 项目套件。如果你是一个 JPA 用户,可以参考 JPA 入门访问数据

ORM 集成的注意事项

Spring 的 ORM 集成的主要目标是清晰的应用程序分层(使用任何数据访问和事务技术)和应用程序对象的松散耦合。目标是采用一种简单且一致的方法来连接应用程序对象,尽可能保持它们的可重用性和不受容器依赖性的影响。所有单独的数据访问功能都可用,但与 Spring 的应用上下文概念很好地集成在一起,提供基于 XML 的配置和不需要 Spring 支持的普通 JavaBean 实例的交叉引用。

在典型的 Spring 应用程序中,许多重要的对象都是 JavaBeans :数据访问模板、数据访问对象、事务管理器、使用数据访问对象和事务管理器的业务服务、web 视图解析器、使用业务服务的 web 控制器,等等。

资源和事务管理

Spring 提倡为适当的资源处理提供简单的解决方案,即在 JDBC 中通过 IoC ,并为 ORM 技术应用 AOP 拦截器。

基础设施提供适当的资源处理和特定 API 异常到非检查异常层次结构的适当转换。Spring 引入了 DAO 异常层次结构,适用于任何数据访问策略。对于直接 JDBC ,JdbcTemplate 类提供连接处理和从 SQLExceptionDataAccessException 层次结构的正确转换,包括将特定于数据库的 SQL 错误代码转换为有意义的异常类。

在事务管理方面,JdbcTemplate 类通过各自的 Spring 事务管理器与 Spring 事务支持挂钩,并支持 JTA 和 JDBC 事务。对于支持的 ORM 技术,Spring 通过 Hibernate 和 JPA 事务管理器以及 JTA 支持提供 Hibernate 和 JPA 支持。

异常转换

当您在 DAO 中使用 Hibernate 或 JPA 时,您必须决定如何处理持久层技术的原生异常类。DAO 抛出 HibernateExceptionPersistenceException 的子类。这些异常都是运行时异常,不必声明或捕获。您可能还需要处理 IllegalArgumentExceptionIllegalStateException 。 这意味着调用者只能将异常视为是致命的,除非他们想依赖持久层技术自身的异常结构。如果不将调用者与实现策略联系起来,就不可能捕获特定原因(例如乐观锁定失败)。对于基于 ORM 或不需要任何特殊异常处理的应用程序,这种权衡可能是可以接受的。但是,Spring 允许通过 @Repository 注解透明地应用异常转换。以下示例(一个用于 Java 配置,一个用于 XML 配置)显示了如何执行此操作:

@Repository
public class ProductDaoImpl implements ProductDao {

    // class body here...

}
<beans>

    <!-- Exception translation bean post processor -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

后置处理器会自动查找所有异常转换器( PersistenceExceptionTranslator 接口的实现 )并增强所有用 @Repository 注解标记的 bean, 以便发现的转换器可以拦截并应用适当的转换来处理抛出的异常。

总之,您可以基于普通持久层技术的 API 和注解来实现 DAO,同时仍然受益于 Spring 管理的事务、依赖注入和透明的异常转换到 Spring 的自定义异常层次结构。

Hibernate

从 Spring Framework 5.3 开始,Spring 需要 Hibernate ORM 5.2+ 才能用于 Spring HibernateJpaVendorAdapter 和原生 Hibernate SessionFactory 设置。对于新应用,强烈建议使用 Hibernate ORM 5.4 。要与 HibernateJpaVendorAdapter 一起使用,Hibernate Search 需要升级到 5.11.6 。

在 Spring 容器中设置 SessionFactory

为了避免将应用程序对象绑定到硬编码的资源查找,您可以将资源(例如 JDBC DataSource 或 Hibernate SessionFactory )定义为 Spring 容器中的 bean。需要访问资源的应用程序对象通过 bean 引用接收对此类预定义实例的引用。

以下 XML 应用上下文定义显示了如何在其上设置 JDBC DataSource 和 Hibernate SessionFactory

<beans>

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            </value>
        </property>
    </bean>

</beans>

从本地 Jakarta Commons DBCP BasicDataSource 切换到 JNDI 的 DataSource(通常由应用服务器管理)只是配置问题,如以下示例所示:

<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

您还可以访问基于 JNDI 的 SessionFactory ,使用 Spring 的 JndiObjectFactoryBean <jee:jndi-lookup> 来检索和公开它。但是,这在 EJB 上下文之外通常并不常见。

Spring 还提供了一个 LocalSessionFactoryBuilder 变体,与 @Bean 风格配置和编程设置(不涉及 FactoryBean )无缝集成。

LocalSessionFactoryBeanLocalSessionFactoryBuilder 支持后台引导,Hibernate 初始化与给定引导执行器(例如 SimpleAsyncTaskExecutor )上的应用程序引导线程并行运行。在 LocalSessionFactoryBean 里,这可以通过 bootstrapExecutor 属性获得。在 LocalSessionFactoryBuilder 上,有一个采用引导执行程序参数的重载 buildSessionFactory 方法。

从 Spring Framework 5.1 开始,这样的原生 Hibernate 设置还可以公开用于标准 JPA 交互的 JPA EntityManagerFactory 。有关详细信息,请参阅 JPA 的原生 Hibernate 设置

基于普通 Hibernate API 实现 DAO

Hibernate 有一项称为上下文会话的功能,其中 Hibernate 为每个事务管理一个 Session 。这大致相当于 Spring 为每个事务同步一个 Hibernate Session 。相应的 DAO 实现类似于以下示例,基于普通的 Hibernate API:

public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}

这种风格类似于 Hibernate 参考文档和示例的风格,不同之处在于将 SessionFactory 保存在实例变量中。我们强烈推荐这种基于实例的设置,而不是来自 Hibernate 的 CaveatEmptor 示例应用程序的老式 static HibernateUtil 类。(一般来说,除非绝对必要,否则不要将任何资源保存在 static 变量中)

前面的 DAO 示例遵循依赖注入模式。它非常适合 Spring IoC 容器,就像针对 Spring 的 HibernateTemplate 。 您还可以在纯 Java 中设置这样的 DAO(例如,在单元测试中)。为此,请将其实例化并使用所需的工厂引用进行调用 setSessionFactory(..) 。作为 Spring bean 定义,DAO 将类似于以下内容:

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

</beans>

这种 DAO 风格的主要优点是它只依赖于 Hibernate API 。不需要引入任何 Spring 类。从非侵入性的角度来看,这很有吸引力。

然而,DAO 抛出普通的 HibernateException(这是非检查的,所以它不会被声明或捕获),这意味着调用者只能将异常视为致命的——除非他们想依赖 Hibernate 自身的异常层次结构。如果不将调用者与实现策略联系起来,就不可能捕获特定原因(例如乐观锁定失败)。对于基于 Hibernate 、不需要任何特殊异常处理的应用程序,这是可以接受的。

幸运的是,Spring LocalSessionFactoryBean 支持 Hibernate 的任何 Spring 事务策略的 SessionFactory.getCurrentSession() 方法,返回当前 Spring 管理的事务性 Session ,即使是使用 HibernateTransactionManager 。该方法的标准行为仍然是返回与正在进行的 JTA 事务相关联的 Session 。无论您使用 Spring 的 JtaTransactionManager 、EJB 容器管理事务 (CMT)还是 JTA,此行为都适用 。

总之,您可以基于普通的 Hibernate API 实现 DAO,同时仍然能够参与 Spring 管理的事务。

声明式事务划分

我们建议您使用 Spring 的声明式事务支持,它允许您使用 AOP 事务拦截器替换 Java 代码中的显式事务划分 API 调用。您可以使用 Java 注解或 XML 在 Spring 容器中配置此事务拦截器。这种声明式事务功能使您可以使业务服务免于重复的事务划分代码,并专注于添加业务逻辑,这是应用程序的真正价值。

您可以使用 @Transactional 对服务层进行注解,并指示 Spring 容器找到这些注解并为注解的方法提供事务语义。以下示例显示了如何执行此操作:

public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    @Transactional
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }

    @Transactional(readOnly = true)
    public List<Product> findAllProducts() {
        return this.productDao.findAllProducts();
    }
}

在容器中,您需要设置 PlatformTransactionManager 实现(作为 bean)和 <tx:annotation-driven/> ,在运行时处理 @Transactional 。以下示例显示了如何执行此操作:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- SessionFactory, DataSource, etc. omitted -->

    <bean id="transactionManager"
            class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <tx:annotation-driven/>

    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>

程序化事务划分

您可以在跨越任意数量操作的较低级别数据访问服务之上,在更高级别的应用程序中划分事务。对周边业务服务的实现也不存在限制。它只需要一个 Spring PlatformTransactionManager 。同样,后者可以来自任何地方,但最好是通过 setTransactionManager(..) 方法作为 bean 引用。此外, 应该通过 setProductDao(..) 方法来设置 productDAO 。以下两个片段显示了 Spring 应用上下文中的事务管理器和业务服务定义以及业务方法实现的示例:

<beans>

    <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager" ref="myTxManager"/>
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private TransactionTemplate transactionTemplate;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                List productsToChange = this.productDao.loadProductsByCategory(category);
                // do the price increase...
            }
        });
    }
}

Spring TransactionInterceptor 允许使用回调代码抛出任何检查异常,而 TransactionTemplate 仅限于回调中的非检查异常。如果发生非检查异常或事务被应用程序标记为仅回滚(通过设置 TransactionStatus ),则 TransactionTemplate 触发回滚。默认情况下,TransactionInterceptor 行为方式相同,但允许为每个方法配置回滚策略。

事务管理策略

TransactionTemplateTransactionInterceptor 委托实际事务处理到 PlatformTransactionManager 实例(可以是 HibernateTransactionManager(针对单个 Hibernate SessionFactory )通过使用 ThreadLocal Session )或一个 JtaTransactionManager(委托给容器的 JTA 子系统),用于 Hibernate 应用程序。您甚至可以使用自定义 PlatformTransactionManager 实现。从原生 Hibernate 事务管理切换到 JTA 只是配置问题。您可以用 Spring 的 JTA 事务实现替换 Hibernate 事务管理器。事务划分和数据访问代码无需更改即可工作,因为它们使用通用事务管理 API。

对于跨多个 Hibernate 会话工厂的分布式事务,您可以将多个 LocalSessionFactoryBean 定义组合 JtaTransactionManager 为一个事务策略 。然后每个 DAO 将一个特定的 SessionFactory 引用传递到其相应的 bean 属性中。如果所有底层 JDBC 数据源都是事务容器数据源,则业务服务可以跨任意数量的 DAO 和任意数量的会话工厂划分事务,而无需特别考虑,只要它用 JtaTransactionManager 作策略即可。

HibernateTransactionManagerJtaTransactionManager 允许使用 Hibernate 进行适当的 JVM 级别的缓存处理,没有特定容器的事务管理器查找或 JCA 连接器(如果你不使用 EJB 发起的事务)。

HibernateTransactionManager 可以将 Hibernate JDBC Connection 导出为特定 DataSource 的普通 JDBC 访问代码。如果您只访问一个数据库,这种能力允许在完全没有 JTA 的情况下混合 Hibernate 和 JDBC 数据访问的高级事务划分。如果您通过 LocalSessionFactoryBean 类的 dataSource 属性设置了带有 DataSourceSessionFactory ,则 HibernateTransactionManager 会自动将 Hibernate 事务公开为 JDBC 事务。或者,您可以通过 HibernateTransactionManager 类的 dataSource 属性显式指定应该为其公开事务的 DataSource

比较容器管理和本地定义的资源

您可以在容器管理的 JNDI SessionFactory 和本地定义的之间切换,而无需更改一行应用程序代码。是将资源定义保留在容器中还是本地应用程序中,主要取决于您使用的事务策略。与 Spring 定义的本地 SessionFactory 相比 ,手动注册的 JNDI SessionFactory 没有任何好处。通过 Hibernate 的 JCA 连接器部署 SessionFactory 提供了参与 Java EE 服务器管理基础设施的附加价值,但不会增加除此之外的实际价值。

Spring 的事务支持不绑定到容器。当配置了 JTA 以外的任何策略时,事务支持也可以在独立或测试环境中工作。特别是在单库事务的典型情况下,Spring 的单资源本地事务支持是 JTA 的轻量级和强大的替代方案。当您使用本地 EJB 无状态会话 bean 来驱动事务时,您既依赖于 EJB 容器又依赖于 JTA,即使您只访问单个数据库并仅使用无状态会话 bean 通过容器管理的事务提供声明式事务。以编程方式直接使用 JTA 还需要 Java EE 环境。JTA 不仅仅涉及 JTA 本身和 JNDI DataSource 实例。对于非 Spring、JTA 驱动的 Hibernate 事务,您必须使用 Hibernate JCA 连接器或额外的 Hibernate 事务代码,并配置为适当的 JVM 级缓存的 TransactionManagerLookup

Spring 驱动的事务与本地定义的 Hibernate SessionFactory 一起工作, 就像它们与本地 JDBC DataSource 一起工作一样,前提是他们访问单个数据库。因此,只有在有分布式事务需求时才需要使用 Spring 的 JTA 事务策略。JCA 连接器首先需要特定于容器的部署步骤,以及 JCA 支持。与部署具有本地资源定义和 Spring 驱动事务的简单 Web 应用程序相比,此配置需要更多的工作。此外,如果您使用不提供 JCA 的 WebLogic Express,则通常需要容器的企业版。具有跨单个数据库的本地资源和事务的 Spring 应用程序可以在任何 Java EE Web 容器(没有 JTA、JCA 或 EJB)中工作,例如 Tomcat、Resin 甚至普通的 Jetty。此外,您可以轻松地在桌面应用程序或测试套件中重用这样的中间层。

考虑到所有因素,如果您不使用 EJB ,请坚持使用本地 SessionFactory 设置和 Spring 的 HibernateTransactionManagerJtaTransactionManager 。您可以获得所有好处,包括适当的事务性 JVM 级缓存和分布式事务,而不必担心容器部署带来的不便。只有在与 EJB 结合使用时,通过 JCA 连接器对 Hibernate SessionFactory 的 JNDI 注册才会增加价值。

使用 Hibernate 时的虚假应用服务器警告

在某些具有非常严格 XADataSource 实现的 JTA 环境(当前是某些 WebLogic Server 和 WebSphere 版本)中,当在不考虑该环境的 JTA 事务管理器的情况下配置 Hibernate 时,虚假警告或异常可能会显示在应用程序服务器日志中。这些警告或异常表明正在访问的连接不再有效或 JDBC 访问不再有效,可能是因为事务不再处于活动状态。例如,以下是 WebLogic 的实际异常:

java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.

另一个常见问题是 JTA 事务后的连接泄漏,Hibernate 会话(以及潜在的底层 JDBC 连接)没有正确关闭。

您可以通过让 Hibernate 感知 JTA 事务管理器来解决这些问题(与 Spring 一起)。为此,您有两种选择:

  • 将您的 Spring JtaTransactionManager bean 传递给您的 Hibernate 设置。最简单的方法是对 LocalSessionFactoryBean bean 的 jtaTransactionManager 属性进行 bean 引用(请参阅 Hibernate Transaction Setup )。然后 Spring 将相应的 JTA 策略提供给 Hibernate
  • 您还可以显式配置 Hibernate 的 JTA 相关属性,特别是 hibernate.transaction.coordinator_classhibernate.connection.handling_mode 和可能在 LocalSessionFactoryBeanhibernateProperties 属性中的 hibernate.transaction.jta.platform (参见 Hibernate 手册)

本节的其余部分描述了在 Hibernate 感知和不感知 JTA PlatformTransactionManager 的情况下发生的事件序列。

当 Hibernate 没有配置任何 JTA 事务管理器的感知时,JTA 事务提交时会发生以下事件:

  • JTA 事务提交
  • Spring 的 JtaTransactionManager 与 JTA 事务同步,因此它通过 JTA 事务管理器的 afterCompletion 被回调
  • 在其他活动中,这种同步可以触发 Spring 对 Hibernate 的回调,通过 Hibernate 的 afterTransactionCompletion 回调(用于清除 Hibernate 缓存),然后是对 Hibernate 会话的显式 close() 调用,这会导致 Hibernate 尝试 close() JDBC 连接
  • 在某些环境中,Connection.close() 调用会触发警告或错误,因为应用程序服务器不再认为 Connection 可用,因为事务已提交

当 Hibernate 配置了 JTA 事务管理器的感知时,JTA 事务提交时会发生以下事件:

  • JTA 事务已准备好提交
  • Spring 的 JtaTransactionManager 与 JTA 事务同步,因此事务由 JTA 事务管理器通过 beforeCompletion 回调回调
  • Spring 感知 Hibernate 本身与 JTA 事务同步,并且其行为与前一个场景不同。特别是,它与 Hibernate 的事务资源管理保持一致
  • JTA 事务提交
  • Hibernate 与 JTA 事务同步,因此事务由 JTA 事务管理器通过 afterCompletion 进行回调,可以正确清除其缓存

JPA

org.springframework.orm.jpa 包下提供的 Spring JPA 以类似于与 Hibernate 集成的方式提供对 Java Persistence API 的全面支持, 同时感知底层实现以提供额外功能。

Spring 环境中 JPA 设置的三种选择

使用 LocalEntityManagerFactoryBean

您只能在简单的部署环境中使用此选项,例如独立应用程序和集成测试。

LocalEntityManagerFactoryBean 创建了一个 EntityManagerFactory 适用于应用程序仅使用 JPA 进行数据访问的简单部署环境。工厂 bean 使用 JPA PersistenceProvider 自动检测机制(根据 JPA 的 Java SE 引导),并且在大多数情况下,只需要您指定持久层单元名称。以下 XML 示例配置了这样一个 bean:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
    </bean>
</beans>

这种 JPA 部署形式最简单,也最受限制。您不能引用现有的 JDBC DataSource bean 定义,并且不存在对全局事务的支持。此外,持久类的编织(字节码转换)是特定于提供者的,通常需要在启动时指定特定的 JVM 代理。此选项仅适用于设计 JPA 规范的独立应用程序和测试环境。

从 JNDI 获取 EntityManagerFactory

部署到 Java EE 服务器时可以使用此选项。检查您的服务器文档,了解如何将自定义 JPA 提供程序部署到您的服务器中,允许使用不同于服务器默认提供程序的提供程序。

从 JNDI(例如,在 Java EE 环境)获得 EntityManagerFactory ,如下面的示例:

<beans>
    <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>

此操作采用标准 Java EE 引导。Java EE 服务器自动检测持久层单元(实际上是应用程序 jar 中的 META-INF/persistence.xml 文件)和 Java EE 部署描述符中的 persistence-unit-ref 条目(例如, web.xml ),并为这些持久层单元定义环境命名上下文位置。

在这种情况下,整个持久层单元部署,包括持久层类的编织(字节码转换),都取决于 Java EE 服务器。JDBC DataSource 通过 META-INF/persistence.xml 文件中的 JNDI 位置定义。 EntityManager 事务与服务器的 JTA 子系统集成在一起。Spring 仅使用所获得的 EntityManagerFactory ,通过依赖注入将其传递给应用程序对象并管理持久层单元的事务(通常通过 JtaTransactionManager )。

如果在同一个应用程序中使用多个持久层单元,则此类 JNDI 检索的持久层单元的 bean 名称应与应用程序用来引用它们的持久层单元名称匹配(例如,@PersistenceUnit@PersistenceContext 注解里)。

使用 LocalContainerEntityManagerFactoryBean

您可以使用此选项在基于 Spring 的应用程序环境中获得完整的 JPA 功能。这包括 Web 容器(如 Tomcat)、独立应用程序和具有复杂持久层要求的集成测试。

如果您想专门配置 Hibernate 设置,直接的替代方法是设置原生 Hibernate LocalSessionFactoryBean 而不是普通的 JPA LocalContainerEntityManagerFactoryBean ,让它与 JPA 访问代码以及原生 Hibernate 访问代码进行交互。有关详细信息,请参阅 用于 JPA 交互的原生 Hibernate 设置

LocalContainerEntityManagerFactoryBean 给出了完全控制 EntityManagerFactory 配置,适合于在需要细粒度的定制环境。LocalContainerEntityManagerFactoryBean 创建一个基于 persistence.xml 文件的 PersistenceUnitInfo 实例,提供 dataSourceLookup 策略,并指定 loadTimeWeaver 。因此,可以使用 JNDI 之外的自定义数据源并控制编织过程。以下示例显示的典型 LocalContainerEntityManagerFactoryBean bean 定义 :

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
    </bean>
</beans>

以下示例显示了一个典型 persistence.xml 文件:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
        <mapping-file>META-INF/orm.xml</mapping-file>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>

<exclude-unlisted-classes/> 表明,不应该扫描带注解的实体类。明确的 true 值 ( <exclude-unlisted-classes>true</exclude-unlisted-classes/> ) 也意味着没有扫描。 <exclude-unlisted-classes>false</exclude-unlisted-classes/> 会触发扫描。但是,如果您希望进行实体类扫描,建议省略 exclude-unlisted-classes 标签。

使用 LocalContainerEntityManagerFactoryBean 是最强大的 JPA 设置选项,允许在应用程序中进行灵活的本地配置。它支持到现有 JDBC DataSource 的链接,支持本地和全局事务,等等。但是,它也对运行时环境提出了要求,例如,如果持久层提供程序需要字节码转换,那么就需要一个可编织的类加载器。

此选项可能与 Java EE 服务器的内置 JPA 功能冲突。在完整的 Java EE 环境中,考虑从 JNDI 获取您的 EntityManagerFactory 。或者,在 LocalContainerEntityManagerFactoryBean 定义中指定自定义 persistenceXmlLocation (例如,META-INF/my-persistence.xml)并在您的应用程序 jar 文件中仅包含具有该名称的描述符。由于 Java EE 服务器仅查找默认 META-INF/persistence.xml 文件,因此它会忽略此类自定义持久层单元,因此避免了与 Spring 驱动的 JPA 预先设置发生冲突。(例如,这适用于 Resin 3.1)

什么时候需要加载时编织?并非所有 JPA 提供程序都需要 JVM 代理。Hibernate 就是一个不这样做的例子。如果您的提供者不需要代理或您有其他替代方法,例如通过自定义编译器或 Ant 任务在构建时应用增强功能,则不应使用加载时编织器。

LoadTimeWeaver 接口是 Spring 提供的类,它允许以特定方式插入 JPA ClassTransformer 实例,具体取决于环境是 Web 容器还是应用程序服务器。ClassTransformers 通过 代理 挂钩通常效率不高。代理针对整个虚拟机工作并检查加载的每个类,这在生产服务器环境中通常是不受欢迎的。

Spring LoadTimeWeaver 为各种环境提供了许多实现,让 ClassTransformer 实例仅应用于每个类加载器而不是每个 VM。

有关 LoadTimeWeaver 实现及其设置的更多见解,请参阅 AOP 章节中的 Spring 配置 ,无论是通用的还是针对各种平台(例如 Tomcat、JBoss 和 WebSphere)定制的。

Spring configuration 中所述,您可以通过使用 XML 元素 context:load-time-weaver@EnableLoadTimeWeaving 注解来配置上下文作用域的 LoadTimeWeaver 。所有 JPA LocalContainerEntityManagerFactoryBean 实例都会自动选取这样的全局编织器。以下示例显示了设置加载时编织器的首选方法,提供平台的自动检测(例如 Tomcat 的具有编织功能的类加载器或 Spring 的 JVM 代理)以及将编织器自动传播到所有可识别编织器的 bean:

<context:load-time-weaver/>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

但是,如果需要,您可以通过 loadTimeWeaver 属性手动指定专用编织器 ,如以下示例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
    </property>
</bean>

无论如何配置 LTW,通过使用这种技术,依赖检测的 JPA 应用程序都可以在目标平台(例如 Tomcat)中运行,而无需代理。当托管应用程序依赖于不同的 JPA 实现时,这一点尤其重要,因为 JPA 转换器仅应用于类加载器级别,因此彼此隔离。

处理多个持久层单元

对于依赖多个持久层单元位置(例如,存储在类路径中的各种 Jar 包中)的应用程序,Spring 提供了 PersistenceUnitManager 充当中央存储库并避免持久层单元发现过程,这可能是昂贵的。默认实现允许指定多个位置。这些位置被解析,然后通过持久层单元名称检索。(默认情况下,在类路径中搜索 META-INF/persistence.xml 文件。)以下示例配置多个位置:

<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
    <property name="persistenceXmlLocations">
        <list>
            <value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
            <value>classpath:/my/package/**/custom-persistence.xml</value>
            <value>classpath*:META-INF/persistence.xml</value>
        </list>
    </property>
    <property name="dataSources">
        <map>
            <entry key="localDataSource" value-ref="local-db"/>
            <entry key="remoteDataSource" value-ref="remote-db"/>
        </map>
    </property>
    <!-- if no datasource is specified, use this one -->
    <property name="defaultDataSource" ref="remoteDataSource"/>
</bean>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="pum"/>
    <property name="persistenceUnitName" value="myCustomUnit"/>
</bean>

默认实现允许以声明方式(通过其属性,影响所有托管单元)或以编程方式(通过 PersistenceUnitPostProcessor 允许持久层单元选择)自定义 PersistenceUnitInfo 实例(在将它们提供给 JPA 提供程序之前 )。如果未指定 PersistenceUnitManager ,则创建一个并由 LocalContainerEntityManagerFactoryBean 在内部使用 。

后台引导

LocalContainerEntityManagerFactoryBean 支持通过 bootstrapExecutor 属性进行后台引导,如以下示例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="bootstrapExecutor">
        <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
    </property>
</bean>

实际的 JPA 提供程序引导程序被移交给指定的执行程序,然后并行运行到应用程序引导程序线程。暴露的 EntityManagerFactory 代理可以注入其他应用程序组件,甚至能够响应 EntityManagerFactoryInfo 配置检查。但是,一旦其他组件(例如,调用 createEntityManager )正在访问实际的 JPA 提供程序,这些调用就会阻塞,直到后台引导完成。特别是,当您使用 Spring Data JPA 时,请确保为其存储库设置延迟引导。

基于 JPA 实现 DAOs:EntityManagerFactoryEntityManager

尽管 EntityManagerFactory 实例是线程安全的,但 EntityManager 实例不是。注入的 JPA EntityManager 就像从应用程序服务器的 JNDI 环境中提取的一样,如 JPA 规范所定义的。如果有 EntityManager ,它将所有调用委托给当前的事务。否则,它会回退到每个操作新创建的一个 EntityManager ,这样实际上它的使用是线程安全的。

通过使用注入的 EntityManagerFactoryEntityManager ,可以在不依赖 Spring 的情况下针对普通 JPA 编写代码。如果启用了 PersistenceAnnotationBeanPostProcessor ,Spring 可以在字段和方法级别解析 @PersistenceUnit@PersistenceContext 注解。下面的示例显示了一个使用 @PersistenceUnit 注解的普通 JPA DAO 实现:

public class ProductDaoImpl implements ProductDao {

    private EntityManagerFactory emf;

    @PersistenceUnit
    public void setEntityManagerFactory(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public Collection loadProductsByCategory(String category) {
        try (EntityManager em = this.emf.createEntityManager()) {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
    }
}

前面的 DAO 不依赖于 Spring ,仍然非常适合 Spring 应用上下文。此外,DAO 利用注解来要求注入默认 EntityManagerFactory ,如以下示例 bean 定义所示:

<beans>

    <!-- bean post-processor for JPA annotations -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

作为显式定义 PersistenceAnnotationBeanPostProcessor 的替代方法,请考虑在应用上下文配置中使用 Spring XML 标签 context:annotation-config 。这样做会自动注册所有 Spring 标准后处理器以进行基于注解的配置,包括 CommonAnnotationBeanPostProcessor 等等。

考虑以下示例:

<beans>

    <!-- post-processors for all standard config annotations -->
    <context:annotation-config/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

这种 DAO 的主要问题是它总是通过工厂创建一个新的 EntityManager 。您可以通过请求注入一个事务 EntityManager (也称为“共享 EntityManager”,因为它是实际事务 EntityManager 的共享线程安全代理)而不是工厂来避免这种情况。以下示例显示了如何执行此操作:

public class ProductDaoImpl implements ProductDao {

    @PersistenceContext
    private EntityManager em;

    public Collection loadProductsByCategory(String category) {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        return query.getResultList();
    }
}

@PersistenceContext 注解有一个名为 type 的可选属性,默认为 PersistenceContextType.TRANSACTION 。您可以使用此默认设置来接收共享 EntityManager 代理。另一种选择 PersistenceContextType.EXTENDED 是完全不同的。这导致了所谓的扩展 EntityManager ,它不是线程安全的,因此不能在并发访问的组件中使用,例如 Spring 管理的单例 bean 。扩展 EntityManager 实例只应该用于有状态的组件,例如,驻留在会话中的组件, EntityManager 的生命周期不依赖于当前事务,而是完全取决于应用程序。

方法级和字段级注入:您可以在类中的字段或方法上应用指示依赖注入(例如 @PersistenceUnit@PersistenceContext )的注解——因此表达“方法级注入”和“字段级注入”。字段级注解简洁易用,而方法级注解允许进一步处理注入的依赖项。在这两种情况下,成员可见性(公共、受保护或私有)都无关紧要。

类级别的注解呢?在 Java EE 平台上,它们用于依赖声明而不是用于资源注入。

注入的 EntityManager 是 Spring 管理的(感知正在进行的事务)。即使新的 DAO 实现使用方法级注入 EntityManager 代替 EntityManagerFactory ,但由于注解的使用,不需要在应用上下文 XML 中进行任何更改。

这种 DAO 风格的主要优点是它只依赖于 Java Persistence API 。不需要导入任何 Spring 类。此外,因为感知 JPA 注解,Spring 容器会自动应用注入。从非侵入性的角度来看,这很有吸引力,并且对 JPA 开发人员来说感觉更自然。

Spring 驱动的 JPA 事务

JPA 的推荐策略是通过 JPA 的原生事务支持进行本地事务。Spring JpaTransactionManager 提供了许多从本地 JDBC 事务中已知的功能(例如特定于事务的隔离级别和资源级别的只读优化)针对任何常规 JDBC 连接池(无 XA 要求)。

Spring JPA 还允许已配置 JpaTransactionManager 的 JPA 事务向访问相同事务的 JDBC 访问代码公开 DataSource ,前提是已注册的 JpaDialect 支持检索底层 JDBC Connection 。Spring 为 EclipseLink 和 Hibernate JPA 实现提供方言。

作为直接的替代方案,Spring 的原生 HibernateTransactionManager 能够与 JPA 访问代码交互,适应几个 Hibernate 细节并提供 JDBC 交互。这与 LocalSessionFactoryBean 设置相结合特别有意义。有关详细信息,请参阅 JPA 交互的原生 Hibernate 设置

理解 JpaDialectJpaVendorAdapter

作为高级功能,JpaTransactionManager 及其子类 AbstractEntityManagerFactoryBean 允许将自定义 JpaDialect 传递到 jpaDialect bean 属性中。JpaDialect 实现可以启用 Spring 支持的以下高级功能,通常以特定于供应商的方式实现:

  • 应用特定的事务语义(例如自定义隔离级别或事务超时)
  • 检索事务性 JDBC Connection(用于接触基于 JDBC 的 DAO)
  • PersistenceExceptions 到 Spring DataAccessExceptions 的高级转换

这对于特殊事务语义和异常的高级翻译特别有价值。默认实现 ( DefaultJpaDialect ) 不提供任何特殊功能,如果需要前面列出的功能,则必须指定适当的方言。

作为主要用于 Spring 的全功能 LocalContainerEntityManagerFactoryBean 设置的更广泛的提供程序适配工具 ,JpaVendorAdapterJpaDialect 的功能与其他特定于提供程序的默认值相结合。分别指定 HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter 是自动配置 Hibernate 或 EclipseLink EntityManagerFactory 设置的最方便的方法。请注意,这些提供程序适配器主要设计用于 Spring 驱动的事务管理(即用于 JpaTransactionManager )。

使用 JTA 事务管理设置 JPA

作为 JpaTransactionManager 的替代方案,Spring 还允许通过 JTA 进行多资源事务协调,无论是在 Java EE 环境中还是使用独立的事务协调器,例如 Atomikos 。除了选择 Spring 的 JtaTransactionManager 而不是 JpaTransactionManager 之外 ,您还需要采取一些进一步的步骤:

  • 底层 JDBC 连接池需要支持 XA 并与您的事务协调器集成。这在 Java EE 环境中通常很简单,通过 JNDI 公开一种不同的 DataSource 类型。有关详细信息,请参阅您的应用程序服务器文档。类似地,独立事务协调器通常带有特殊的 XA 集成 DataSource 变体
  • 需要为 JTA 配置 JPA EntityManagerFactory 设置。这是特定于供应商的,通常通过在 LocalContainerEntityManagerFactoryBean 上指定为 jpaProperties 的特殊属性来实现。在 Hibernate 的情况下,这些属性甚至是特定于版本的。
  • Spring HibernateJpaVendorAdapter 强制执行某些面向 Spring 的默认值,例如连接释放模式 on-close ,它在 Hibernate 5.0 中与 Hibernate 自己的默认值匹配,但在 Hibernate 5.1+ 中不再匹配。对于 JTA 设置,请确保将持久层单元事务类型声明为 “JTA” 。或者,将 Hibernate 5.2 的 hibernate.connection.handling_mode 属性设置为 DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT 以恢复 Hibernate 自己的默认值。有关相关说明,请参阅 Hibernate 的虚假应用程序服务器警告
  • 或者,考虑从您的应用程序服务器本身获取 EntityManagerFactory (通过 JNDI 查找而不是本地声明的 LocalContainerEntityManagerFactoryBean )。服务器提供的 EntityManagerFactory 可能需要在您的服务器配置中进行特殊定义(使部署的可移植性降低),但它是为服务器的 JTA 环境设置的。

用于 JPA 交互的原生 Hibernate 设置和原生 Hibernate 事务

原生 LocalSessionFactoryBean 设置结合 HibernateTransactionManager 允许 @PersistenceContext 与其他 JPA 访问代码进行交互。Hibernate SessionFactory 实现了 JPA 的 EntityManagerFactory 接口,而 Hibernate Session 实现了 JPA EntityManager 。Spring 的 JPA 支持工具自动检测 Hibernate 会话。

因此,这种原生 Hibernate 设置可以在许多场景中作为标准 JPA LocalContainerEntityManagerFactoryBeanJpaTransactionManager 组合的替代品 ,允许与 SessionFactory.getCurrentSession() (以及 HibernateTemplate@PersistenceContext EntityManager 在同一本地事务中进行交互。这样的设置还提供了更强大的 Hibernate 集成和更多的配置灵活性,因为它不受 JPA 引导程序契约的约束。

在这种情况下您不需要配置 HibernateJpaVendorAdapter ,因为 Spring 的原生 Hibernate 设置提供了更多功能(例如,自定义 Hibernate Integrator 设置、Hibernate 5.3 bean 容器集成以及对只读事务的更强优化)。最后,您还可以通过 LocalSessionFactoryBuilder@Bean 风格配置无缝集成(不涉及 FactoryBean )来表达原生 Hibernate 设置。

LocalSessionFactoryBeanLocalSessionFactoryBuilder 支持后台引导,就像 JPA LocalContainerEntityManagerFactoryBean 一样。

LocalSessionFactoryBean 可以通过 bootstrapExecutor 属性获得。在 LocalSessionFactoryBuilder 上,重载 buildSessionFactory 方法接受 bootstrap executor 参数。

posted @ 2022-06-09 21:23  流星<。)#)))≦  阅读(22)  评论(0编辑  收藏  举报