4.对象关系 Map(ORM)数据访问
4.对象关系 Map(ORM)数据访问
本节介绍使用对象关系 Map(ORM)时的数据访问。
4.1. Spring ORM 简介
Spring Framework 支持与 Java Persistence API(JPA)集成,并支持用于资源 Management,数据访问对象(DAO)实现和事务策略的本地 Hibernate。例如,对于 Hibernate,提供了一流的支持以及一些便捷的 IoC 功能,这些功能可以解决许多典型的 Hibernate 集成问题。您可以通过“依赖关系注入”为 OR(对象关系)Map 工具配置所有受支持的功能。他们可以参与 Spring 的资源和事务 Management,并且符合 Spring 的通用事务和 DAO 异常层次结构。推荐的集成样式是针对普通的 Hibernate 或 JPA API 编写 DAO。
当您创建数据访问应用程序时,Spring 会为您选择的 ORM 层显着增强。您可以根据需要利用尽可能多的集成支持,并且应该将这种集成工作与内部构建类似基础架构的成本和风险进行比较。不管使用哪种技术,您都可以像使用库一样使用许多 ORM 支持,因为所有内容都是作为一组可重用的 JavaBean 设计的。 Spring IoC 容器中的 ORM 有助于配置和部署。因此,本节中的大多数示例都显示了 Spring 容器内部的配置。
使用 Spring 框架创建 ORM DAO 的好处包括:
-
更轻松的测试. Spring 的 IoC 方法使交换 Hibernate
SessionFactory
实例,JDBCDataSource
实例,事务 Management 器和 Map 对象实现(如果需要)的实现和配置位置变得容易。反过来,这使得隔离每个与持久性相关的代码的测试变得容易得多。 -
通用数据访问异常. Spring 可以包装您的 ORM 工具中的异常,将其从专有(可能已检查)的异常转换为通用的运行时
DataAccessException
层次结构。通过此功能,您可以仅在适当的层中处理大多数不可恢复的持久性异常,而不会烦人样板捕获,抛出和异常声明。您仍然可以根据需要捕获和处理异常。请记住,JDBC 异常(包括特定于 DB 的方言)也将转换为相同的层次结构,这意味着您可以在一致的编程模型中使用 JDBC 执行某些操作。 -
常规资源 Management. Spring 应用程序上下文可以处理 Hibernate
SessionFactory
实例,JPAEntityManagerFactory
实例,JDBCDataSource
实例和其他相关资源的位置和配置。这使得这些值易于 Management 和更改。 Spring 提供了对持久性资源的高效,便捷和安全的处理。例如,使用 Hibernate 的相关代码通常需要使用相同的 HibernateSession
,以确保效率和适当的事务处理。通过通过 HibernateSessionFactory
公开当前的Session
,Spring 可以轻松地透明地创建Session
并将其绑定到当前线程。因此,对于任何本地或 JTA 事务环境,Spring 都解决了典型的 Hibernate 使用中的许多长期问题。 -
集成的事务 Management. 您可以通过
@Transactional
Comments 或通过在 XML 配置文件中显式配置事务 AOP 建议,使用声明性的,面向方面的编程(AOP)样式方法拦截器包装 ORM 代码。在这两种情况下,都为您处理事务语义和异常处理(回滚等)。如资源与 TransactionManagement中所述,您还可以交换各种事务 Management 器,而不会影响与 ORM 相关的代码。例如,您可以在两种情况下使用相同的完整服务(例如声明式事务)在本地事务和 JTA 之间交换。此外,与 JDBC 相关的代码可以与您用于执行 ORM 的代码完全事务集成。这对于不适合 ORM(例如批处理和 BLOB 流)但仍需要与 ORM 操作共享常见事务的数据访问很有用。
Tip
要获得更全面的 ORM 支持,包括对 MongoDB 等替代数据库技术的支持,您可能需要查看Spring Data项目套件。如果您是 JPA 用户,则https://spring.io的使用 JPA 访问数据的入门指南提供了很好的介绍。
4.2. ORM 集成的一般注意事项
本节重点介绍适用于所有 ORM 技术的注意事项。 Hibernate部分提供更多详细信息,并在具体上下文中显示这些功能和配置。
Spring 的 ORM 集成的主要目标是清晰的应用程序分层(具有任何数据访问和事务技术)以及松散耦合应用程序对象–不再对数据访问或事务策略依赖业务服务,不再进行硬编码资源查找,更多难以替换的单例,不再需要自定义服务注册表。目标是采用一种简单且一致的方法来连接应用程序对象,使它们尽可能可重用,并尽可能避免容器依赖性。所有单独的数据访问功能都可以单独使用,但可以与 Spring 的应用程序上下文概念很好地集成,从而提供基于 XML 的配置和对不需要 Spring 意识的纯 JavaBean 实例的交叉引用。在典型的 Spring 应用程序中,许多重要的对象是 JavaBean:数据访问模板,数据访问对象,事务 Management 器,使用数据访问对象和事务 Management 器的业务服务,Web 视图解析器,使用业务服务的 Web 控制器等等。
4.2.1. 资源与 TransactionManagement
典型的业务应用程序中充斥着重复的资源 Management 代码。许多项目试图发明自己的解决方案,有时为了编程方便而牺牲了对故障的正确处理。 Spring 提倡简单的解决方案来进行适当的资源处理,即通过在 JDBC 情况下进行模板化以及为 ORM 技术应用 AOP 拦截器来实现 IoC。
基础结构提供适当的资源处理,并将特定的 API 异常适当地转换为未经检查的基础结构异常层次结构。 Spring 引入了 DAO 异常层次结构,适用于任何数据访问策略。对于直接 JDBC,previous section中提到的JdbcTemplate
类提供了连接处理和SQLException
到DataAccessException
层次结构的正确转换,包括将特定于数据库的 SQL 错误代码转换为有意义的异常类。对于 ORM 技术,请参见next section以获取相同的异常翻译好处。
在事务 Management 方面,JdbcTemplate
类通过相应的 Spring 事务 Management 器挂接到 Spring 事务支持并支持 JTA 和 JDBC 事务。对于受支持的 ORM 技术,Spring 通过 Hibernate 和 JPA 事务 Management 器提供了 Hibernate 和 JPA 支持以及 JTA 支持。有关 Transaction 支持的详细信息,请参见Transaction Management章。
4.2.2. exception 翻译
在 DAO 中使用 Hibernate 或 JPA 时,必须决定如何处理持久性技术的本机异常类。 DAO 抛出HibernateException
或PersistenceException
的子类,具体取决于技术。这些异常都是运行时异常,不必声明或捕获。您可能还必须处理IllegalArgumentException
和IllegalStateException
。这意味着调用者只能将异常视为一般致命的,除非他们希望依赖于持久性技术自身的异常结构。如果不将调用者与实现策略联系在一起,则无法捕获特定原因(例如乐观锁定失败)。这种权衡可能对于基于 ORM 的应用程序或不需要任何特殊异常处理(或两者都使用)的应用程序是可接受的。但是,Spring 允许通过@Repository
Comments 透明地应用异常转换。以下示例(一个用于 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 和 Comments 来实现 DAO,同时仍可受益于 SpringManagement 的事务,依赖项注入以及对 Spring 的自定义异常层次结构的透明异常转换(如果需要)。
4.3. Hibernate
我们从 Spring 环境中的Hibernate 5开始,使用它来演示 Spring 集成 ORMap 器所采用的方法。本节详细讨论了许多问题,并展示了 DAO 实现和事务划分的不同变体。这些模式中的大多数都可以直接转换为所有其他受支持的 ORM 工具。然后,本章后面的部分将介绍其他 ORM 技术,并显示一些简短的示例。
Note
从 Spring Framework 5.0 开始,Spring 需要 Hibernate ORM 4.3 或更高版本来支持 JPA,甚至需要 Hibernate ORM 5.0 来针对本机 Hibernate Session API 进行编程。请注意,Hibernate 团队不再维护 5.1 之前的任何版本,并且可能很快会专注于 5.3.
4.3.1. Spring 容器中的 SessionFactory 设置
为了避免将应用程序对象与硬编码的资源查找绑定在一起,可以在 Spring 容器中将资源(例如 JDBC DataSource
或 Hibernate SessionFactory
)定义为 bean。需要访问资源的应用程序对象通过 Bean 引用接收对此类 sched 义实例的引用,如next section中的 DAO 定义所示。
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
(通常由应用服务器 Management)只是配置问题,如以下示例所示:
<beans> <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/> </beans>
您还可以使用 Spring 的JndiObjectFactoryBean
/<jee:jndi-lookup>
来访问位于 JNDI 的SessionFactory
,以检索并公开它。但是,这通常在 EJB 上下文之外并不常见。
Note
Spring 还提供了LocalSessionFactoryBuilder
变体,可与@Bean
样式配置和编程设置(不涉及FactoryBean
)无缝集成。
LocalSessionFactoryBean
和LocalSessionFactoryBuilder
都支持后台引导,并且 Hibernate 初始化与给定引导执行程序(例如SimpleAsyncTaskExecutor
)上的应用程序引导线程并行运行。在LocalSessionFactoryBean
上,可以通过bootstrapExecutor
属性使用。在程序化LocalSessionFactoryBuilder
上,有一个重载的buildSessionFactory
方法,该方法带有引导执行程序参数。
从 Spring Framework 5.1 开始,这样的本地 Hibernate 设置还可以在本地 Hibernate 访问旁边公开用于标准 JPA 交互的 JPA EntityManagerFactory
。有关详情,请参见JPA 的本机 Hibernate 设置。
4.3.2. 基于 Plain Hibernate API 实现 DAO
Hibernate 具有称为上下文会话的功能,其中,Hibernate 本身每个事务 Management 一个当前的Session
。这大致相当于 Spring 对每个事务同步一个 Hibernate Session
。基于普通的 Hibernate API,相应的 DAO 实现类似于以下示例:
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 类。从非侵入性的角度来看,这很有吸引力,并且对于 Hibernate 开发人员而言可能更自然。
但是,DAO 会抛出普通的HibernateException
(未经检查,因此不必声明或捕获),这意味着调用方只能将异常视为一般致命的消息-除非他们希望依赖于 Hibernate 自己的异常层次结构。如果不将调用者与实现策略联系在一起,则无法捕获特定原因(例如乐观锁定失败)。这种权衡对于基于 Hibernate 的应用程序,不需要任何特殊异常处理或两者都可以接受。
幸运的是,Spring 的LocalSessionFactoryBean
支持任何 Spring 事务策略的 Hibernate 的SessionFactory.getCurrentSession()
方法,甚至返回HibernateTransactionManager
,返回当前的 SpringManagement 的事务Session
。该方法的标准行为仍然是返回与正在进行的 JTA 事务关联的当前Session
(如果有)。无论您使用 Spring 的JtaTransactionManager
,EJB 容器 Management 的事务(CMT)还是 JTA,此行为均适用。
总之,您可以基于普通的 Hibernate API 实现 DAO,同时仍然能够参与 SpringManagement 的事务。
4.3.3. 声明式事务划分
我们建议您使用 Spring 的声明式事务支持,该支持使您可以用 AOP 事务拦截器替换 Java 代码中的显式事务划分 API 调用。您可以使用 Java 注解或 XML 在 Spring 容器中配置此事务拦截器。这种声明式事务处理功能使您可以使业务服务免于重复的事务划分代码,并专注于添加业务逻辑,这是应用程序的 true 价值。
Note
您可以使用@Transactional
Comments 对服务层进行 Comments,并指示 Spring 容器查找这些 Comments 并为这些带 Comments 的方法提供事务性语义。以下示例显示了如何执行此操作:
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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://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>
4.3.4. 程序化 Transaction 划分
您可以在应用程序的较高级别中划分事务,而这些较低级别的数据访问服务可以跨越任意数量的操作。对周围业务服务的实施也没有限制。它只需要一个 Spring PlatformTransactionManager
。同样,后者可以来自任何地方,但最好通过setTransactionManager(..)
方法作为 bean 的引用。同样,应通过setProductDao(..)
方法设置productDAO
。以下几对代码片段显示了 Spring 应用程序上下文中的事务 Management 器和业务服务定义,以及业务方法实现的示例:
<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
的行为方式相同,但允许每个方法配置可回退策略。
4.3.5. TransactionManagement 策略
TransactionTemplate
和TransactionInterceptor
都将实际的事务处理委托给PlatformTransactionManager
实例(对于内部使用ThreadLocal
Session
可以是HibernateTransactionManager
(对于单个 Hibernate SessionFactory
))或JtaTransactionManager
(委托给容器的 JTA 子系统)。应用程序。您甚至可以使用自定义PlatformTransactionManager
实现。从本机 Hibernate 事务 Management 切换到 JTA(例如,当面对某些应用程序部署的分布式事务要求时)仅是配置问题。您可以用 Spring 的 JTA 事务实现替换 Hibernate 事务 Management 器。事务划分和数据访问代码都无需更改即可工作,因为它们使用通用的事务 ManagementAPI。
对于跨多个 Hibernate 会话工厂的分布式事务,可以将JtaTransactionManager
作为事务策略与多个LocalSessionFactoryBean
定义结合使用。然后,每个 DAO 都将一个特定的SessionFactory
引用传递到其相应的 bean 属性中。如果所有基础 JDBC 数据源都是事务性容器数据源,那么只要使用JtaTransactionManager
作为策略,业务服务就可以在任何数量的 DAO 和任意数量的会话工厂之间划分事务。
HibernateTransactionManager
和JtaTransactionManager
都允许使用 Hibernate 进行正确的 JVM 级别的缓存处理,而无需特定于容器的事务 Management 器查找或 JCA 连接器(如果您不使用 EJB 来启动事务)。
HibernateTransactionManager
可以将 Hibernate JDBC Connection
导出为特定DataSource
的普通 JDBC 访问代码。此功能允许使用混合的 Hibernate 和 JDBC 数据访问进行高级事务划分,而完全无需 JTA,前提是您仅访问一个数据库。如果已通过LocalSessionFactoryBean
类的dataSource
属性使用DataSource
设置了传入的SessionFactory
,则HibernateTransactionManager
自动将 Hibernate 事务公开为 JDBC 事务。或者,您可以通过HibernateTransactionManager
类的dataSource
属性显式指定应该为其公开事务的DataSource
。
4.3.6. 比较容器 Management 的资源和本地定义的资源
您可以在容器 Management 的 JNDI SessionFactory
和本地定义的 JNDI SessionFactory
之间切换,而无需更改单行应用程序代码。将资源定义保留在容器中还是在应用程序中本地保留,主要取决于您使用的事务策略。与 Spring 定义的本地SessionFactory
相比,手动注册的 JNDI SessionFactory
没有任何好处。通过 Hibernate 的 JCA 连接器部署SessionFactory
可以增加参与 Java EE 服务器的 Management 基础结构的附加价值,但不会增加实际价值。
Spring 的事务支持未绑定到容器。当配置了 JTA 以外的任何策略时,事务支持也可以在独立或测试环境中工作。尤其是在单数据库事务的典型情况下,Spring 的单资源本地事务支持是 JTA 的轻量级功能强大的替代方案。当您使用本地 EJBStateless 会话 Bean 驱动事务时,即使您仅访问单个数据库并且仅使用 Stateless 会话 Bean 通过容器 Management 的事务来提供声明性事务,也要依赖 EJB 容器和 JTA。以编程方式直接使用 JTA 还需要 Java EE 环境。就 JTA 本身和 JNDI DataSource
实例而言,JTA 不仅仅涉及容器依赖项。对于非 Spring 的,由 JTA 驱动的 Hibernate 事务,您必须使用 Hibernate JCA 连接器或额外的 Hibernate 事务代码,并将TransactionManagerLookup
配置为正确的 JVM 级别的缓存。
如果 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 的HibernateTransactionManager
或JtaTransactionManager
。您将获得所有好处,包括适当的事务性 JVM 级别的缓存和分布式事务,而不会给容器部署带来不便。通过 JCA 连接器对 Hibernate SessionFactory
进行 JNDI 注册仅在与 EJB 结合使用时才增加价值。
4.3.7. Hibernate 虚 Pseudo 应用程序服务器警告
在某些具有非常严格的XADataSource
实现的 JTA 环境中(当前仅某些 WebLogic Server 和 WebSphere 版本),在不考虑该环境的 JTA PlatformTransactionManager
对象的情况下配置 Hibernate 时,虚假警告或异常会显示在应用程序服务器日志中。这些警告或异常指示正在访问的连接不再有效或 JDBC 访问不再有效,这可能是因为事务不再有效。例如,这是 WebLogic 的实际异常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
您可以通过使 Hibernate 知道与之同步的 JTA PlatformTransactionManager
实例(以及 Spring)来解决此警告。您可以通过以下两种方式执行此操作:
-
如果在您的应用程序上下文中,您已经直接获取 JTA
PlatformTransactionManager
对象(大概是通过JndiObjectFactoryBean
或<jee:jndi-lookup>
从 JNDI 获取)并将其提供给例如 Spring 的JtaTransactionManager
,则最简单的方法是指定对定义此 JTA 的 bean 的引用PlatformTransactionManager
实例作为LocalSessionFactoryBean.
Spring 的jtaTransactionManager
属性的值,然后使该对象可用于 Hibernate。 -
更可能的是,您还没有 JTA
PlatformTransactionManager
实例,因为 Spring 的JtaTransactionManager
可以自己找到它。因此,您需要配置 Hibernate 以直接查找 JTAPlatformTransactionManager
。通过在 Hibernate 配置中配置特定于应用程序服务器的TransactionManagerLookup
类来执行此操作,如 Hibernate 手册中所述。
本节的其余部分描述了在 Hibernate 不了解 JTA PlatformTransactionManager
的情况下发生的事件的 Sequences。
如果未配置 Hibernate 来识别 JTA PlatformTransactionManager
,则在 JTA 事务提交时会发生以下事件:
-
JTA 事务提交。
-
Spring 的
JtaTransactionManager
已同步到 JTA 事务,因此 JTA 事务 Management 器通过afterCompletion
回调对其进行回调。 -
在其他活动中,这种同步可以触发 Spring 通过 Hibernate 的
afterTransactionCompletion
回调(用于清除 Hibernate 缓存)来回调到 Hibernate,然后在 Hibernate 会话上进行显式close()
调用,这将导致 Hibernate 尝试close()
JDBC Connection。 -
在某些环境中,此
Connection.close()
调用随后触发警告或错误,因为应用程序服务器不再认为Connection
可用,因为事务已被提交。
当 Hibernate 配置为具有 JTA PlatformTransactionManager
感知能力时,在 JTA 事务提交时会发生以下事件:
-
JTA 事务已准备好提交。
-
Spring 的
JtaTransactionManager
已同步到 JTA 事务,因此 JTA 事务 Management 器通过beforeCompletion
回调回调了该事务。 -
Spring 知道,Hibernate 本身已同步到 JTA 事务,并且其行为与以前的场景不同。假设完全需要关闭 Hibernate
Session
,Spring 现在将其关闭。 -
JTA 事务提交。
-
Hibernate 已同步到 JTA 事务,因此 JTA 事务 Management 器通过
afterCompletion
回调调用了该事务,并可以正确清除其缓存。
4.4. JPA
可以在org.springframework.orm.jpa
软件包下获得的 Spring JPA 以类似于与 Hibernate 集成的方式为Java 持久性 API提供全面的支持,同时知道底层实现以提供附加功能。
4.4.1. 在 Spring 环境中设置 JPA 的三个选项
Spring JPA 支持提供了三种设置 JPA EntityManagerFactory
的方式,供应用程序用来获取实体 Management 器。
Using 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
是更改 XML 配置的问题,如以下示例所示:
<beans> <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/> </beans>
此操作假定标准 Java EE 引导。 Java EE 服务器自动检测持久性单元(实际上是应用程序 jar 中的META-INF/persistence.xml
个文件)和 Java EE 部署 Descriptors 中的persistence-unit-ref
个条目(例如web.xml
),并为这些持久性单元定义环境命名上下文位置。
在这种情况下,整个持久性单元部署,包括持久性类的编织(字节码转换),都取决于 Java EE 服务器。 JDBC DataSource
是通过META-INF/persistence.xml
文件中的 JNDI 位置定义的。 EntityManager
事务与服务器的 JTA 子系统集成在一起。 Spring 仅使用获得的EntityManagerFactory
,通过依赖性注入将其传递给应用程序对象,并 Management 持久性单元的事务(通常通过JtaTransactionManager
)。
如果在同一应用程序中使用多个持久性单元,则此类 JNDI 检索的持久性单元的 Bean 名称应与应用程序用来引用它们的持久性单元名称匹配(例如,在@PersistenceUnit
和@PersistenceContext
注解中)。
Using LocalContainerEntityManagerFactoryBean
您可以将此选项用于基于 Spring 的应用程序环境中的完整 JPA 功能。这包括 Web 容器(例如 Tomcat),独立应用程序以及具有复杂持久性要求的集成测试。
Note
如果要专门配置 Hibernate 设置,则直接的替代方法是使用 Hibernate 5.2 或 5.3 并设置本机 Hibernate LocalSessionFactoryBean
而不是纯 JPA LocalContainerEntityManagerFactoryBean
,从而使其与 JPA 访问代码以及本机 Hibernate 访问代码交互。有关详情,请参见用于 JPA 交互的本地 Hibernate 设置。
LocalContainerEntityManagerFactoryBean
可以完全控制EntityManagerFactory
的配置,适用于需要细粒度自定义的环境。 LocalContainerEntityManagerFactoryBean
基于persistence.xml
文件,提供的dataSourceLookup
策略和指定的loadTimeWeaver
创建一个PersistenceUnitInfo
实例。因此,可以在 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>
Note
<exclude-unlisted-classes/>
快捷方式指示不应进行对带 Comments 的实体类的扫描。显式的“ 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 文件中仅包含具有该名称的 Descriptors。因为 Java EE 服务器仅查找默认的META-INF/persistence.xml
文件,所以它忽略了此类自定义持久性单元,因此避免了与 Spring 预先驱动的 JPA 设置发生冲突。 (例如,这适用于 Resin 3.1. )
什么时候需要加载时编织?
并非所有的 JPA 提供程序都需要 JVM 代理。休眠是一个没有的例子。如果您的提供程序不需要代理,或者您有其他选择,例如在构建时通过自定义编译器或 Ant 任务应用增强功能,则不应使用加载时织布器。
LoadTimeWeaver
接口是 Spring 提供的类,可让 JPA ClassTransformer
实例以特定方式插入,具体取决于环境是 Web 容器还是应用程序服务器。通过agent钩子ClassTransformers
通常效率不高。代理针对整个虚拟机工作,并检查每个已加载的类,这在生产服务器环境中通常是不希望的。
Spring 为各种环境提供了许多LoadTimeWeaver
实现,让ClassTransformer
实例仅应用于每个类加载器,而不应用于每个 VM。
有关LoadTimeWeaver
实现及其设置的更多信息,请参见 AOP 章节中的Spring configuration,这些实现是通用的还是针对各种平台(例如 Tomcat,WebLogic,GlassFish,Resin 和 JBoss)定制的。
如Spring configuration中所述,您可以使用context:load-time-weaver
XML 元素的@EnableLoadTimeWeaving
注解来配置上下文范围内的LoadTimeWeaver
。所有 JPA LocalContainerEntityManagerFactoryBean
实例都会自动拾取这样的全局编织器。以下示例显示了设置加载时织构的首选方法,该方法可自动检测平台(WebLogic,GlassFish,Tomcat,Resin,JBoss 或 VM 代理),并将织构自动传播到所有可识别织构的 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 转换器仅应用于类加载器级别,因此彼此隔离。
处理多个持久性单元
对于依赖多个持久性单元位置的应用程序(例如,存储在 Classpath 中的各种 JARS 中),Spring 提供了PersistenceUnitManager
充当中央存储库并避免了持久性单元发现过程,这可能是昂贵的。默认实现允许指定多个位置。解析这些位置,然后通过持久性单元名称进行检索。 (默认情况下,在 Classpath 中搜索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
在内部创建和使用一个。
Background Bootstrapping
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
配置检查。但是,一旦其他组件访问了实际的 JPA 提供程序(例如,调用createEntityManager
),这些调用将阻塞,直到后台引导完成为止。特别是,当您使用 Spring Data JPA 时,请确保还为其存储库设置了延迟引导。
4.4.2. 基于 JPA 的 DAO 实现:EntityManagerFactory 和 EntityManager
Note
尽管EntityManagerFactory
个实例是线程安全的,但EntityManager
个实例不是线程安全的。根据 JPA 规范定义,注入的 JPA EntityManager
的行为类似于从应用程序服务器的 JNDI 环境中获取的EntityManager
。它将所有调用委派给当前事务EntityManager
(如果有)。否则,它会退回到每个操作新创建的EntityManager
,实际上使它的使用成为线程安全的。
通过使用注入的EntityManagerFactory
或EntityManager
,可以针对不带任何 Spring 依赖关系的普通 JPA 编写代码。如果启用了PersistenceAnnotationBeanPostProcessor
,则 Spring 可以在字段和方法级别理解@PersistenceUnit
和@PersistenceContext
注解。以下示例显示了使用@PersistenceUnit
Comments 的普通 JPA DAO 实现:
public class ProductDaoImpl implements ProductDao { private EntityManagerFactory emf; @PersistenceUnit public void setEntityManagerFactory(EntityManagerFactory emf) { this.emf = emf; } public Collection loadProductsByCategory(String category) { EntityManager em = this.emf.createEntityManager(); try { Query query = em.createQuery("from Product as p where p.category = ?1"); query.setParameter(1, category); return query.getResultList(); } finally { if (em != null) { em.close(); } } } }
前面的 DAO 不依赖于 Spring,并且仍然非常适合 Spring 应用程序上下文。此外,DAO 利用 Comments 的优势要求注入默认的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 context:annotation-config
XML 元素。这样做会自动注册所有 Spring 标准后处理器以进行基于 Comments 的配置,包括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
Comments 具有一个名为type
的可选属性,默认为PersistenceContextType.TRANSACTION
。您可以使用此默认值来接收共享的EntityManager
代理。替代PersistenceContextType.EXTENDED
是完全不同的事情。这导致所谓的EntityManager
扩展,它不是线程安全的,因此不能在并发访问的组件(例如 SpringManagement 的单例 bean)中使用。扩展的EntityManager
实例仅应用于有状态的组件中,例如,驻留在会话中的状态组件,而EntityManager
的生命周期不依赖于当前事务,而完全取决于应用程序。
方法级和 site 级进样
您可以在类中的字段或方法上应用指示依赖项注入的 Comments(例如@PersistenceUnit
和@PersistenceContext
),因此表达式为“方法级注入”和“字段级注入”。字段级 Comments 简洁明了,易于使用,而方法级 Comments 则允许对注入的依赖项进行进一步处理。在这两种情况下,成员的可见性(公共,受保护或私有)都无关紧要。
那么类级 Comments 呢?
在 Java EE 平台上,它们用于依赖性声明,而不用于资源注入。
注入的EntityManager
是 SpringManagement 的(意识到正在进行的事务)。即使新的 DAO 实现使用EntityManager
而不是EntityManagerFactory
的方法级注入,由于 Comments 的使用,应用程序上下文 XML 也不需要更改。
这种 DAO 样式的主要优点是,它仅取决于 Java Persistence API。不需要导入任何 Spring 类。而且,由于可以理解 JPA 注解,因此 Spring 容器会自动应用注入。从非侵入性的角度来看,这是有吸引力的,并且对于 JPA 开发人员而言,感觉会更自然。
4.4.3. Spring 驱动的 JPATransaction
Note
我们强烈建议您阅读声明式 TransactionManagement(如果您尚未阅读的话),以更详细地介绍 Spring 的声明式事务支持。
对于 JPA,推荐的策略是通过 JPA 的本机事务支持来进行本地事务。 Spring 的JpaTransactionManager
提供了针对任何常规 JDBC 连接池(无需 XA 要求)的本地 JDBC 事务中已知的许多功能(例如,特定于事务的隔离级别和资源级别的只读优化)。
Spring JPA 还允许已配置的JpaTransactionManager
将 JPA 事务公开给访问同一DataSource
的 JDBC 访问代码,只要注册的JpaDialect
支持底层 JDBC Connection
的检索。 Spring 为 EclipseLink 和 Hibernate JPA 实现提供了方言。有关JpaDialect
机制的详细信息,请参见next section。
Note
作为直接替代方案,Spring 的本机HibernateTransactionManager
能够与 Spring Framework 5.1 和 Hibernate 5.2/5.3 以及更高版本的 JPA 访问代码进行交互,以适应多种 Hibernate 规范并提供 JDBC 交互。结合LocalSessionFactoryBean
设置,这特别有意义。有关详情,请参见用于 JPA 交互的本机 Hibernate 设置。
4.4.4. 了解 JpaDialect 和 JpaVendorAdapter
作为高级功能,JpaTransactionManager
和AbstractEntityManagerFactoryBean
的子类允许将自定义JpaDialect
传递到jpaDialect
bean 属性中。 JpaDialect
实现通常可以以特定于供应商的方式启用 Spring 支持的以下高级功能:
-
应用特定的事务语义(例如自定义隔离级别或事务超时)
-
检索事务性 JDBC
Connection
(用于公开基于 JDBC 的 DAO) -
PersistenceExceptions
到 SpringDataAccessExceptions
的高级翻译
这对于特殊的事务语义和异常的高级翻译特别有价值。默认实现(DefaultJpaDialect
)不提供任何特殊功能,如果需要前面列出的功能,则必须指定适当的方言。
Tip
JpaVendorAdapter
作为主要用于 Spring 的全功能LocalContainerEntityManagerFactoryBean
设置的更广泛的提供程序适应工具,JpaVendorAdapter
将JpaDialect
的功能与其他特定于提供程序的默认值结合在一起。分别指定HibernateJpaVendorAdapter
或EclipseLinkJpaVendorAdapter
是分别为 Hibernate 或 EclipseLink 自动配置EntityManagerFactory
设置的最便捷方法。请注意,这些提供程序适配器主要设计用于与 Spring 驱动的事务 Management 一起使用(即,与JpaTransactionManager
一起使用)。
有关其操作以及在 Spring 的 JPA 支持中如何使用它们的更多详细信息,请参见JpaDialect和JpaVendorAdapter javadoc。
4.4.5. 使用 JTA 事务 Management 设置 JPA
作为JpaTransactionManager
的替代方法,Spring 还允许通过 JTA 在 Java EE 环境中或与独立事务协调器(例如 Atomikos)一起进行多资源事务协调。除了选择 Spring 的JtaTransactionManager
而不是JpaTransactionManager
之外,您还需要采取其他步骤:
-
底层的 JDBC 连接池必须具有 XA 功能,并与事务协调器集成。这在 Java EE 环境中通常很简单,通过 JNDI 公开了另一种
DataSource
。有关详细信息,请参见您的应用程序服务器文档。类似地,独立事务协调器通常带有特殊的 XA 集成DataSource
实现。再次,检查其文档。 -
需要为 JTA 配置 JPA
EntityManagerFactory
设置。这是特定于提供程序的,通常通过将特殊属性指定为LocalContainerEntityManagerFactoryBean
上的jpaProperties
。对于 Hibernate,这些属性甚至是特定于版本的。有关详细信息,请参见 Hibernate 文档。 -
Spring 的
HibernateJpaVendorAdapter
强制执行某些面向 Spring 的默认值,例如连接释放模式on-close
,它与 Hibernate 5.0 中 Hibernate 自己的默认值匹配,但在 5.1/5.2 中不再匹配。对于 JTA 设置,请不要声明HibernateJpaVendorAdapter
开头或关闭其prepareConnection
标志。或者,将 Hibernate 5.2 的hibernate.connection.handling_mode
属性设置为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
以恢复 Hibernate 自己的默认值。有关 WebLogic 的相关说明,请参见Hibernate 虚 Pseudo 应用程序服务器警告。 -
或者,考虑从应用程序服务器本身获取
EntityManagerFactory
(即,通过 JNDI 查找而不是本地声明的LocalContainerEntityManagerFactoryBean
)。服务器提供的EntityManagerFactory
可能需要在服务器配置中进行特殊定义(使部署的可移植性降低),但已针对服务器的 JTA 环境进行了设置。
4.4.6. 用于 JPA 交互的本机 Hibernate 设置和本机 Hibernate 事务
从 Spring Framework 5.1 和 Hibernate 5.2/5.3 开始,与LocalSessionFactoryBean
结合使用的本地LocalSessionFactoryBean
设置允许与@PersistenceContext
和其他 JPA 访问代码进行交互。现在,Hibernate SessionFactory
本机实现 JPA 的EntityManagerFactory
接口,而 Hibernate Session
句柄本机则是 JPA EntityManager
。 Spring 的 JPA 支持工具会自动检测本地 Hibernate 会话。
因此,这种本机 Hibernate 设置可以在许多情况下替代标准 JPA LocalContainerEntityManagerFactoryBean
和JpaTransactionManager
组合,从而允许在同一本地事务中与@PersistenceContext EntityManager
旁边的SessionFactory.getCurrentSession()
(以及HibernateTemplate
)进行交互。这样的设置还提供了更强大的 Hibernate 集成和更大的配置灵 Active,因为它不受 JPA 引导 Contract 的约束。
在这种情况下,您不需要HibernateJpaVendorAdapter
配置,因为 Spring 的本机 Hibernate 设置提供了更多功能(例如,自定义 Hibernate Integrator 设置,Hibernate 5.3 Bean 容器集成以及对只读事务的更强优化)。最后但并非最不重要的一点是,您还可以通过LocalSessionFactoryBuilder
表示本机 Hibernate 设置,并与@Bean
样式配置(不涉及FactoryBean
)无缝集成。
Note
LocalSessionFactoryBean
和LocalSessionFactoryBuilder
支持后台引导,就像 JPA LocalContainerEntityManagerFactoryBean
一样。有关简介,请参见Background Bootstrapping。
在LocalSessionFactoryBean
上,可以通过bootstrapExecutor
属性使用。在程序化LocalSessionFactoryBuilder
上,重载的buildSessionFactory
方法采用引导执行程序参数。