数据访问
事务管理
对于事务的全面支持是使用 Spring Framework 的最主要原因之一。Spring Framework 为事务管理提供了统一的抽象,这带来了如下好处:
- 跨越不同 transaction API 的一致编程模型,例如 Java Transaction API(JTA),JDBC,Hibernate 和 Java Persistence API(JPA)。
- 对声明式事务管理的支持。
- 一种相比于复杂事务API(例如JTA)要更简单的编程式事务管理。
- 与 Spring 的数据访问抽象的完美整合。
以下部分描述了 Spring Framework 的事务特性和术语:
- Spring Framework 的 transaction 支持 model 的优点描述了为什么要使用 Spring Framework 的 transaction 抽象而不是 EJB Container-Managed Transactions(CMT)或选择通过专有 API 驱动本地 transactions,例如 Hibernate。
- 理解 Spring Framework transaction 抽象概述了核心 classes,并介绍了如何从各种来源配置和获取
DataSource
实例。 - 使用 transactions 同步资源描述了 application code 如何确保正确创建,重用和清理资源。
- 声明式 transaction management描述了对声明性 transaction management 的支持。
- 程序化 transaction management涵盖对程序化(即明确编码)transaction management 的支持。
- Transaction bound event描述了如何在 transaction 中使用 application events。
(该章还包括对最佳实践的讨论,application server integration和常见问题的解决方案 .)
SpringFramework的事物支持model的优点
传统上,Java EE 开发人员对 transaction management 有两种选择:global 或 local transactions,这两种选择都有很大的局限性。 Global 和 local transaction management 将在接下来的两节中进行回顾,然后讨论 Spring Framework 的 transaction management 支持如何解决 global 和 local transaction 模型的局限性。
全局事务
Global transactions 允许您使用多个 transactional 资源,通常是关系数据库和消息队列。 application 服务器通过 JTA 管理 global transactions,这是一个繁琐的 API(部分原因是它的 exception model)。此外,JTA UserTransaction
通常需要从 JNDI 获取,这意味着您还需要在 order 中使用 JNDI 来使用 JTA。 global transactions 的使用限制了 application code 的任何潜在重用,因为 JTA 通常仅在 application 服务器环境中可用。
以前,使用 global transactions 的首选方法是通过 EJB CMT(Container Managed Transaction)。 CMT 是声明性 transaction management 的一种形式(与程序性 transaction management 不同)。 EJB CMT 消除了对 transaction-related JNDI 查找的需要,尽管使用 EJB 本身需要使用 JNDI。它删除了大多数但不是全部需要编写 Java code 来控制 transactions。显着的缺点是 CMT 与 JTA 和应用程序服务器环境相关联。此外,只有在选择在 EJB 中实现业务逻辑(或至少在 transactional EJB 外观后面)时,它才可用。一般来说,EJB 的负面影响是如此之大,以至于这不是一个有吸引力的主张,特别是在面对声明性交易管理的令人信服的替代方案时。
本地事务
本地事务与资源挂钩,例如与 JDBC 关联的事务。本地事务可能更容易使用但有一个显著的缺点:不能跨多个事务资源。例如使用 JDBC 连接管理事务的代码不能在 JTA 事务中运行。对于 example,使用 JDBC 连接管理 transactions 的 code 不能在 global JTA transaction 中运行。由于 application 服务器不参与 transaction management,因此无法确保跨多个资源的正确性。 (值得注意的是,大多数 applications 使用单个 transaction resource.)另一个缺点是本地 transactions 对编程 model 是侵入性的。
SpringFramework的一致编程模型
Spring 解决了全局和本地事务的缺点。应用开发者能在任何环境中使用一致的编程模型。只需要编写一次代码,就能适用于不同环境中的各种事务管理策略。Spring Framework 同时提供了声明式和编程式的事务管理。大多是用户更喜欢声明式事务管理,这也是在大多数情况下我们推荐使用的。 通过程序化的 transaction management,开发人员可以使用 Spring Framework transaction 抽象,它可以运行任何底层的 transaction 基础架构。使用首选的声明性 model,开发人员通常只编写与 transaction management 相关的很少或没有 code,因此,不依赖于 Spring Framework transaction API 或任何其他 transaction API。
transaction management 是否需要 application 服务器?
Spring Framework 的 transaction management 支持改变了传统规则,即企业 Java application 何时需要 application 服务器。
特别是,您不需要纯粹用于通过 EJB 的声明性 transactions 的 application 服务器。实际上,即使您的 application 服务器具有强大的 JTA 功能,您也可以认为 Spring Framework 的声明性 transactions 提供了比 EJB CMT 更强大的功能和更高效的编程 model。
通常,只有当 application 需要跨多个资源处理 transactions 时才需要 application 服务器的 JTA 功能,这对于许多 applications 来说并不是必需的。许多 high-end applications 使用单个高度可伸缩的数据库(例如 Oracle RAC)。 Stand-alone transaction managers(例如Atomikos Transactions和JOTM)是其他选项。当然,您可能需要其他 application Server 功能,例如 Java Message Service(JMS)和 Java EE Connector Architecture(JCA)。
Spring Framework 让您可以选择何时将 application 扩展为完全加载的 application 服务器。使用 EJB CMT 或 JTA 的唯一替代方法是使用本地 transactions(例如 JDBC 连接上的那些)编写 code,并且如果在 global,container-managed transactions 中需要 code 到 run,则会面临大量的返工。使用 Spring Framework,只需要更改 configuration 文件中的某些 bean 定义(而不是您的 code)。
理解SpringFramework事务抽象
Spring 事务抽象的关键在于事务策略的概念。org.springframework.transaction.PlatformTransactionManager
定义了事务策略,如下所示:
1 | public interface PlatformTransactionManager { |
尽管可以在应用代码中以编程方式使用它,但它主要是服务提供者接口(SPI)。因为PlatformTransactionManager
是一个接口,所以可以根据需要轻松地模拟或存根。它与查找策略无关,例如 JNDI。 PlatformTransactionManager
implementations 的定义与 Spring Framework IoC 容器中的任何其他 object(或 bean)相同。即使你使用 JTA,只有这个好处使 Spring Framework transactions 成为一个有价值的抽象。与直接使用 JTA 相比,您可以更轻松地测试 transactional code。
同样,为了与 Spring 的哲学保持一致,PlatformTransactionManager
接口的所有方法都能抛出的TransactionException
是非受检异常(也就是说它继承java.lang.RuntimeException
)。事务基础设施故障几乎总是很严重的,只有极少数情况应用代码可以真正从事务失败中恢复,但应用开发者也可选择捕获并处理异常。。重点是开发人员不会被迫这样做。
getTransaction(..)
方法根据TransactionDefinition
参数返回TransactionStatus
对象。如果当前调用栈中有匹配的事务,则返回的TransactionStatus
代表已存在的事务,否则代表新的事务。后一种情况的含义是,与 Java EE transaction 上下文一样,TransactionStatus
与执行线程相关联。
TransactionDefinition
接口指定:
- 传播:通常,transaction 范围内执行的所有 code 都在 transaction 中运行。但是,如果 transaction context 已存在,则执行 transactional 方法时,可以指定行为。例如,运行在已经存在的事务中(这是通常情况),或暂停已存在的事务并创建新事务。 Spring 提供 EJB CMT 中熟悉的所有 transaction 传播选项。要阅读 Spring 中 transaction 传播的语义,请参阅Transaction 传播。
- 隔离:此 transaction 与其他 transactions 的工作隔离的程度。对于 example,这个 transaction 可以看到来自其他 transactions 的未提交的写入吗?
- 超时:transaction 如何在超时之前运行并由底层 transaction 基础架构自动回滚。
- Read-only status:当只需要读不需要写时,可以使用只读事务。只读事务可在某些场景中用于优化,例如使用 Hibernate 时。
这些设置反映了标准的 transactional 概念。如有必要,请参阅讨论 transaction 隔离级别和其他核心 transaction 概念的资源。理解这些概念对于使用 Spring Framework 或任何 transaction management 解决方案至关重要。
TransactionStatus
接口为编写事务代码以控制事务执行和查询事务状态提供了一种更简单的方式。这些概念应该不陌生,因为它们对于所有的事务 API 来说都是常见的。以下清单显示了TransactionStatus
接口:
1 | public interface TransactionStatus extends SavepointManager { |
无论是选择声明式还是编程式事务管理,定义正确的PlatformTransactionManager
都是绝对必要的。通常通过依赖注入定义其实现。
PlatformTransactionManager
implementations 通常需要了解它们工作的环境:JDBC,JTA,Hibernate 等。以下示例显示如何定义本地PlatformTransactionManager
implementation(在本例中,使用 plain JDBC.)
您可以通过 creating bean 来定义 JDBC DataSource
,类似于以下内容:
1 | <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> |
然后,相关的PlatformTransactionManager
bean 定义具有DataSource
定义的 reference。它应该类似于以下 example:
1 | <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> |
如果在 Java EE 容器中使用 JTA,则使用通过 JNDI 获得的容器DataSource
和 Spring 的JtaTransactionManager
。以下 example 显示了 JTA 和 JNDI 查找 version 的外观:
1 |
|
JtaTransactionManager
不需要知道DataSource
(或任何其他特定资源),因为它使用容器的 global transaction management 基础结构。
前面的
dataSource
bean 定义使用jee
命名空间中的<jndi-lookup/>
标记。有关更多信息,请参阅JEE Schema。
您还可以轻松使用 Hibernate local transactions,如以下示例所示。在这种情况下,您需要定义 Hibernate LocalSessionFactoryBean
,application code 可以使用它来获取 Hibernate Session
实例。
DataSource
bean 定义类似于前面显示的本地 JDBC example,因此,未在以下 example 中显示。
如果
DataSource
(由任何 non-JTA transaction manager 使用)通过 JNDI 查找并由 Java EE 容器管理,它应该是 non-transactional,因为 Spring Framework(而不是 Java EE 容器)管理 transactions。
在这种情况下,txManager
bean 是HibernateTransactionManager
类型。与DataSourceTransactionManager
需要的DataSource
相同,HibernateTransactionManager
需要的 reference。以下 example 声明sessionFactory
和txManager
beans:
1 | <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> |
如果使用 Hibernate 和 Java EE container-managed JTA transactions,则应使用与之前的 JTA example 相同的JtaTransactionManager
,如下面的 example 所示:
1 | <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> |
如果使用 JTA,无论使用何种数据访问技术,无论是 JDBC,Hibernate JPA 还是任何其他支持的技术,transaction manager 定义应该看起来都一样。这是因为 JTA transactions 是 global transactions,它可以登记任何 transactional 资源。
在所有这些情况下,application code 不需要更改。您可以仅通过更改 configuration 来更改 transactions 的管理方式,即使该更改意味着从本地移动到 global transactions,反之亦然。
使用Transactions同步资源
如何创建不同的 transaction managers 以及它们如何链接到需要同步到 transactions 的相关资源(对于 example DataSourceTransactionManager
到 JDBC DataSource
,HibernateTransactionManager
到 Hibernate SessionFactory
等等)现在应该是清楚的。本节描述了 application code(直接或间接使用诸如 JDBC,Hibernate 或 JPA 之类的持久性 API)如何确保正确创建,重用和清理这些资源。本节还讨论了如何通过相关的PlatformTransactionManager
触发 transaction 同步(可选)。
High-level同步方法
首选方法是使用基于 Spring 的 highest-level 模板的持久性 integration API,或者使用带有 transaction-aware factory beans 或代理的本机 ORM API 来管理本机资源工厂。这些 transaction-aware 解决方案在内部处理资源创建和重用,清理,资源的可选 transaction 同步和 exception 映射。因此,用户数据访问 code 不必解决这些任务,但可以完全专注于 non-boilerplate 持久性逻辑。通常,您使用本机 ORM API 或使用模板方法使用JdbcTemplate
进行 JDBC 访问。这些解决方案将在本参考文档的后续章节中详细介绍。
Low-level同步方法
(用于 JDBC),EntityManagerFactoryUtils
(用于 JPA),SessionFactoryUtils
(用于 Hibernate)等类存在于较低的 level 中。如果希望 application code 直接处理本机持久性 API 的资源类型,可以使用这些 classes 来确保获得正确的 Spring Framework-managed 实例,transactions(可选)同步,并且 process 中出现的 exceptions 正确映射一致的 API。
例如,对于 JDBC,不是传统的 JDBC 方法在DataSource
上调用getConnection()
方法,而是使用 Spring 的org.springframework.jdbc.datasource.DataSourceUtils
class,如下所示:
1 | Connection conn = DataSourceUtils.getConnection(dataSource); |
如果现有 transaction 已经与其同步(链接)了连接,则返回该实例。否则,方法调用会触发创建新连接,该连接(可选)与任何现有的 transaction 同步,并可在后续的同一 transaction 中重用。如前所述,任何SQLException
都包含在 Spring Framework CannotGetJdbcConnectionException
中,这是 Spring Framework 的未经检查的DataAccessException
类型的层次结构之一。这种方法为您提供了比从SQLException
轻松获得的更多信息,并确保跨数据库甚至跨不同持久性技术的可移植性。
这种方法也可以在没有 Spring transaction management(transaction 同步是可选的)的情况下工作,因此无论是否使用 Spring 进行 transaction management 都可以使用它。
当然,一旦你使用了 Spring 的 JDBC 支持,JPA 支持或 Hibernate 支持,你通常不喜欢使用DataSourceUtils
或其他帮助 classes,因为你通过 Spring 抽象而不是直接使用相关 API 更快乐。例如,如果您使用 Spring JdbcTemplate
或jdbc.object
包来简化 JDBC 的使用,则在幕后进行正确的连接检索,您无需编写任何特殊的 code。
类TransactionAwareDataSourceProxy
在最低 level 存在TransactionAwareDataSourceProxy
class。这是目标DataSource
的代理,它包装目标DataSource
以添加 Spring-managed transactions 的意识。在这方面,它类似于 Java EE 服务器提供的 transactional JNDI DataSource
。
您几乎从不需要或不想使用此 class,除非必须调用现有的 code 并传递标准的 JDBC DataSource
接口 implementation。在这种情况下,此 code 可能可用但参与 Spring-managed transactions。您可以使用前面提到的 higher-level 抽象编写新的 code。
声明式事务管理
大多数 Spring Framework 用户选择声明式 transaction management。此选项对 application code 的影响最小,因此与 non-invasive 轻量级容器的理想最为一致。
使用 Spring aspect-oriented 编程(AOP)可以实现 Spring Framework 的声明式 transaction management。但是,由于 transactional 方面 code 附带 Spring Framework 分布并且可能以样板方式使用,因此通常不必理解 AOP 概念以有效地使用此 code。
Spring Framework 的声明性 transaction management 类似于 EJB CMT,因为您可以将 transaction 行为(或缺少行为)指定为单个方法 level。如有必要,您可以在 transaction context 中进行setRollbackOnly()
调用。两种 transaction management 之间的区别是:
- 与绑定到 JTA 的 EJB CMT 不同,Spring Framework 的声明性 transaction management 可以在任何环境中使用。通过调整 configuration files,它可以使用 JDBC,JPA 或 Hibernate 与 JTA transactions 或 local transactions 一起使用。
- 您可以将 Spring Framework 声明 transaction management 应用于任何 class,而不仅仅是特殊的 classes,例如 EJB。
- Spring Framework 提供声明性回滚规则,一个没有 EJB 等效的 feature。提供了对回滚规则的编程和声明性支持。
- Spring Framework 允许您使用 AOP 自定义 transactional 行为。对于 example,您可以在 transaction 回滚的情况下插入自定义行为。您还可以添加任意建议以及 transactional 建议。使用 EJB CMT,除了
setRollbackOnly()
之外,您不能影响容器的事务管理。 - Spring Framework 不支持跨 remote calls 传播 transaction 上下文,就像 high-end application 服务器那样。如果您需要此 feature,我们建议您使用 EJB。但是,在使用这样的 feature 之前要仔细考虑,因为通常情况下,人们不希望 transactions 对 span remote calls。
TransactionProxyFactoryBean 在哪里?
Spring 2.0 及以上版本中的声明性 transaction configuration 与以前版本的 Spring 有很大不同。主要区别在于不再需要配置TransactionProxyFactoryBean
beans。
pre-Spring 2.0 configuration 样式仍然是 100%有效的 configuration。将新的<tx:tags/>
视为为您定义TransactionProxyFactoryBean
beans。
回滚规则的概念很重要。它们允许您指定哪些 exceptions(和 throwables)应该导致自动回滚。您可以在 configuration 中以声明方式指定,而不是在 Java code 中。因此,虽然您仍然可以在TransactionStatus
object 上调用setRollbackOnly()
来回滚当前的 transaction,但大多数情况下您可以指定MyApplicationException
必须始终导致回滚的规则。此选项的显着优势是业务 objects 不依赖于 transaction 基础结构。例如,它们通常不需要 import Spring transaction API 或其他 Spring API。
尽管 EJB 容器默认行为会自动回滚系统 exception(通常是运行时 exception)上的 transaction,但 EJB CMT 不会在 application exception(即java.rmi.RemoteException
以外的已检查 exception)上自动回滚 transaction。虽然声明性 transaction management 的 Spring 默认行为遵循 EJB 约定(回滚仅在未经检查的 exceptions 上自动回放),但定制此行为通常很有用。
理解SpringFramework的声明事物实现
仅仅告诉您使用@Transactional
注释您的类,将@EnableTransactionManagement
添加到您的配置中,并期望您理解它是如何工作的,这是不够的。为了加深理解,本节解释了Spring框架声明式事务基础结构在与事务相关的问题上下文中的内部工作方式。
关于 Spring Framework 的声明性 transaction 支持,要掌握的最重要的概念是,这种支持是通过 AOP 代理启用的,而 transactional 建议是由元数据驱动的(目前是 XML-或 annotation-based)。 AOP 与 transactional 元数据的组合产生一个 AOP 代理,它使用TransactionInterceptor
和适当的PlatformTransactionManager
implementation 来驱动方法调用的 transactions。
Spring AOP 涵盖在AOP 部分中。
以下图像显示了在 transactional 代理上调用方法的概念视图:
声明式事务实现示例
考虑以下接口及其伴随 implementation。此 example 使用Foo
和Bar
classes 作为占位符,以便您可以专注于 transaction 用法,而无需关注特定的域 model。出于本示例的目的,DefaultFooService
class 在每个实现的方法的主体中抛出UnsupportedOperationException
实例的事实是好的。通过该行为,您可以看到 transactions 被创建,然后回滚以响应UnsupportedOperationException
实例。以下清单显示了FooService
接口:
1 | // the service interface that we want to make transactional |
以下 example 显示了前面接口的 implementation:
1 | package x.y.service; |
假设FooService
接口的前两个方法getFoo(String)
和getFoo(String, String)
必须在具有 read-only 语义的 transaction 的 context 中执行,并且其他方法insertFoo(Foo)
和updateFoo(Foo)
必须在具有 read-write 语义的 transaction 的 context 中执行。以下 configuration 将在接下来的几段中详细解释:
1 | <!-- from the file 'context.xml' --> |
检查前面的 configuration。它假定您要创建服务 object,fooService
bean,transactional。要应用的 transaction 语义封装在<tx:advice/>
定义中。 <tx:advice/>
定义读作“所有方法,从get
开始,将在 read-only transaction 的 context 中执行,所有其他方法将使用默认的 transaction 语义执行”。 <tx:advice/>
标记的transaction-manager
属性设置为PlatformTransactionManager
bean 的 name,它将驱动 transactions(在本例中为txManager
bean)。
如果要连接的
PlatformTransactionManager
的 bean name 具有 nametransactionManager
,则可以省略 transactional 通知(<tx:advice/>
)中的transaction-manager
属性。如果要连接的PlatformTransactionManager
bean 具有任何其他 name,则必须显式使用transaction-manager
属性,如前面的 example 中所示。
<aop:config/>
定义确保txAdvice
bean 定义的 transactional 通知在程序中的适当位置执行。首先,定义一个切入点,该切入点与FooService
接口(fooServiceOperation
)中定义的任何操作的执行相匹配。然后使用顾问程序将切入点与txAdvice
相关联。结果表明,在执行fooServiceOperation
时,txAdvice
定义的建议是 run。
<aop:pointcut/>
元素中定义的表达式是 AspectJ 切入点表达式。有关 Spring 中切入点表达式的更多详细信息,请参阅AOP 部分。
common 要求是创建整个服务层 transactional。执行此操作的最佳方法是将切入点表达式更改为 match 服务层中的任何操作。以下 example 显示了如何执行此操作:
1 | <aop:config> |
在前面的示例中,假设所有服务接口都在
x.y.service
包中定义。有关详细信息,请参阅AOP 部分。
现在我们已经分析了 configuration,你可能会问自己,“所有这些 configuration 实际上做了什么?”
前面显示的 configuration 用于围绕从fooService
bean 定义创建的 object 创建 transactional 代理。代理配置了 transactional 通知,以便在代理上调用适当的方法时,transaction 被启动,挂起,标记为 read-only,依此类推,具体取决于与该方法关联的 transaction configuration。考虑以下测试驱动前面显示的 configuration 的程序:
1 | public final class Boot { |
running 前面程序的输出应类似于以下内容(Log4J 输出和 DefaultFooService class 的 insertFoo(..)方法抛出的 UnsupportedOperationException 中的堆栈跟踪已被截断,以便清楚):
1 | <!-- the Spring container is starting up... --> |
回滚声明式Transaction
上一节概述了如何在 application 中以声明方式为 classes(通常是服务层 classes)指定 transactional 设置的基础知识。本节介绍如何以简单的声明方式控制 transactions 的回滚。
向 Spring Framework 的 transaction 基础结构指示 transaction 的工作将被回滚的推荐方法是从当前在 transaction 的 context 中执行的 code 中抛出Exception
。 Spring Framework 的 transaction infrastructure code 捕获任何未处理的Exception
,因为它会使调用堆栈冒泡并确定是否标记 transaction 以进行回滚。
在默认的 configuration 中,Spring Framework 的 transaction infrastructure code 仅在运行时未经检查的 exceptions 的情况下标记 transaction 以进行回滚。也就是说,抛出的 exception 是RuntimeException
的实例或子类。 (默认情况下,Error
实例也会导致回滚)。从 transactional 方法抛出的已检查 exceptions 不会导致默认 configuration 中的回滚。
您可以准确配置哪些Exception
类型标记 transaction 以进行回滚,包括已检查的 exceptions。以下 XML 代码段演示了如何为已检查的 application-specific Exception
类型配置回滚:
1 | <tx:advice id="txAdvice" transaction-manager="txManager"> |
如果您不希望在抛出 exception 时回滚 transaction,您还可以指定“no rollback rules”。以下 example 告诉 Spring Framework 的 transaction 基础设施提交服务员 transaction,即使面对未处理的InstrumentNotFoundException
:
1 | <tx:advice id="txAdvice"> |
当 Spring Framework 的 transaction 基础结构捕获 exception 并且它查询配置的回滚规则以确定是否标记 transaction 进行回滚时,最强匹配规则获胜。因此,在以下 configuration 的情况下,除了InstrumentNotFoundException
之外的任何 exception 都会导致响尾符 transaction 的回滚:
1 | <tx:advice id="txAdvice"> |
您还可以以编程方式指示所需的回滚。虽然很简单,但这个 process 非常具有侵入性,并且会将 code 紧密地耦合到 Spring Framework 的 transaction 基础架构。以下 example 显示了如何以编程方式指示所需的回滚:
1 | public void resolvePosition() { |
如果可能的话,强烈建议您使用声明性方法进行回滚。如果您绝对需要程序化回滚,则可以使用程序化回滚,但它的使用方式可以实现干净的 POJO-based architecture。
为不同的Beans配置不同的Transactional语义
考虑具有多个服务层 objects 的场景,并且您希望对它们中的每一个应用完全不同的 transactional configuration。您可以通过定义具有不同pointcut
和advice-ref
属性值的不同<aop:advisor/>
元素来实现。
作为比较,首先假设您的所有服务层 classes 都在根x.y.service
包中定义。要使所有 beans 成为在该包(或子包中)中定义的 classes 实例并且名称以Service
结尾,并且具有默认的 transactional configuration,您可以编写以下内容:
1 |
|
以下 example 显示了如何使用完全不同的 transactional 设置配置两个不同的 beans:
1 |
|
设置
本节总结了您可以使用<tx:advice/>
标记指定的各种 transactional 设置。默认的<tx:advice/>
设置为:
- 传播设置是
REQUIRED.
- 隔离 level 是
DEFAULT.
- transaction 是 read-write。
- transaction 超时默认为基础 transaction 系统的默认超时,如果不支持超时,则默认为 none。
- 任何
RuntimeException
触发回滚,任何已检查的Exception
都没有。
您可以更改这些默认设置。以下 table 总结了嵌套在<tx:advice/>
和<tx:attributes/>
标记内的<tx:method/>
标记的各种属性:
属性 | 需要? | 默认 | 描述 |
---|---|---|---|
name |
是 | 与 transaction 属性关联的方法名称。通配符()字符可用于将相同的 transaction 属性设置与多个方法相关联(对于 example,`get, handle, onEvent`等)。 |
|
propagation |
没有 | REQUIRED |
Transaction 传播行为。 |
isolation |
没有 | DEFAULT |
Transaction isolation level。仅适用于REQUIRED 或REQUIRES_NEW 的传播设置。 |
timeout |
没有 | -1 | Transaction timeout(秒)。仅适用于传播REQUIRED 或REQUIRES_NEW 。 |
read-only |
没有 | 假 | Read-write 与 read-only transaction。仅适用于REQUIRED 或REQUIRES_NEW 。 |
rollback-for |
没有 | Comma-delimited 触发回滚的Exception 实例列表。对于 example,com.foo.MyBusinessException,ServletException. |
|
no-rollback-for |
没有 | Comma-delimited 不触发回滚的Exception 实例列表。对于 example,com.foo.MyBusinessException,ServletException. |
使用Transactional注解
除_tra_action configuration 的 XML-based 声明方法之外,您还可以使用 annotation-based 方法。直接在 Java source code 中声明 transaction 语义会使声明更接近受影响的 code。没有太多的过度耦合的危险,因为无论如何,用于交易使用的 code 几乎总是以这种方式部署。
标准
javax.transaction.Transactional
annotation 也被支持为 Spring 自己的 annotation 的 drop-in 替换。有关更多详细信息,请参阅 JTA 1.2 文档。
使用@Transactional
annotation 提供的 ease-of-use 最好用 example 说明,后面的文字对此进行了解释。考虑以下 class 定义:
1 | // the service class that we want to make transactional |
如上所述在 class level 中使用,annotation 指示声明 class(及其子类)的所有方法的默认值。或者,每个方法都可以单独注释。请注意,class-level annotation 不适用于 class 层次结构的祖先 classes;在这种情况下,需要在 order 中本地重新声明方法以参与 subclass-level annotation。
当如上所述的 POJO class 在 Spring context 中定义为 bean 时,可以通过@Configuration
class 中的@EnableTransactionManagement
annotation 创建 bean 实例 transactional。有关详细信息,请参阅javadoc。
在 XML configuration 中,<tx:annotation-driven/>
标签提供了类似的便利:
1 | <!-- from the file 'context.xml' --> |
1 | 使 bean 实例 transactional 的 line。 |
如果要连接的
PlatformTransactionManager
的 bean name 具有 name,transactionManager
,则可以省略<tx:annotation-driven/>
标记中的transaction-manager
属性。如果要的bean 具有任何其他 name,则必须使用transaction-manager
属性,如前面的 example 中所示。
方法可见性和@Transactional
使用代理时,应仅将@Transactional
annotation 应用于具有公共可见性的方法。如果使用@Transactional
annotation 注释 protected,private 或 package-visible 方法,则不会引发错误,但带注释的方法不会显示已配置的 transactional 设置。如果需要注释 non-public 方法,请考虑使用 AspectJ(稍后介绍)。
您可以将@Transactional
annotation 应用于接口定义,接口上的方法,class 定义或 class 上的公共方法。但是,仅仅存在@Transactional
annotation 不足以激活 transactional 行为。 @Transactional
annotation 只是元数据,可以被@Transactional
-aware 的某些运行时基础结构使用,并且可以使用元数据来配置具有 transactional 行为的相应 beans。在前面的 example 中,<tx:annotation-driven/>
元素会切换 transactional 行为。
Spring 团队建议您使用
@Transactional
annotation 仅注释具体的 classes(以及具体 classes 的方法),而不是注释接口。您当然可以将@Transactional
annotation 放在接口(或接口方法)上,但这只能在您使用 interface-based 代理时按预期工作。 Java annotations 不是从接口继承的事实意味着,如果使用 class-based 代理(proxy-target-class="true"
)或 weaving-based aspect(mode="aspectj"
),代理和编织基础结构无法识别 transaction 设置,并且 object 不包含在 transactional 代理。在代理模式(默认设置)下,只拦截通过代理进入的外部方法 calls。这意味着 self-invocation(实际上,目标 object 中的一个方法调用目标 object 的另一个方法)在运行时不会导致实际的 transaction,即使被调用的方法用
@Transactional
标记。此外,必须完全初始化代理以提供预期的行为,因此您不应该在初始化 code(即@PostConstruct
)中依赖此 feature。
如果您希望 self-invocations 也包含 transactions,请考虑使用 AspectJ 模式(请参阅以下 table 中的mode
属性)。在这种情况下,首先没有代理。相反,目标 class 被编织(即,其字节 code 被修改)以在任何类型的方法上将@Transactional
转换为运行时行为。
XML 属性 | Annotation 属性 | 默认 | 描述 |
---|---|---|---|
transaction-manager |
N/A(见TransactionManagementConfigurer javadoc) | transactionManager |
_transaction manager 要使用的名称。仅当 transaction manager 的 name 不是transactionManager 时才需要,如前面的 example 中所示。 |
mode |
mode |
proxy |
默认模式(proxy )通过使用 Spring 的 AOP framework 处理带注释的 beans 代理(遵循代理语义,如前所述,仅适用于通过代理进入的方法 calls)。替代模式(aspectj )使用 Spring 的 AspectJ transaction aspect 编织受影响的 classes,修改目标 class byte code 以应用于任何类型的方法调用。 AspectJ 编织在 classpath 中需要spring-aspects.jar 以及启用 load-time 编织(或 compile-time 编织)。 (有关如何设置 load-time weaving.)的详细信息,请参阅Spring configuration |
proxy-target-class |
proxyTargetClass |
false |
仅适用于proxy 模式。控制为使用@Transactional annotation 注释的 classes 创建的 transactional 代理类型。如果proxy-target-class 属性设置为true ,则会创建 class-based 个代理。如果proxy-target-class 是false 或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理人的详细检查,请参阅代理机制 types.) |
order |
order |
Ordered.LOWEST_PRECEDENCE |
定义应用于使用@Transactional 注释的 beans 的 transaction 建议的 order。 (有关与 AOP 建议的 ordering 相关的规则的更多信息,请参阅通知顺序)没有指定的 ordering 意味着 AOP 子系统确定建议的 order。 |
处理
@Transactional
annotations 的默认建议模式是proxy
,它允许仅通过代理拦截 calls。同一 class 中的本地 calls 不能以这种方式截获。对于更高级的拦截模式,请考虑结合 compile-time 或 load-time 编织切换到aspectj
模式。
proxy-target-class
属性控制为使用@Transactional
annotation 注释的 classes 创建的 transactional 代理类型。如果proxy-target-class
设置为true
,则会创建 class-based 个代理。如果proxy-target-class
是false
或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理的讨论,请参阅aop-proxying types.)
@EnableTransactionManagement
和<tx:annotation-driven/>
仅在定义它们的同一 application context 中的 beans 上查找@Transactional
。这意味着,如果您将 annotation-driven configuration 放在WebApplicationContext
中DispatcherServlet
,它只会在您的控制器而不是您的服务中检查@Transactional
beans。有关更多信息,请参见MVC。
在评估方法的 transactional 设置时,派生的位置优先。在以下 example 的情况下,DefaultFooService
class 在 class level 中使用 read-only transaction 的设置进行注释,但同一 class 中updateFoo(Foo)
方法上的@Transactional
annotation 优先于 class level 中定义的 transactional 设置。
1 |
|
Transactional注解设置
@Transactional
annotation 是元数据,指定接口,class 或方法必须具有 transactional 语义(对于 example,“在调用此方法时启动全新的 read-only transaction,暂停任何现有的 transaction”)。默认的@Transactional
设置如下:
- 传播设置为
PROPAGATION_REQUIRED.
- 隔离 level 是
ISOLATION_DEFAULT.
- transaction 是 read-write。
- transaction 超时默认为基础 transaction 系统的默认超时,如果不支持超时,则为 none。
- 任何
RuntimeException
触发回滚,任何已检查的Exception
都没有。
您可以更改这些默认设置。以下 table 总结了@Transactional
annotation 的各种 properties:
属性 | 类型 | 描述 |
---|---|---|
值 | String |
可选限定符,指定要使用的 transaction manager。 |
传播 | enum : Propagation |
可选的传播设置。 |
isolation |
enum : Isolation |
可选隔离 level。仅适用于REQUIRED 或REQUIRES_NEW 的传播值。 |
timeout |
int (以粒度为单位) |
可选的 transaction 超时。仅适用于REQUIRED 或REQUIRES_NEW 的传播值。 |
readOnly |
boolean |
Read-write 与 read-only transaction。仅适用于REQUIRED 或REQUIRES_NEW 的值。 |
rollbackFor |
_A_ray Class objects,必须从Throwable. 派生 |
可选的 exception exception classes 必须导致回滚。 |
rollbackForClassName |
_Alass 类的 class 名称。 classes 必须从Throwable. 派生 |
exception classes 名称的可选 array 必须导致回滚。 |
noRollbackFor |
_A_ray Class objects,必须从Throwable. 派生 |
可选 array 的 exception classes,不得导致回滚。 |
noRollbackForClassName |
Array 的String class 名称,必须从Throwable. 派生 |
可选 array 的 exception classes 名称,不得导致回滚。 |
目前,您无法明确控制 transaction 的 name,其中’name’表示 transaction 监视器中出现的 transaction name(如果适用)(对于 example,WebLogic 的 transaction 监视器)和 logging 输出。对于声明式 transactions,transaction name 始终是 fully-qualified class name .
事务建议 class 的方法 name。例如,如果BusinessService
class 的handlePayment(..)
方法启动 transaction,则 transaction 的 name 将为:com.example.BusinessService.handlePayment
。
使用Transactional注解的多个事务管理器
大多数 Spring applications 只需要一个 transaction manager,但是在某些 application 中你可能需要多个独立的 transaction managers。您可以使用@Transactional
annotation 的value
属性来选择性地指定要使用的PlatformTransactionManager
的标识。这可以是 bean name 或 transaction manager bean 的限定符 value。对于 example,使用限定符表示法,可以将以下 Java code 与 application context 中的以下 transaction manager bean 声明组合使用:
1 | public class TransactionalService { |
以下清单显示了 bean 声明:
1 | <tx:annotation-driven/> |
在这种情况下,TransactionalService
run 上的两个方法在单独的 transaction managers 下,由order
和account
限定符区分。如果未找到特定限定的PlatformTransactionManager
bean,则仍使用默认的<tx:annotation-driven>
目标 bean name,transactionManager
。
自定义复合注解
如果您发现在许多不同方法上重复使用与@Transactional
相同的属性,则Spring 的 meta-annotation 支持允许您为特定用例定义自定义快捷方式注释。对于 example,请考虑以下 annotation 定义:
1 |
|
前面的注释允许我们编写上一节中的 example,如下所示:
1 | public class TransactionalService { |
在前面的示例中,我们使用语法来定义 transaction manager 限定符,但我们也可以包含传播行为,回滚规则,超时和其他 features。
Transaction传播
本节描述了 Spring 中 transaction 传播的一些语义。请注意,本节不是 transaction 传播的简介。相反,它详细介绍了 Spring 中有关 transaction 传播的一些语义。
在 Spring-managed transactions 中,请注意物理和逻辑 transactions 之间的区别,以及传播设置如何应用于此差异。
了解PROPAGATION_REQUIRED
如果还没有 transaction 存在,或者参与为更大范围定义的现有“外部”transaction,则PROPAGATION_REQUIRED
对当前作用域本地执行物理 transaction。这是同一线程中 common 调用堆栈排列的一个很好的默认值(对于 example,一个服务门面,它委托给几个 repository 方法,其中所有底层资源都必须参与 service-level transaction)。
默认情况下,参与的 transaction 连接外部作用域的特征,静默忽略本地隔离 level,timeout value 或 read-only flag(如果有)。如果希望在参与具有不同隔离 level 的现有 transaction 时拒绝隔离 level 声明,请考虑将
validateExistingTransactions
flag 切换到 transactionmanager 上的true
。此 non-lenient 模式也拒绝 read-only 不匹配(即,尝试参与 read-only 外部范围的内部 read-write transaction)。
当传播设置为PROPAGATION_REQUIRED
时,将为应用该设置的每个方法创建逻辑 transaction 范围。每个这样的逻辑 transaction 范围可以单独确定 rollback-only status,外部 transaction 范围在逻辑上独立于内部 transaction 范围。在标准PROPAGATION_REQUIRED
行为的情况下,所有这些范围都映射到相同的物理 transaction。因此,内部 transaction 范围中的 rollback-only 标记集确实会影响外部 transaction 实际提交的机会。
但是,在内部 transaction 范围设置 rollback-only 标记的情况下,外部 transaction 尚未决定回滚本身,因此回滚(由内部 transaction 范围静默触发)是意外的。此时抛出相应的UnexpectedRollbackException
。这是预期的行为,因此 transaction 的调用者永远不会被误导,假设在实际上没有执行提交。因此,如果内部 transaction(外部调用者不知道)默认将 transaction 标记为 rollback-only,则外部调用者仍然会 calls commit。外部调用者需要接收UnexpectedRollbackException
以清楚地指示已执行回滚。
了解PROPAGATION_REQUIRES_NEW
与PROPAGATION_REQUIRED
相比,PROPAGATION_REQUIRES_NEW
始终对每个受影响的 transaction 范围使用独立的物理 transaction,从不参与外部范围的现有 transaction。在这样的安排中,底层资源 transactions 是不同的,因此可以独立提交或回滚,外部 transaction 不受内部 transaction 的回滚状态的影响,并且内部 transaction 的锁在完成后立即释放。这样一个独立的内部 transaction 也可以声明它自己的隔离 level,timeout 和 read-only 设置,而不是继承外部 transaction 的特性。
了解PROPAGATION_NESTED
PROPAGATION_NESTED
使用具有多个保存点的单个物理 transaction,它可以回滚到。这样的部分回滚允许内部 transaction 范围触发其范围的回滚,外部 transaction 能够继续物理 transaction,尽管已经回滚了一些操作。此设置通常映射到 JDBC 保存点,因此它仅适用于 JDBC 资源 transactions。见 Spring 的DataSourceTransactionManager 对象。
建议事物操作
假设您要执行 transactional 操作和一些基本的分析建议。你如何在<tx:annotation-driven/>
的 context 中实现这一点?
当您调用updateFoo(Foo)
方法时,您希望看到以下操作:
- 配置的分析 aspect 启动。
- transactional 建议执行。
- 建议 object 上的方法执行。
- transaction 提交。
- 分析 aspect 报告整个 transactional 方法调用的确切持续时间。
本章不涉及详细解释 AOP(除非适用于 transactions)。有关 AOP configuration 和 AOP 的详细介绍,请参见AOP。
以下 code 显示了前面讨论过的简单分析 aspect:
1 | package x.y; |
建议的 ordering 通过Ordered
接口控制。有关建议 ordering 的详细信息,请参阅建议 ordering。
以下 configuration 创建一个fooService
bean,在所需的 order 中应用了 profiling 和 transactional 方面:
1 |
|
您可以以类似的方式配置任意数量的其他方面。
以下 example 创建与前两个示例相同的设置,但使用纯 XML 声明方法:
1 |
|
前面的 configuration 的结果是fooService
bean,它在该 order 中应用了 profiling 和 transactional 方面。如果您希望在关于出路的 transactional 建议之前和之前的 transactional 建议之后执行分析建议,您可以交换分析 aspect bean 的order
property 的 value,使其高于 transactional advice 的 order value。
您可以以类似的方式配置其他方面。
在AspectJ中使用Transactional注解
您还可以通过 AspectJ aspect 在 Spring 容器外部使用 Spring Framework 的@Transactional
支持。为此,首先使用@Transactional
annotation 注释 classes(以及可选的 classes’方法),然后将 application 与spring-aspects.jar
文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect
链接(编织)。您还必须使用 transaction manager 配置 aspect。您可以使用 Spring Framework 的 IoC 容器来处理 dependency-injecting aspect。配置 transaction management aspect 的最简单方法是使用<tx:annotation-driven/>
元素并将mode
属性指定为aspectj
,如使用 @Transactional中所述。因为我们专注于在 Spring 容器之外运行的 applications,我们将向您展示如何以编程方式执行此操作。
在继续之前,您可能需要分别阅读使用 @Transactional和AOP。
以下 example 显示了如何创建 transaction manager 并配置AnnotationTransactionAspect
以使用它:
1 | // construct an appropriate transaction manager |
使用此 aspect 时,必须注解实现class(或该 class 中的方法或两者),而不是 class 实现的接口(如果有)。 AspectJ 遵循 Java 的规则,即接口上的注释不会被继承。
class 上的@Transactional
annotation 指定了 class 中执行任何公共方法的默认 transaction 语义。
class 中的方法上的@Transactional
annotation 覆盖 class annotation(如果存在)给出的默认 transaction 语义。无论可见性如何,您都可以注释任何方法。
要使用AnnotationTransactionAspect
编写 applications,您必须使用 AspectJ 构建 application(请参阅AspectJ 开发指南)或使用 load-time 编织。有关使用 AspectJ 进行 load-time 编织的讨论,请参阅Load-time 在 Spring Framework 中使用 AspectJ 进行编织。
编程式事物管理
Spring Framework 提供了两种程序化 transaction management 方法,使用:
TransactionTemplate
。- 直接
PlatformTransactionManager
implementation。
Spring 团队通常建议使用TransactionTemplate
进行程序化事务管理。第二种方法类似于使用 JTA UserTransaction
API,尽管 exception 处理不那么麻烦。
使用TransactionTemplate
TransactionTemplate
采用与其他 Spring 模板相同的方法,例如JdbcTemplate
。它使用回调方法(免费 application code 从必须进行样板采集和释放 transactional 资源)并导致 code 被意图驱动,因为你的 code 只关注你想要做的事情。
如下面的示例所示,使用
TransactionTemplate
绝对会将您与 Spring 的 transaction 基础结构和 API 相结合。程序化 transaction management 是否适合您的开发需求是您必须自己做出的决定。
Application code 必须在 transactional context 中执行,并且显式使用TransactionTemplate
类似于下一个 example。作为 application 开发人员,您可以编写TransactionCallback
implementation(通常表示为匿名内部 class),其中包含您需要在 transaction 的 context 中执行的 code。然后,您可以将自定义TransactionCallback
的实例传递给TransactionTemplate
上公开的execute(..)
方法。以下 example 显示了如何执行此操作:
1 | public class SimpleService implements Service { |
如果没有 return value,则可以使用带有匿名 class 的方便的TransactionCallbackWithoutResult
class,如下所示:
1 | transactionTemplate.execute(new TransactionCallbackWithoutResult() { |
回调中的 Code 可以通过调用提供的TransactionStatus
object 上的setRollbackOnly()
方法来回滚 transaction,如下所示:
1 | transactionTemplate.execute(new TransactionCallbackWithoutResult() { |
指定Transaction设置
您可以在TransactionTemplate
上以编程方式或在 configuration 中指定 transaction 设置(例如传播模式,隔离 level,超时等)。默认情况下,TransactionTemplate
实例具有默认 transactional 设置。以下 example 显示了特定TransactionTemplate:
的 transactional 设置的编程自定义
1 | public class SimpleService implements Service { |
以下 example 通过使用 Spring XML configuration 定义带有一些自定义 transactional 设置的TransactionTemplate
:
1 | <bean id="sharedTransactionTemplate" |
然后,您可以_将sharedTransactionTemplate
注入所需数量的服务。
最后,TransactionTemplate
class 的实例是 thread-safe,在这种情况下不保持任何会话 state。但是,TransactionTemplate
实例会保持 configuration state。因此,虽然许多 classes 可以共享TransactionTemplate
的单个实例,但如果 class 需要使用具有不同设置的TransactionTemplate
(对于 example,不同的隔离 level),则需要创建两个不同的TransactionTemplate
实例。
使用PlatformTransactionManager
您也可以直接使用org.springframework.transaction.PlatformTransactionManager
来管理 transaction。为此,请通过 bean reference 将您使用的PlatformTransactionManager
的_imple 实现传递给 bean。然后,通过使用TransactionDefinition
和TransactionStatus
objects,您可以启动 transactions,回滚和提交。以下 example 显示了如何执行此操作:
1 | DefaultTransactionDefinition def = new DefaultTransactionDefinition(); |
在编程式和声明式之间选择事务管理
只有少数 transactional 操作,程序化 transaction management 通常是一个很好的 idea。例如,如果您的 web application 只需要 transactions 用于某些更新操作,您可能不希望使用 Spring 或任何其他技术设置 transactional 代理。在这种情况下,使用TransactionTemplate
可能是一个很好的方法。能够明确地设置 transaction name 也是可以通过使用 transaction management 的编程方法来完成的。
另一方面,如果你的 application 有很多 transactional 操作,声明 transaction management 通常是值得的。它使 transaction management 不受业务逻辑的影响,并且不难配置。当使用 Spring Framework 而不是 EJB CMT 时,声明性 transaction management 的 configuration 成本大大降低。
Transaction-bound事件
从 Spring 4.2 开始,event 的 listener 可以绑定到 transaction 的一个阶段。典型的 example 是在 transaction 成功完成后处理 event。这样做可以在当前 transaction 的结果对 listener 实际上很重要时更灵活地使用 events。
您可以使用@EventListener
annotation 注册常规 event listener。如果需要将其绑定到 transaction,请使用@TransactionalEventListener
。执行此操作时,listener 默认绑定到 transaction 的提交阶段。
下一个例子显示了这个概念。假设一个 component 发布一个 order-created event,并且我们想要定义一个 listener,它只应该在已经发布它的 transaction 成功提交后才处理该 event。以下 example 设置了这样一个 event listener:
1 |
|
@TransactionalEventListener
annotation 公开了一个phase
属性,该属性允许您自定义 listener 应绑定到的 transaction 的阶段。有效阶段是BEFORE_COMMIT
,AFTER_COMMIT
(默认),AFTER_ROLLBACK
和AFTER_COMPLETION
,它们聚合 transaction 完成(无论是提交还是回滚)。
如果没有 transaction running,则根本不会调用 listener,因为我们无法遵守所需的语义。但是,您可以通过将 annotation 的fallbackExecution
属性设置为true
来覆盖该行为。
特定于应用服务器的集成
Spring 的 transaction 抽象通常是 application server-agnostic。此外,Spring 的JtaTransactionManager
class(可以选择对 JTA UserTransaction
和TransactionManager
objects 执行 JNDI 查找)自动检测后者 object 的位置,该位置因 application 服务器而异。有权访问 JTA TransactionManager
允许增强的 transaction 语义 - 特别是支持 transaction 暂停。有关详细信息,请参阅JtaTransactionManager javadoc。
Spring 的JtaTransactionManager
是 Java EE application 服务器上 run 的标准选择,并且已知适用于所有 common 服务器。高级功能,例如 transaction suspension,也适用于许多服务器(包括 GlassFish,JBoss 和 Geronimo),无需任何特殊的 configuration 配置。但是,对于完全支持的 transaction 暂停和进一步的高级 integration,Spring 包含用于 WebLogic Server 和 WebSphere 的特殊适配器。以下各节将讨论这些适配器。
对于标准方案(包括 WebLogic Server 和 WebSphere),请考虑使用方便的<tx:jta-transaction-manager/>
configuration 元素。配置后,此元素会自动检测基础服务器并选择可用于平台的最佳 transaction manager。这意味着您无需显式配置 server-specific adapter classes(如以下部分所述)。相反,它们是自动选择的,标准JtaTransactionManager
作为默认回退。
IBM WebSphere
在 WebSphere 6.1.0.9 及更高版本上,推荐使用的 Spring JTA transaction manager 是WebSphereUowTransactionManager
。此特殊适配器使用 IBM 的UOWManager
API,该 API 在 WebSphere Application Server 6.1.0.9 及更高版本中可用。使用此适配器,IBM 正式支持 Spring-driven transaction 暂停(由PROPAGATION_REQUIRES_NEW
启动暂停和恢复)。
Oracle WebLogic Server
在 WebLogic Server 9.0 或更高版本上,通常使用WebLogicJtaTransactionManager
而不是 stock JtaTransactionManager
class。正常JtaTransactionManager
的这个特殊的 WebLogic-specific 子类支持 WebLogic-managed transaction 定义中 Spring 的 transaction 定义的全部功能,超出了标准的 JTA 语义。 Features 包括 transaction 名称,per-transaction 隔离级别,以及在所有情况下 transactions 的正确恢复。
Common问题的解决方案
本节介绍了一些常见问题的解决方案。
使用错误的事务管理用于特定数据源
根据您选择的 transactional 技术和要求,使用正确的PlatformTransactionManager
implementation。如果使用得当,Spring Framework 只提供简单易用的抽象。如果使用 global transactions,则必须对所有 transactional 操作使用org.springframework.transaction.jta.JtaTransactionManager
class(或application server-specific subclass)。否则,transaction 基础结构会尝试在容器DataSource
实例等资源上执行本地 transactions。这样的本地 transactions 没有意义,一个好的 application 服务器将它们视为错误。
更多资源
有关 Spring Framework 的 transaction 支持的更多信息,请参阅:
- Spring 中的分布式 transactions,有和没有 XA是一个 JavaWorld 演示文稿,其中 Spring 的 David Syer 在 Spring applications 中引导您完成分布式 transactions 的七种模式,其中三种模式使用 XA,另外四种没有。
- Java Transaction 设计策略是一本来自InfoQ 中文站的书,它为 Java 中的 transactions 提供 well-paced 介绍。它还包括如何使用 Spring Framework 和 EJB3 配置和使用 transactions 的 side-by-side 示例。
DAO支持
Spring 中的数据访问 Object(DAO)支持旨在使以一致的方式使用数据访问技术(如 JDBC,Hibernate 或 JPA)变得容易。这使您可以相当容易地在上述持久性技术之间切换,并且它还允许您 code 而不必担心捕获特定于每种技术的 exceptions。
一致的Exception层次结构
Spring 提供了从 technology-specific exceptions(例如SQLException
)到其自己的 exception class 层次结构的方便转换,其中DataAccessException
作为根 exception。这些 exceptions 包装了原始的 exception,因此您可能永远不会丢失任何可能出错的信息。
除了 JDBC exceptions 之外,Spring 还可以包装 JPA-和 Hibernate-specific exceptions,将它们转换为一组专注的运行时 exceptions。这使您可以仅在适当的层中处理大多数 non-recoverable 持久性 exceptions,而不会在 DAO 中使用恼人的样板 catch-and-throw 块和 exception 声明。 (您仍然可以在任何需要的地方捕获和处理 exceptions though.)如上所述,JDBC exceptions(包括 database-specific 方言)也会转换为相同的层次结构,这意味着您可以在一致的编程 model 中使用 JDBC 执行某些操作。
前面的讨论为 Spring 支持各种 ORM 框架的各种模板 classes 保存了 true。如果使用 interceptor-based classes,则 application 必须关心处理HibernateExceptions
和PersistenceExceptions
本身,最好分别委托给SessionFactoryUtils
的convertHibernateAccessException(..)
或convertJpaAccessException()
方法。这些方法将 exceptions 转换为与org.springframework.dao
exception 层次结构中的 exceptions 兼容的 exceptions。由于PersistenceExceptions
未经检查,它们也可能被抛出(尽管在 exceptions 方面牺牲了通用的 DAO 抽象)。
下图显示了 Spring 提供的 exception 层次结构。 (请注意,图像中详细说明的 class 层次结构仅显示整个DataAccessException
hierarchy.)的子集
Annotations用于配置DAO或Repository类
保证数据访问 Objects(DAO)或 repositories 提供 exception 转换的最佳方法是使用@Repository
annotation。此 annotation 还允许 component 扫描支持查找和配置您的 DAO 和 repositories,而无需为它们提供 XML configuration 条目。以下 example 显示了如何使用@Repository
annotation:
1 | 1) ( |
1 | @Repository annotation。 |
任何 DAO 或 repository implementation 都需要访问持久性资源,具体取决于所使用的持久性技术。例如,JDBC-based repository 需要访问 JDBC DataSource
,而 JPA-based repository 需要访问EntityManager
。完成此操作的最简单方法是使用@Autowired
,@Inject
,@Resource
或@PersistenceContext
注释之一注入此资源依赖项。以下 example 适用于 JPA repository:
1 |
|
如果使用经典的 Hibernate API,则可以 inject SessionFactory
,如下面的 example 所示:
1 |
|
我们在这里展示的最后一个例子是典型的 JDBC 支持。您可以将DataSource
注入初始化方法,您可以使用此DataSource
创建JdbcTemplate
和其他数据访问支持 classes(例如SimpleJdbcCall
和其他)。以下 example 自动装配DataSource
:
1 |
|
有关如何配置 application context 以利用这些注释的详细信息,请参阅每种持久性技术的具体内容。
使用JDBC进行数据访问
Spring Framework JDBC 抽象提供的 value 可能最好通过下面的 table 中概述的操作序列来显示。 table 显示 Spring 负责哪些操作以及哪些操作是您的责任。
行动 | 弹簧 | 您 |
---|---|---|
定义连接参数。 | X | |
打开连接。 | X | |
指定 SQL 语句。 | X | |
声明参数并提供参数值 | X | |
准备并执行声明。 | X | |
设置循环以迭代结果(如果有)。 | X | |
为每次迭代做好工作。 | X | |
Process 任何 exception。 | X | |
处理 transactions。 | X | |
关闭连接,语句和结果集。 | X |
Spring Framework 负责处理所有可能使 JDBC 成为繁琐 API 的 low-level 细节。
选择JDBC数据库访问方法
您可以选择多种方法来构成 JDBC 数据库访问的基础。除了三种JdbcTemplate
之外,新的SimpleJdbcInsert
和SimpleJdbcCall
方法优化了数据库元数据,而 RDBMS Object 样式采用了类似于 JDO Query 设计的更多 object-oriented 方法。一旦开始使用这些方法之一,您仍然可以混合和 match 以包含来自不同方法的 feature。所有方法都需要 JDBC 2.0-compliant 驱动程序,而某些高级 features 需要 JDBC 3.0 驱动程序。
JdbcTemplate
是经典且最流行的 Spring JDBC 方法。这种“lowest-level”方法和所有其他方法都使用了 JdbcTemplate。NamedParameterJdbcTemplate
包装JdbcTemplate
以提供命名参数,而不是传统的 JDBC?
占位符。当您有多个 SQL 语句参数时,此方法可提供更好的文档和易用性。SimpleJdbcInsert
和SimpleJdbcCall
优化数据库元数据以限制必要的 configuration 数量。此方法简化了编码,因此您只需要提供 table 或过程的 name,并提供与列名匹配的参数的 map。仅当数据库提供足够的元数据时,这才有效。如果数据库未提供此元数据,则必须提供参数的显式 configuration。- RDBMS objects,包括
MappingSqlQuery
,SqlUpdate
和StoredProcedure
,要求您在 data-access 层初始化期间创建可重用和 thread-safe objects。此方法以 JDO Query 为模型,其中您定义查询 string,声明参数和编译查询。一旦这样做,就可以使用各种参数值多次调用 execute 方法。
包层次结构
Spring Framework 的 JDBC 抽象 framework 由四个不同的包组成:
core
:org.springframework.jdbc.core
包包含JdbcTemplate
class 及其各种回调接口,以及各种相关的 classes。名为org.springframework.jdbc.core.simple
的子包包含SimpleJdbcInsert
和SimpleJdbcCall
classes。另一个名为org.springframework.jdbc.core.namedparam
的子包包含NamedParameterJdbcTemplate
class 和相关的支持 classes。请参见使用 JDBC Core Classes 控制基本 JDBC 处理和错误处理,JDBC 批处理操作和使用 SimpleJdbc Classes 简化 JDBC 操作。datasource
:org.springframework.jdbc.datasource
包中包含一个用于轻松DataSource
访问的实用程序 class 和各种简单的DataSource
__mplementations,可用于测试和运行 Java EE 容器外的未修改的 JDBC code。名为org.springfamework.jdbc.datasource.embedded
的子包通过使用 Java 数据库引擎(如 HSQL,H2 和 Derby)为创建嵌入式数据库提供支持。见控制数据库连接和嵌入式数据库支持。
object
:org.springframework.jdbc.object
包中包含 classes,它们将 RDBMS 查询,更新和存储过程表示为 thread-safe,可重用的 objects。见将 JDBC 操作建模为 Java Objects。这种方法由 JDO 建模,尽管查询返回的 objects 自然地与数据库断开连接。这个 higher-level 的 JDBC 抽象取决于org.springframework.jdbc.core
包中的 lower-level 抽象。
support
:org.springframework.jdbc.support
包提供SQLException
转换功能和一些实用程序 classes。 _ JDBC 处理期间抛出的异常被转换为org.springframework.dao
包中定义的 exceptions。这意味着使用 Spring JDBC 抽象层的 code 不需要实现 JDBC 或 RDBMS-specific 错误处理。所有已翻译的 exceptions 都是未选中的,这使您可以选择捕获 exceptions,从中可以恢复,同时让其他 exceptions 传播给调用者。见使用 SQLExceptionTranslator。
使用JDBC核心类控制基本JDBC处理和错误处理
本节介绍如何使用 JDBC 核心 classes 来控制基本的 JDBC 处理,包括错误处理。它包括以下主题:
- 使用 JdbcTemplate
- 使用 NamedParameterJdbcTemplate
- 使用 SQLExceptionTranslator
- Running Statements
- Running Queries
- 更新数据库
- 检索 Auto-generated 键
使用JdbcTemplate
JdbcTemplate
是 JDBC 核心包中的中心 class。它处理资源的创建和释放,帮助您避免 common 错误,例如忘记关闭连接。它执行核心 JDBC 工作流的基本任务(例如语句创建和执行),留下 application code 以提供 SQL 并提取结果。 JdbcTemplate
class:
- 运行 SQL 查询
- 更新 statements 和存储过程 calls
- 对
ResultSet
实例执行迭代并提取返回的参数值。 - 捕获 JDBC exceptions 并将它们转换为
org.springframework.dao
包中定义的通用的,更具信息性的 exception 层次结构。 (见一致的 Exception 层次结构 .)
当您为 code 使用JdbcTemplate
时,您只需要实现回调接口,为它们提供明确定义的 contract。给定JdbcTemplate
class 提供的Connection
,PreparedStatementCreator
回调接口创建一个预准备语句,提供 SQL 和任何必要的参数。 CallableStatementCreator
接口的 true 也是如此,它创建了可调用的 statements。 RowCallbackHandler
接口从ResultSet
的每一行中提取值。
您可以通过使用DataSource
reference 直接实例化在 DAO implementation 中使用JdbcTemplate
,也可以在 Spring IoC 容器中对其进行配置,并将其作为 bean reference 提供给 DAO。
始终应在 Spring IoC 容器中将
DataSource
配置为 bean。在第一种情况下,bean 直接提供给服务;在第二种情况下,它被给予准备好的模板。
此 class 发出的所有 SQL 都记录在与模板实例的完全限定 class name 对应的类别下的DEBUG
level 下(通常为JdbcTemplate
,但如果使用JdbcTemplate
class 的自定义子类,则可能会有所不同)。
以下部分提供了JdbcTemplate
用法的一些示例。这些示例并不是JdbcTemplate
所公开的所有功能的详尽列表。请参阅服务员javadoc。
查询(SELECT)
以下查询获取关系中的行数:
1 | int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class); |
以下查询使用绑定变量:
1 | int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( |
以下查询查找String
:
1 | String lastName = this.jdbcTemplate.queryForObject( |
以下查询查找并填充单个域 object:
1 | Actor actor = this.jdbcTemplate.queryForObject( |
以下查询查找并填充了许多域 objects:
1 | List<Actor> actors = this.jdbcTemplate.query( |
如果 code 的最后两个片段实际存在于同一个 application 中,那么删除两个RowMapper
匿名内部 classes 中存在的重复并将它们提取到一个 class(通常是一个static
嵌套的 class)然后可以被引用是有意义的。根据需要通过 DAO 方法。例如,最好按如下方式编写前面的 code 代码段:
1 | public List<Actor> findAllActors() { |
使用JdbcTemplate更新(INSERT,UPDATE 和 DELETE)
您可以使用update(..)
方法执行 insert,update 和 delete 操作。参数值通常作为变量 arguments 提供,或者作为 object array 提供。
以下 example 插入一个新条目:
1 | this.jdbcTemplate.update( |
以下 example 更新现有条目:
1 | this.jdbcTemplate.update( |
以下 example 删除条目:
1 | this.jdbcTemplate.update( |
其他JdbcTemplate操作
您可以使用execute(..)
方法来运行任意 SQL。因此,该方法通常用于 DDL 语句。它过多地采用了带回调接口,binding 变量数组等的变体。以下 example 创建一个 table:
1 | this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))"); |
以下 example 调用存储过程:
1 | this.jdbcTemplate.update( |
更复杂的存储过程支持是稍后报道。
JdbcTemplate最佳实践
一旦配置,JdbcTemplate
class 的实例为 thread-safe。这很重要,因为这意味着您可以配置JdbcTemplate
的单个实例,然后安全地将此共享 reference 注入多个 DAO(或 repositories)。 JdbcTemplate
是有状态的,因为它维护的 reference,但是 state 不是会话 state。
使用JdbcTemplate
class(以及关联的是 NamedParameterJdbcTemplate class)时的 common 实践是在 Spring configuration 文件中配置DataSource
,然后在bean 中配置DataSource
bean。 JdbcTemplate
是在DataSource
的 setter 中创建的。这导致 DAO 类似于以下内容:
1 | public class JdbcCorporateEventDao implements CorporateEventDao { |
以下 example 显示了相应的 XML configuration:
1 |
|
显式 configuration 的替代方法是使用 component-scanning 和 annotation 支持依赖注入。在这种情况下,您可以使用@Repository
(使其成为 component-scanning 的候选者)注释 class,并使用@Autowired
注释DataSource
setter 方法。以下 example 显示了如何执行此操作:
1 | 1) ( |
1 | 使用@Repository 注释 class。 |
2 | 使用@Autowired 注释DataSource setter 方法。 |
3 | 使用DataSource 创建一个新的JdbcTemplate 。 |
以下 example 显示了相应的 XML configuration:
1 |
|
如果你使用 Spring 的JdbcDaoSupport
class 并从中扩展了各种 JDBC-backed DAO classes,那么 sub-class 会从JdbcDaoSupport
class 继承一个setDataSource(..)
方法。您可以选择是否继承此 class。 JdbcDaoSupport
class 仅为方便起见而提供。
无论您选择使用(或不使用)上述哪种模板初始化样式,都很少需要在每次 time _运行 SQL 时创建JdbcTemplate
class 的新实例。配置完成后,JdbcTemplate
实例为 thread-safe。如果您的 application 访问多个数据库,您可能需要多个JdbcTemplate
实例,这需要多个DataSources
,以及随后多个不同配置的JdbcTemplate
实例。
使用NamedParameterJdbcTemplate
NamedParameterJdbcTemplate
class 通过使用命名参数添加了对 JDBC statements 编程的支持,而不是仅使用经典占位符('?'
)arguments 编写 JDBC statements。 NamedParameterJdbcTemplate
class 包装JdbcTemplate
并委托包装的JdbcTemplate
来完成它的大部分工作。本节仅描述NamedParameterJdbcTemplate
class 与JdbcTemplate
本身不同的那些区域 - 即使用命名参数编写 JDBC statements。以下 example 显示了如何使用NamedParameterJdbcTemplate
:
1 | // some JDBC-backed DAO class... |
注意在分配给sql
变量的 value 中使用命名参数表示法,以及插入namedParameters
变量(类型MapSqlParameterSource
)的相应 value。
或者,您可以使用由NamedParameterJdbcOperations
公开的Map
-based style.The 剩余方法将命名参数及其相应值传递给NamedParameterJdbcTemplate
实例,并由NamedParameterJdbcTemplate
class 实现,遵循类似的 pattern,此处不予介绍。
以下 example 显示了Map
-based 样式的用法:
1 | // some JDBC-backed DAO class... |
与NamedParameterJdbcTemplate
相关的一个很好的 feature(并且存在于同一个 Java 包中)是SqlParameterSource
接口。您已经在之前的一个 code 片段(MapSqlParameterSource
class)中看到了此接口的实例实例。 SqlParameterSource
是NamedParameterJdbcTemplate
的命名参数值的来源。 MapSqlParameterSource
class 是一个简单的 implementation,是一个围绕java.util.Map
的适配器,其中键是参数名称,值是参数值。
另一个SqlParameterSource
implementation 是BeanPropertySqlParameterSource
class。此 class 包装任意 JavaBean(即,遵循JavaBean 约定的 class 实例),并使用包装的 JavaBean 的 properties 作为命名参数值的来源。
以下 example 显示了一个典型的 JavaBean:
1 | public class Actor { |
以下 example 使用NamedParameterJdbcTemplate
来_return 前面 example 中显示的 class 成员的计数:
1 | // some JDBC-backed DAO class... |
请记住,NamedParameterJdbcTemplate
class 包装了一个经典的JdbcTemplate
模板。如果需要访问包装的JdbcTemplate
实例来访问仅存在于JdbcTemplate
class 中的功能,则可以使用getJdbcOperations()
方法通过JdbcOperations
接口访问包装的JdbcTemplate
。
有关在 application 的 context 中使用NamedParameterJdbcTemplate
class 的指导,另请参阅JdbcTemplate 最佳实践。
使用SQLExceptionTranslator
SQLExceptionTranslator
是由 classes 实现的接口,可以在SQLExceptions
和 Spring 自己的org.springframework.dao.DataAccessException
之间进行转换,这与数据访问策略无关。 实现可以是通用的(例如,使用 JDBC 的 SQLState 代码)或专有的(例如,使用 Oracle 错误代码)以获得更高的精度。
SQLErrorCodeSQLExceptionTranslator
是默认使用的SQLExceptionTranslator
的 实现。此实现使用特定的供应商代码。它比SQLState
implementation 更精确。错误 code 转换基于 JavaBean 类型 class 中保存的代码SQLErrorCodes
。这个 class 由SQLErrorCodesFactory
创建和填充,(根据 name 建议)是 creating SQLErrorCodes
的工厂,它基于名为sql-error-codes.xml
的 configuration 文件的内容。此文件使用供应商代码填充,并基于DatabaseMetaData
中的DatabaseProductName
。使用您正在使用的实际数据库的代码。
SQLErrorCodeSQLExceptionTranslator
按以下顺序应用匹配规则:
- 由子类实现的任何自定义转换。通常,使用提供的具体
SQLErrorCodeSQLExceptionTranslator
,因此该规则不适用。仅当您实际提供了子类 implementation 时才适用。 SQLExceptionTranslator
接口的任何自定义 implementation,作为SQLErrorCodes
class 的customSqlExceptionTranslator
property 提供。- 搜索
CustomSQLErrorCodesTranslation
class 的实例列表(为SQLErrorCodes
class 的customTranslations
property 提供)以查找 match。 - 应用错误 code 匹配。
- 使用后备翻译器。
SQLExceptionSubclassTranslator
是默认的后备翻译器。如果此转换不可用,则下一个后备转换器为SQLStateSQLExceptionTranslator
。
默认情况下,
SQLErrorCodesFactory
用于定义Error
代码和自定义 exception 转换。它们在 classpath 中名为sql-error-codes.xml
的文件中查找,匹配的SQLErrorCodes
实例基于正在使用的数据库的数据库元数据中的数据库 name。
您可以扩展SQLErrorCodeSQLExceptionTranslator
,如下面的 example 所示:
1 | public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { |
在前面的 example 中,特定错误 code(-12345
)被翻译,而其他错误则由默认翻译器实现翻译。要使用此自定义转换程序,必须通过方法setExceptionTranslator
将其传递给JdbcTemplate
,并且必须将此JdbcTemplate
用于需要此转换程序的所有数据访问处理。以下 example 显示了如何使用此自定义转换器:
1 | private JdbcTemplate jdbcTemplate; |
自定义转换器在 order 中传递数据源以查找sql-error-codes.xml
中的错误代码。
执行Statements
运行 SQL 语句只需要很少的 code。您需要DataSource
和JdbcTemplate
,包括随JdbcTemplate
提供的便捷方法。以下 example 显示了为创建新 table 的最小但功能齐全的 class 所需要包含的内容:
1 | import javax.sql.DataSource; |
执行Queries
一些查询方法 return 一个 value。要从一行检索计数或特定 value,请使用queryForObject(..)
。后者将返回的 JDBC Type
转换为作为参数传入的 Java class。如果类型转换无效,则抛出InvalidDataAccessApiUsageException
。以下 example 包含两个查询方法,一个用于int
,另一个用于查询String
:
1 | import javax.sql.DataSource; |
除了单个结果查询方法之外,还有几个方法_返回一个列表,其中包含查询返回的每一行的条目。最通用的方法是queryForList(..)
,它返回List
,其中每个元素都是Map
,每列包含一个条目,使用 name 作为 key。如果向前面的 example 添加方法以检索所有行的列表,则可能如下所示:
1 | private JdbcTemplate jdbcTemplate; |
返回的列表将类似于以下内容:
1 | [{name=Bob, id=1}, {name=Mary, id=2}] |
更新数据库
以下 example 更新某个主 key 的列:
1 | import javax.sql.DataSource; |
在前面的示例中,SQL 语句具有行参数的占位符。您可以将参数值作为 varargs 传递,或者作为 objects 的 array 传递。因此,您应该在原始 wrapper classes 中显式包装 primitives,或者您应该使用 auto-boxing。
检索Auto-generated键
update()
便捷方法支持检索数据库生成的主键。这种支持是 JDBC 3.0 标准的一部分。有关详细信息,请参阅规范的第 13.6 章。该方法将PreparedStatementCreator
作为其第一个参数,这是指定所需的 insert 语句的方式。另一个参数是KeyHolder
,它包含从更新成功 return 时生成的 key。没有标准的单一方法来创建适当的PreparedStatement
(这解释了为什么方法签名就是这样)。以下 example 适用于 Oracle,但可能无法在其他平台上运行:
1 | final String INSERT_SQL = "insert into my_test (name) values(?)"; |
控制数据库连接
本节包括:
- 使用 DataSource
- 使用 DataSourceUtils
- 实现 SmartDataSource
- 扩展 AbstractDataSource
- 使用 SingleConnectionDataSource
- 使用 DriverManagerDataSource
- 使用 TransactionAwareDataSourceProxy
- 使用 DataSourceTransactionManager
使用DataSource
Spring 通过DataSource
获取与数据库的连接。 DataSource
是 JDBC 规范的一部分,是一个通用的连接工厂。它允许容器或 framework 隐藏 application code 中的连接池和 transaction management 问题。作为开发人员,您无需了解有关如何连接到数据库的详细信息。这是设置数据源的管理员的责任。您最有可能在开发和测试 code 时填充这两个角色,但您不一定要知道如何配置 production 数据源。
当您使用 Spring 的 JDBC 层时,您可以从 JNDI 获取数据源,或者您可以使用第三方提供的连接池 implementation 配置您自己的数据源。流行的 implementations 是 Apache Jakarta Commons _DBCP 和 C3P0。 Spring 发行版中的实现仅用于测试目的,不提供合并。
本节使用 Spring 的DriverManagerDataSource
implementation,稍后将介绍几个额外的 实现。
您应该仅将
DriverManagerDataSource
class 用于测试目的,因为它不提供池,并且在进行多个连接请求时性能很差。
要配置DriverManagerDataSource
:
- 通常获取 JDBC 连接时,与
DriverManagerDataSource
建立连接。 - 指定 JDBC 驱动程序的完全限定类名,以便
DriverManager
可以加载驱动程序 class。 - 提供不同 JDBC 驱动程序之间的 URL。 (有关正确的 value.),请参阅驱动程序的文档
- 提供用户名和密码以连接到数据库。
以下 example 显示了如何在 Java 中配置DriverManagerDataSource
:
1 | DriverManagerDataSource dataSource = new DriverManagerDataSource(); |
以下 example 显示了相应的 XML configuration:
1 | <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> |
接下来的两个示例显示了 DBCP 和 C3P0 的基本连接和 configuration。要了解有助于控制池 features 的更多选项,请参阅相应连接池 implementations 的产品文档。
以下 example 显示了 DBCP configuration:
1 | <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> |
以下 example 显示了 C3P0 configuration:
1 | <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> |
使用DataSourceUtils
DataSourceUtils
class 是一个方便且强大的帮助器 class,它提供static
方法以在必要时从 JNDI 获取连接并关闭连接。它支持 thread-bound 连接,例如,DataSourceTransactionManager
。
实现SmartDataSource
SmartDataSource
接口应该由可以提供与关系数据库的连接的 classes 实现。它扩展了DataSource
接口,让 classes 使用它来查询是否应该在给定的操作后关闭连接。当您知道需要重用连接时,此用法很有效。
扩展AbstractDataSource
AbstractDataSource
是 Spring的DataSource
实现的abstract
base class。它为所有DataSource
implementations 实现 common common。如果编写自己的DataSource
implementation,则应扩展AbstractDataSource
class。
使用SingleConnectionDataSource
SingleConnectionDataSource
class 是SmartDataSource
接口的 implementation,它包装了每次使用后未关闭的单个Connection
。这不是 multi-threading 能力。
如果任何 client code calls close
假设池化连接(如使用持久性工具时),则应将suppressClose
property 设置为true
。此设置返回包装物理连接的 close-suppressing 代理。请注意,您不能再将其强制转换为本机 Oracle Connection
或类似的 object。
SingleConnectionDataSource
主要是测试 class。例如,它可以在一个简单的 JNDI 环境中轻松测试 application 服务器外部的 code。与DriverManagerDataSource
相比,它在所有 time 中重用相同的连接,避免过多地创建物理连接。
使用DriverManagerDataSource
DriverManagerDataSource
class 是标准DataSource
接口的 implementation,它通过 bean properties 配置普通的 JDBC 驱动程序,并且每 time 返回一个新的Connection
。
此 implementation 对于 Java EE 容器外部的测试和 stand-alone 环境非常有用,可以作为 Spring IoC 容器中的DataSource
bean 或与简单的 JNDI 环境结合使用。 Pool-assuming Connection.close()
calls 关闭连接,因此任何DataSource
-aware 持久性 code 都应该有效。但是,即使在测试环境中,使用 JavaBean-style 连接池(例如commons-dbcp
)也非常容易,因此使用这样的连接池几乎总是优于DriverManagerDataSource
。
使用TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy
是目标DataSource
的代理。代理包装目标DataSource
以添加 Spring-managed transactions 的意识。在这方面,它类似于 Java EE 服务器提供的 transactional JNDI DataSource
。
除非必须调用已经存在的 code 并传递标准 JDBC
DataSource
接口 implementation,否则很少使用此 class。在这种情况下,您仍然可以使此 code 可用,并且在同一时间,让 code 参与 Spring managed transactions。通常最好使用资源 management 的更高 level 抽象来编写自己的新 code,例如JdbcTemplate
或DataSourceUtils
。
有关更多详细信息,请参阅类 TransactionAwareDataSourceProxy javadoc。
使用DataSourceTransactionManager
DataSourceTransactionManager
class 是单个 JDBC 数据源的PlatformTransactionManager
implementation。它将 JDBC 连接从指定的数据源绑定到当前正在执行的线程,可能允许每个数据源一个线程连接。
Application code 需要通过DataSourceUtils.getConnection(DataSource)
而不是 Java EE 的标准DataSource.getConnection
来检索 JDBC 连接。它会抛出未经检查的org.springframework.dao
exceptions 而不是选中SQLExceptions
。所有 framework classes(例如JdbcTemplate
)都隐式使用此策略。如果不与此 transaction manager 一起使用,则查找策略的行为与 common 完全相同。因此,它可以在任何情况下使用。
DataSourceTransactionManager
class 支持自定义隔离级别和超时,这些级别和超时将作为适当的 JDBC 语句查询超时应用。要支持后者,application code 必须使用JdbcTemplate
或为每个创建的语句调用DataSourceUtils.applyTransactionTimeout(..)
方法。
您可以在 single-resource 情况下使用此 implementation 而不是JtaTransactionManager
,因为它不需要容器支持 JTA。如果您坚持所需的连接查找 pattern,则在两者之间切换只是 configuration 的问题。 JTA 不支持自定义隔离级别。
JDBC批处理操作
如果将多个 calls 批处理到同一个预准备语句,则大多数 JDBC 驱动程序都会提供改进的 performance。通过将更新分组到批次中,可以限制到数据库的往返次数。
使用JdbcTemplate进行基本批处理操作
您通过实现特殊接口BatchPreparedStatementSetter
的两个方法,并将实现作为batchUpdate
方法调用中的第二个参数传递来完成JdbcTemplate
批处理。您可以使用getBatchSize
方法提供当前批次的大小。您可以使用setValues
方法设置预准备语句的参数值。此方法称为您在getBatchSize
调用中指定的次数。以下 example 根据列表中的条目更新actor
table,整个列表用作批处理:
1 | public class JdbcActorDao implements ActorDao { |
如果您处理更新流或从文件读取,则可能具有首选批量大小,但最后一批可能没有该数量的条目。在这种情况下,您可以使用InterruptibleBatchPreparedStatementSetter
接口,该接口允许您在输入源耗尽时中断批处理。 isBatchExhausted
方法允许您发出批次结束的信号。
使用Objects列表的批处理操作
JdbcTemplate
和NamedParameterJdbcTemplate
都提供了另一种提供批量更新的方法。您可以将调用中的所有参数值作为列表提供,而不是实现特殊的批处理接口。 framework 循环遍历这些值并使用内部预处理语句 setter。 API 会有所不同,具体取决于您是否使用命名参数。对于命名参数,为SqlParameterSource
提供 array,为批处理的每个成员提供一个条目。您可以使用SqlParameterSourceUtils.createBatch
便捷方法创建此 array,传入 bean-style objects 的 array(使用与参数对应的 getter 方法),String
-keyed Map
实例(包含相应参数作为值),或两者的混合。
以下 example 显示了使用命名参数的批量更新:
1 | public class JdbcActorDao implements ActorDao { |
对于使用经典?
占位符的 SQL 语句,您传入包含带有更新值的 object array 的列表。此 object array 必须在 SQL 语句中为每个占位符分配一个条目,并且它们必须与 SQL 语句中定义的顺序相同。
以下 example 与前面的 example 相同,只是它使用经典的 JDBC ?
占位符:
1 | public class JdbcActorDao implements ActorDao { |
我们之前描述的所有批处理更新方法都返回int
array,其中包含每个批处理条目的受影响行数。 JDBC 驱动程序报告此计数。如果计数不可用,则 JDBC 驱动程序返回_val的 value。
在这种情况下,通过在底层
PreparedStatement
上自动设置值,每个 value 的相应 JDBC 类型需要从给定的 Java 类型派生。虽然这通常很有效,但可能存在问题(例如,Map-containednull
值)。 Spring,默认情况下,callsParameterMetaData.getParameterType
在这种情况下,使用 JDBC 驱动程序可能会很昂贵。如果遇到 performance 问题,您应该使用最近的驱动程序 version 并考虑将spring.jdbc.getParameterType.ignore
property 设置为true
(作为 JVM 系统 property 或 class 路径根目录中的spring.properties
文件) - 对于 example,如 Oracle 12c 上报告的那样(SPR-16139)。
或者,您可以考虑通过’BatchPreparedStatementSetter’(如前所示),通过给定基于’List
多批次批处理操作
批量更新的前一个示例处理的批量非常大,您希望将它们分成几个较小的批次。您可以使用前面提到的方法通过对batchUpdate
方法进行多次 calls 来完成此操作,但现在有一种更方便的方法。除了 SQL 语句之外,此方法还包含Collection
个 objects,其中包含参数,每个批次要进行的更新次数,以及ParameterizedPreparedStatementSetter
来设置预准备语句的参数值。 framework 循环提供的值并将 update calls 分解为指定大小的批处理。
以下 example 显示批量更新使用批量大小 100:
1 | public class JdbcActorDao implements ActorDao { |
此调用的批处理更新方法返回int
数组的 array,其中包含每个批处理的 array 条目,每个更新的受影响行数为 array。 top level array 的长度表示执行的批次数,第二 level array 的长度表示该批次中的更新数。每个批次中的更新数量应该是为所有批次提供的批量大小(最后一个批次可能更少),具体取决于提供的更新对象的总数。每个更新语句的更新计数是 JDBC 驱动程序报告的更新计数。如果计数不可用,则 JDBC 驱动程序返回_val的 value。
使用SimpleJdbc类简化JDBC操作
SimpleJdbcInsert
和SimpleJdbcCall
classes 通过利用可通过 JDBC 驱动程序检索的数据库元数据来提供简化的 configuration。这意味着您可以更少地预先配置,但如果您希望提供 code 中的所有详细信息,则可以覆盖或关闭元数据处理。
使用SimpleJdbcInsert插入数据
我们首先使用最少量的配置选项查看SimpleJdbcInsert
类。您应该在数据访问层的初始化方法中实例化SimpleJdbcInsert
。对于此 example,初始化方法是setDataSource
方法。您不需要继承SimpleJdbcInsert
类。相反,您可以使用withTableName
方法创建新实例并设置 table name。该类的配置方法遵循返回SimpleJdbcInsert实例的fluid样式,该样式返回SimpleJdbcInsert
的实例,它允许您链接所有 configuration 方法。以下 example 仅使用一个 configuration 方法(稍后我们将显示多个方法的示例):
1 | public class JdbcActorDao implements ActorDao { |
这里使用的execute
方法将普通java.util.Map
作为唯一参数。这里要注意的重要一点是,用于Map
的键必须 match table 的_list,如数据库中定义的那样。这是因为我们读取元数据来构造实际的 insert 语句。
使用SimpleJdbcInsert检索Auto-generated键
下一个 example 使用与前面的 example 相同的 insert,但是,它不是传入id
,而是检索 auto-generated key 并在新的Actor
object 上设置它。当它创建SimpleJdbcInsert
时,除了指定 table name 之外,它还使用usingGeneratedKeyColumns
方法指定生成的 key 列的 name。以下清单显示了它的工作原理:
1 | public class JdbcActorDao implements ActorDao { |
使用第二种方法运行 insert 时的主要区别在于,不要将id
添加到Map
,而是调用executeAndReturnKey
方法。这将返回一个java.lang.Number
object,您可以使用该对象创建域 class 中使用的数字类型的实例。您不能依赖所有数据库来返回特定的 Java class。 java.lang.Number
是您可以信赖的 base class。如果您有多个 auto-generated 列或生成的值为 non-numeric,则可以使用从executeAndReturnKeyHolder
方法返回的KeyHolder
。
为 SimpleJdbcInsert 指定列
您可以通过使用usingColumns
方法指定列名列表来限制 insert 的列,如下面的 example 所示:
1 | public class JdbcActorDao implements ActorDao { |
insert 的执行与依赖元数据确定要使用的列相同。
使用SqlParameterSource提供参数值
使用Map
提供参数值工作正常,但它不是最方便使用的 class。 Spring 提供了几个SqlParameterSource
接口的 implementations,您可以使用它。第一个是BeanPropertySqlParameterSource
,如果你有一个包含你的值的 JavaBean-compliant class,这是一个非常方便的 class。它使用相应的 getter 方法来提取参数值。以下 example 显示了如何使用BeanPropertySqlParameterSource
:
1 | public class JdbcActorDao implements ActorDao { |
另一种选择是类似于Map
的MapSqlParameterSource
,但提供了一种可以链接的更方便的addValue
方法。以下 example 显示了如何使用它:
1 | public class JdbcActorDao implements ActorDao { |
如您所见,configuration 是相同的。只有执行 code 必须更改为使用这些替代输入 classes。
使用SimpleJdbcCall调用存储过程
SimpleJdbcCall
class 使用数据库中的元数据来查找in
和out
参数的名称,这样您就不必显式声明它们。如果您愿意这样做,或者您有没有自动映射到 Java class 的参数(例如ARRAY
或STRUCT
),则可以声明参数。第一个 example 显示了一个简单的过程,该过程仅从 MySQL 数据库返回VARCHAR
和DATE
格式的标量值。 example 过程读取指定的 actor 条目,并以out
参数的形式返回first_name
,last_name
和birth_date
列。以下列表显示了第一个 example:
1 | CREATE PROCEDURE read_actor ( |
in_id
参数包含您正在查找的 actor 的id
。 out
参数 return 从 table 读取的数据。
您可以以类似于声明SimpleJdbcInsert
的方式声明SimpleJdbcCall
。您应该在 data-access 层的初始化方法中实例化和配置 class。与StoredProcedure
class 相比,您无需创建子类,也无需声明可在数据库元数据中查找的参数。以下configuration 的示例使用前面的存储过程(除了DataSource
之外,唯一的 configuration 选项是存储过程的 name):
1 | public class JdbcActorDao implements ActorDao { |
为执行调用而编写的 code 涉及 creating 包含 IN 参数的SqlParameterSource
。您必须_输入为输入 value 提供的 name 与存储过程中声明的参数 name 的 name。该案例不必 match,因为您使用元数据来确定应如何在存储过程中引用数据库对象。存储过程的源中指定的内容不一定是存储在数据库中的方式。某些数据库将名称转换为全部大写,而其他数据库使用小写或使用指定的大小写。
execute
方法接受 IN 参数并返回Map
,其中包含由 name 键入的任何out
参数,如存储过程中指定的那样。在这种情况下,它们是out_first_name
,out_last_name
和out_birth_date
。
execute
方法的最后一部分创建一个Actor
实例,用于 return 检索的数据。同样,使用out
参数的名称非常重要,因为它们在存储过程中声明。此外,结果 map 中存储的out
参数名称中的情况与数据库中out
参数名称的情况相匹配,这可能因数据库而异。为了使 code 更具可移植性,您应该进行 case-insensitive 查找或指示 Spring 使用LinkedCaseInsensitiveMap
。要执行后者,您可以创建自己的JdbcTemplate
并将setResultsMapCaseInsensitive
property 设置为true
。然后,您可以将此自定义的JdbcTemplate
实例传递给SimpleJdbcCall
的构造函数。以下 example 显示了此 configuration:
1 | public class JdbcActorDao implements ActorDao { |
通过执行此操作,可以避免在用于返回的out
参数名称的情况下发生冲突。
显式声明用于SimpleJdbcCall的参数
在本章的前面部分,我们描述了如何从元数据中推导出参数,但如果您愿意,可以明确声明它们。您可以通过使用declareParameters
方法创建SimpleJdbcCall
来实现此目的,该方法将SqlParameter
objects 的可变数量作为输入。有关如何定义SqlParameter
的详细信息,请参阅下一节。
如果您使用的数据库不是 Spring-supported 数据库,则必须使用显式声明。目前,Spring 支持以下数据库的存储过程 calls 的元数据查找:Apache Derby,DB2,MySQL,Microsoft SQL Server,Oracle 和 Sybase。我们还支持 MySQL,Microsoft SQL Server 和 Oracle 的存储函数的元数据查找。
您可以选择显式声明一个,部分或全部参数。在未明确声明参数的情况下,仍会使用参数元数据。要绕过对潜在参数的元数据查找的所有处理并仅使用声明的参数,可以将方法withoutProcedureColumnMetaDataAccess
作为声明的一部分进行调用。假设您为数据库 function 声明了两个或更多不同的调用签名。在这种情况下,您调用useInParameterNames
指定要包含给定签名的 IN 参数名称列表。
以下 example 显示了一个完全声明的过程调用,并使用前面的 example 中的信息:
1 | public class JdbcActorDao implements ActorDao { |
两个示例的执行和结束结果是相同的。第二个 example 明确指定所有细节,而不是依赖于元数据。
如何定义SqlParameters
要为SimpleJdbc
classes 以及 RDBMS 操作 classes(在将 JDBC 操作建模为 Java Objects中涵盖)定义参数,可以使用SqlParameter
或其子类之一。为此,通常在构造函数中指定参数 name 和 SQL 类型。 SQL 类型是使用java.sql.Types
常量指定的。在本章的前面,我们看到了类似于以下内容的声明:
1 | new SqlParameter("in_id", Types.NUMERIC), |
带SqlParameter
的第一个 line 声明一个 IN 参数。您可以使用 IN 参数为存储过程 calls 和查询使用SqlQuery
及其子类(在了解 SqlQuery中涵盖)。
第二个 line(带SqlOutParameter
)声明了一个out
参数,用于存储过程调用。还有SqlInOutParameter
for InOut
参数(为过程提供 IN value 并且 return value 的参数)。
仅声明为
SqlParameter
和SqlInOutParameter
的参数用于提供输入值。这与StoredProcedure
class 不同,后者(为了向后兼容性)允许为声明为SqlOutParameter
的参数提供输入值。
对于 IN 参数,除了 name 和 SQL 类型之外,还可以为数字数据指定比例,或为自定义数据库类型指定类型 name。对于out
参数,您可以提供RowMapper
来处理从REF
游标返回的行的映射。另一个选项是指定SqlReturnType
,它提供了定义 return 值的自定义处理的机会。
使用SimpleJdbcCall调用存储的Function
除了提供 function name 而不是 procedure name 之外,您可以使用与调用存储过程几乎相同的方式调用存储的 function。您使用withFunctionName
方法作为 configuration 的一部分来指示您要调用 function,并生成 function 调用的相应 string。专用的执行调用(executeFunction
)用于执行 function,它返回 function return value 作为指定类型的 object,这意味着您不必从结果 map 中检索 return value。类似的便捷方法(名为executeObject
)也可用于只有一个out
参数的存储过程。以下 example(对于 MySQL)基于一个名为get_actor_name
的存储 function,它返回一个 actor 的完整 name:
1 | CREATE FUNCTION get_actor_name (in_id INTEGER) |
要调用此 function,我们再次在初始化方法中创建SimpleJdbcCall
,如下面的 example 所示:
1 | public class JdbcActorDao implements ActorDao { |
使用的executeFunction
方法返回,其中包含 function 调用的 return value。
从SimpleJdbcCall返回ResultSet或REF游标
调用存储过程或返回结果集的 function 有点棘手。某些数据库在 JDBC 结果处理期间返回结果_set,而其他数据库则需要显式注册特定类型的out
参数。这两种方法都需要额外的处理来循环结果集并处理返回的行。使用SimpleJdbcCall
,您可以使用returningResultSet
方法并声明RowMapper
实现用于特定参数。如果在结果处理期间返回结果集,则不会定义任何名称,因此返回的结果必须_使用声明RowMapper
实现的 order。指定的 name 仍用于在execute
语句返回的结果 map 中存储已处理的结果列表。
下一个 example(对于 MySQL)使用一个不带 IN 参数的存储过程,并返回t_actor
table 中的所有行:
1 | CREATE PROCEDURE read_all_actors() |
要调用此过程,可以声明RowMapper
。因为要 map 的 class 遵循 JavaBean 规则,所以可以使用通过在newInstance
方法中将所需的 class 传递给 map 而创建的BeanPropertyRowMapper
。以下 example 显示了如何执行此操作:
1 | public class JdbcActorDao implements ActorDao { |
execute
调用传入空Map
,因为此调用不接受任何参数。然后从结果 map 中检索 actor 列表并返回给调用者。
将JDBC操作建模为Java对象
org.springframework.jdbc.object
包中包含 classes,允许您以更 object-oriented 的方式访问数据库。作为示例,您可以执行查询并将结果作为包含 business objects 的列表返回,其中关系列数据映射到 business object 的 properties。您还可以 run 存储过程和 run 更新,删除和 insert statements。
许多 Spring 开发人员认为下面描述的各种 RDBMS 操作 classes(使用StoredProcedure class 的 exception)通常可以用直
JdbcTemplate
calls 替换。通常,编写一个直接在JdbcTemplate
上调用方法的 DAO 方法(而不是将查询封装为 full-blown class)更简单。
但是,如果您从使用 RDBMS 操作 classes 获得可测量的 value,则应该继续使用这些 classes。
了解SqlQuery
SqlQuery
是一个可重用的 thread-safe class,它封装了一个 SQL 查询。子类必须实现newRowMapper(..)
方法以提供RowMapper
实例,该实例可以通过迭代在执行查询期间创建的ResultSet
而获得的每行创建一个 object。 SqlQuery
class 很少直接使用,因为MappingSqlQuery
子类为将行映射到 Java classes 提供了更方便的 implementation。延伸SqlQuery
的其他 implementation 是MappingSqlQueryWithParameters
和UpdatableSqlQuery
。
使用MappingSqlQuery
MappingSqlQuery
是一个可重用的查询,其中具体的子类必须实现 abstract mapRow(..)
方法,以将提供的ResultSet
的每一行转换为指定类型的 object。以下 example 显示了一个自定义查询,该查询将t_actor
关系中的数据映射到Actor
class 的实例:
1 | public class ActorMappingQuery extends MappingSqlQuery<Actor> { |
class 使用Actor
类型扩展MappingSqlQuery
参数化。此客户查询的构造函数将DataSource
作为唯一参数。在此构造函数中,您可以使用DataSource
和应该执行的 SQL 来调用超类上的构造函数,以检索此查询的行。此 SQL 用于创建PreparedStatement
,因此它可能包含在执行期间传递的任何参数的占位符。您必须使用传入SqlParameter
的declareParameter
方法声明每个参数。 SqlParameter
采用 name,以及java.sql.Types
中定义的 JDBC 类型。定义所有参数后,可以调用compile()
方法,以便可以准备语句并稍后运行 run。这个 class 在编译之后是 thread-safe,因此,当初始化 DAO 时,这些实例被创建为 long,它们可以作为实例变量保存并重用。以下 example 显示了如何定义这样的 class:
1 | private ActorMappingQuery actorMappingQuery; |
前面 example 中的方法使用作为唯一参数传入的id
检索客户。由于我们只想返回一个 object,因此我们使用id
作为参数调用findObject
便捷方法。如果我们有一个返回 objects 列表并获取其他参数的查询,我们将使用execute
方法之一,它将 array 参数值作为 varargs 传入。以下 example 显示了这样一种方法:
1 | public List<Actor> searchForActors(int age, String namePattern) { |
使用SqlUpdate
SqlUpdate
class 封装了 SQL 更新。与查询一样,更新 object 是可重用的,并且与所有RdbmsOperation
classes 一样,更新可以具有参数并在 SQL 中定义。这个 class 提供了许多update(..)
方法,类似于查询 objects 的execute(..)
方法。 SQLUpdate
class 是具体的。它可以是子类 - 例如,添加自定义更新方法。但是,您不必为SqlUpdate
class 创建子类,因为可以通过设置 SQL 和声明参数来轻松地对其进行参数化。以下 example 创建名为execute
的自定义更新方法:
1 | import java.sql.Types; |
使用StoredProcedure
StoredProcedure
class 是 RDBMS 存储过程的 object 抽象的超类。这个 class 是abstract
,它的各种execute(..)
方法都有protected
访问权限,阻止了通过提供更严格 typing 的子类以外的用法。
继承的sql
property 是 RDBMS 中存储过程的 name。
要为StoredProcedure
class 定义参数,可以使用SqlParameter
或其子类之一。您必须在构造函数中指定参数 name 和 SQL 类型,如下面的 code 片段所示:
1 | new SqlParameter("in_id", Types.NUMERIC), |
使用java.sql.Types
常量指定 SQL 类型。
第一个 line(带SqlParameter
)声明一个 IN 参数。您可以对存储过程 calls 和使用SqlQuery
及其子类(在了解 SqlQuery中涵盖)的查询使用 IN 参数。
第二个 line(带SqlOutParameter
)声明了一个out
参数,用于存储过程调用。还有一个SqlInOutParameter
for InOut
参数(为过程提供in
value 并且 return value 的参数)。
对于in
参数,除了 name 和 SQL 类型之外,还可以为数字数据指定比例,或为自定义数据库类型指定类型 name。对于out
参数,您可以提供RowMapper
来处理从REF
游标返回的行的映射。另一个选项是指定SqlReturnType
,它允许您定义 return 值的自定义处理。
简单 DAO 的下一个示例使用StoredProcedure
来调用 function(sysdate()
),它随任何 Oracle 数据库一起提供。要使用存储过程功能,您必须创建一个扩展StoredProcedure
的 class。在这个 example 中,StoredProcedure
class 是一个内部 class。但是,如果需要重用StoredProcedure
,则可以将其声明为 top-level class。此 example 没有输入参数,但通过使用SqlOutParameter
class 将输出参数声明为 date 类型。 execute()
方法运行该过程并从结果Map
中提取返回的 date。结果Map
通过使用参数 name 作为 key,为每个声明的输出参数(在本例中只有一个)提供了一个条目。以下清单显示了我们的自定义 StoredProcedure class:
1 | import java.sql.Types; |
以下的示例有两个输出参数(在本例中为 Oracle REF 游标):
1 | import java.util.HashMap; |
注意TitlesAndGenresStoredProcedure
构造函数中使用的declareParameter(..)
方法的重载变体是如何传递RowMapper
implementation 实例的。这是重用现有功能的一种非常方便和强大的方法。接下来的两个示例为两个RowMapper
实现提供了 code。
对于提供的ResultSet
中的每一行,TitleMapper
class maps 到Title
域 object,如下所示:
1 | import java.sql.ResultSet; |
对于提供的ResultSet
中的每一行,GenreMapper
class maps 到Genre
域 object,如下所示:
1 | import java.sql.ResultSet; |
要将参数传递给在 RDBMS 的定义中具有一个或多个输入参数的存储过程,您可以 code 一个强类型的execute(..)
方法,该方法将委托给超类中的无类型execute(Map)
方法,如下面的 example 所示:
1 | import java.sql.Types; |
参数和数据Value处理的常见问题
参数和数据值的常见问题存在于 Spring Framework 的 JDBC 支持提供的不同方法中。本节介绍如何解决这些问题。
为参数提供SQL类型信息
通常,Spring 根据传入的参数类型确定参数的 SQL 类型。可以显式提供设置参数值时要使用的 SQL 类型。有时需要正确设置NULL
值。
您可以通过多种方式提供 SQL 类型信息:
JdbcTemplate
的许多更新和查询方法以int
array 的形式获取附加参数。此 array 用于通过使用java.sql.Types
class 中的常量值来指示相应参数的 SQL 类型。为每个参数提供一个条目。- 您可以使用
SqlParameterValue
class 来包装需要此附加信息的参数 value。为此,请为每个 value 创建一个新实例,并在构造函数中传入 SQL 类型和参数 value。您还可以为数值提供可选的缩放参数。 - 对于使用命名参数的方法,可以使用
SqlParameterSource
classes,BeanPropertySqlParameterSource
或MapSqlParameterSource
。它们都具有为任何命名参数值注册 SQL 类型的方法。
处理BLOB和CLOB对象
您可以存储数据库中的图像,其他二进制数据和大块文本。这些大 objects 被称为 BLOB(二进制大 OBject)用于二进制数据和 CLOB(字符大 OBject)用于字符数据。在 Spring 中,您可以直接使用JdbcTemplate
处理这些大型 object,也可以使用 RDBMS Objects 和SimpleJdbc
classes 提供的更高抽象。所有这些方法都使用LobHandler
接口的 implementation 来实现 LOB(Large OBject)数据的实际管理。 LobHandler
通过getLobCreator
方法提供对LobCreator
class 的访问,该方法用于创建要插入的新 LOB objects。
LobCreator
和LobHandler
为 LOB 输入和输出提供以下支持:
- BLOB
byte[]
:getBlobAsBytes
和setBlobAsBytes
InputStream
:getBlobAsBinaryStream
和setBlobAsBinaryStream
- CLOB
String
:getClobAsString
和setClobAsString
InputStream
:getClobAsAsciiStream
和setClobAsAsciiStream
Reader
:getClobAsCharacterStream
和setClobAsCharacterStream
下一个 example 显示了如何创建和插入 BLOB。稍后我们将展示如何从数据库中读取它。
此 example 使用AbstractLobCreatingPreparedStatementCallback
和AbstractLobCreatingPreparedStatementCallback
的 implementation。它实现了一个方法setValues
。此方法提供了一个LobCreator
,我们用它来设置 SQL insert 语句中 LOB 列的值。
对于这个例子,我们假设有一个变量lobHandler
,它已经被设置为DefaultLobHandler
的一个实例。您通常通过依赖注入设置此 value。
以下 example 显示了如何创建和插入 BLOB:
1 | final File blobIn = new File("spring2004.jpg"); |
1 | 传入lobHandler (在此 example 中)是一个普通的DefaultLobHandler 。 |
2 | 使用方法setClobAsCharacterStream 传入 CLOB 的内容。 |
3 | 使用方法setBlobAsBinaryStream 传入 BLOB 的内容。 |
如果从
DefaultLobHandler.getLobCreator()
返回的LobCreator
上调用setBlobAsBinaryStream
,setClobAsAsciiStream
或setClobAsCharacterStream
方法,则可以选择为contentLength
参数指定负值。如果指定的内容长度为负,则DefaultLobHandler
使用 set-stream 方法的 JDBC 4.0 变体而不使用 length 参数。否则,它将指定的长度传递给驱动程序。
请参阅用于验证它是否支持在不提供内容长度的情况下流式传输 LOB 的 JDBC 驱动程序的文档。
现在是 time 从数据库中读取 LOB 数据。同样,您使用JdbcTemplate
与相同的实例变量lobHandler
和 reference 到DefaultLobHandler
。以下 example 显示了如何执行此操作:
1 | List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table", |
1 | 使用方法getClobAsString 来检索 CLOB 的内容。 |
2 | 使用方法getBlobAsBytes 来检索 BLOB 的内容。 |
传入IN条款的ListsofValues
SQL 标准允许基于包含变量值列表的表达式来选择行。典型的 example 将是select * from T_ACTOR where id in (1, 2, 3)
。 JDBC 标准不直接支持准备的 statements 这个变量列表。您不能声明可变数量的占位符。您需要准备好所需占位符数量的多种变体,或者一旦知道需要多少占位符,就需要动态生成 SQL string。 NamedParameterJdbcTemplate
和JdbcTemplate
中提供的命名参数支持采用后一种方法。您可以将值作为java.util.List
原始 objects 传递。此列表用于插入所需的占位符并在语句执行期间传入值。
传递许多值时要小心。 JDBC 标准不保证您可以为
in
表达式列表使用 100 个以上的值。各种数据库超过此数量,但它们通常对允许的值有多少硬性限制。例如,Oracle 的限制为 1000。
除了 value 列表中的原始值之外,您还可以创建java.util.List
的 object 数组。此列表可以支持为in
子句定义的多个表达式,例如select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'\))
。当然,这要求您的数据库支持此语法。
处理存储过程的复杂类型Calls
调用存储过程时,有时可以使用特定于数据库的复杂类型。为了适应这些类型,Spring 提供SqlReturnType
用于在从存储过程调用返回它们时处理它们,并在它们作为参数传入存储过程时SqlTypeValue
。
SqlReturnType
接口有一个必须实现的方法(名为getTypeValue
)。此接口用作SqlOutParameter
声明的一部分。以下 example 显示返回用户声明类型ITEM_TYPE
的 Oracle STRUCT
object 的 value:
1 | public class TestItemStoredProcedure extends StoredProcedure { |
您可以使用SqlTypeValue
将 Java object 的 value(例如TestItem
)传递给存储过程。 SqlTypeValue
接口有一个必须实现的方法(名为createTypeValue
)。传入 active 连接,您可以使用它来创建 database-specific objects,例如StructDescriptor
实例或ArrayDescriptor
实例。以下 example 创建StructDescriptor
实例:
1 | final TestItem testItem = new TestItem(123L, "A test item", |
您现在可以将此SqlTypeValue
添加到Map
,其中包含存储过程的execute
调用的输入参数。
SqlTypeValue
的另一个用途是将 array 值传递给 Oracle 存储过程。 Oracle 有自己的内部ARRAY
class,在这种情况下必须使用它,您可以使用SqlTypeValue
创建 Oracle ARRAY
的实例并使用 Java ARRAY
中的值填充它,如下面的示例所示:
1 | final Long[] ids = new Long[] {1L, 2L}; |
嵌入式数据库支持
org.springframework.jdbc.datasource.embedded
包提供对嵌入式 Java 数据库引擎的支持。本机提供对HSQL,H2和Derby的支持。您还可以使用可扩展 API 来插入新的嵌入式数据库类型和DataSource
实现。
为何使用嵌入式数据库?
嵌入式数据库在项目的开发阶段非常有用,因为它具有轻量级特性。优点包括易于配置,快速启动 time,可测试性以及在开发过程中快速发展 SQL 的能力。
使用SpringXML创建嵌入式数据库
如果要在 Spring ApplicationContext
中将嵌入式数据库实例公开为 bean,则可以在spring-jdbc
命名空间中使用embedded-database
标记:
1 | <jdbc:embedded-database id="dataSource" generate-name="true"> |
前面的 configuration 创建一个嵌入式 HSQL 数据库,该数据库使用 SQL 填充 classpath 根目录中的schema.sql
和test-data.sql
资源。此外,作为最佳实践,将为嵌入式数据库分配唯一生成的 name。嵌入式数据库作为类型的 bean 可用于 Spring 容器,然后可根据需要将其注入数据访问 objects。
以编程方式创建嵌入式数据库
EmbeddedDatabaseBuilder
class 提供了一个 fluent API,用于以编程方式构建嵌入式数据库。当您需要在 stand-alone 环境或 stand-alone integration 测试中创建嵌入式数据库时,可以使用此方法,如下面的示例所示:
1 | EmbeddedDatabase db = new EmbeddedDatabaseBuilder() |
有关所有支持选项的更多详细信息,请参阅用于 EmbeddedDatabaseBuilder 的 javadoc。
您还可以使用通过 Java configuration 创建嵌入式数据库,如下面的示例所示:
1 |
|
选择嵌入式数据库类型
本节介绍如何选择 Spring 支持的三个嵌入式数据库之一。它包括以下主题:
使用HSQL
Spring 支持 HSQL 1.8.0 及以上版本。如果未明确指定类型,HSQL 是默认的嵌入式数据库。要显式指定 HSQL,请将embedded-database
标记的type
属性设置为HSQL
。如果使用构建器 API,请使用EmbeddedDatabaseType.HSQL
调用setType(EmbeddedDatabaseType)
方法。
使用H2
Spring 支持 H2 数据库。要启用 H2,请将embedded-database
标记的type
属性设置为H2
。如果使用构建器 API,请使用EmbeddedDatabaseType.H2
调用setType(EmbeddedDatabaseType)
方法。
使用Derby
Spring 支持 Apache Derby 10.5 及以上版本。要启用 Derby,请将embedded-database
标记的type
属性设置为DERBY
。如果使用构建器 API,请使用EmbeddedDatabaseType.DERBY
调用setType(EmbeddedDatabaseType)
方法。
使用嵌入式数据库测试数据访问逻辑
嵌入式数据库提供了一种测试数据访问 code 的轻量级方法。下一个 example 是一个使用嵌入式数据库的数据访问 integration 测试模板。当嵌入式数据库不需要跨测试 classes 重用时,使用这样的模板对于 one-offs 非常有用。但是,如果您希望创建在测试套件中共享的嵌入式数据库,请考虑使用Spring TestContext Framework并将嵌入式数据库配置为 Spring ApplicationContext
中的 bean,如使用 Spring XML 创建嵌入式数据库和以编程方式创建嵌入式数据库中所述。以下清单显示了测试模板:
1 | public class DataAccessIntegrationTestTemplate { |
为嵌入式数据库生成唯一名称
如果测试套件无意中尝试重新创建同一数据库的其他实例,则开发团队经常会遇到嵌入式数据库的错误。如果 XML configuration 文件或@Configuration
class 负责创建嵌入式数据库,并且相应的 configuration 随后在同一个测试套件中的多个测试场景中重复使用(即,在同一个 JVM process 中),则这很容易发生 - 对于 example, integration 测试针对嵌入式数据库,其ApplicationContext
configuration 仅在 bean 定义 profiles 为 active 时有所不同。
这种错误的根本原因是 Spring 的EmbeddedDatabaseFactory
(由<jdbc:embedded-database>
XML 命名空间元素和EmbeddedDatabaseBuilder
用于 Java configuration)在内部使用_如果没有另外指定,则将嵌入式数据库的 name 设置为testdb
。对于<jdbc:embedded-database>
的情况,嵌入式数据库通常被赋予 name 等于 bean 的id
(通常类似于dataSource
)。因此,后续创建嵌入式数据库的尝试不会产生新的数据库。相反,重用相同的 JDBC 连接 URL,并且尝试创建新的嵌入式数据库实际上指向从相同的 configuration 创建的现有嵌入式数据库。
为了解决这个常见问题,Spring Framework 4.2 支持为嵌入式数据库生成唯一名称。要启用生成的名称,请使用以下选项之一。
EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()
EmbeddedDatabaseBuilder.generateUniqueName()
<jdbc:embedded-database generate-name="true" … >
扩展嵌入式数据库支持
您可以通过两种方式扩展 Spring JDBC 嵌入式数据库支持:
- 实现
EmbeddedDatabaseConfigurer
以支持新的嵌入式数据库类型。 - 实现
DataSourceFactory
以支持新的DataSource
implementation,例如用于管理嵌入式数据库连接的连接池。
我们鼓励您在jira.spring.io向 Spring 社区贡献 extensions。
初始化DataSource
org.springframework.jdbc.datasource.init
包提供对初始化现有DataSource
的支持。嵌入式数据库支持提供了一个选项,用于为 application 创建DataSource
。但是,您有时可能需要初始化在某个服务器上运行的实例。
使用SpringXML初始化数据库
如果要初始化数据库并且可以为DataSource
bean 提供 reference,则可以在spring-jdbc
命名空间中使用initialize-database
标记:
1 | <jdbc:initialize-database data-source="dataSource"> |
前面的 example 针对数据库运行两个指定的脚本。第一个脚本创建 schema,第二个脚本使用测试数据集填充表。脚本位置也可以是带有通配符的通配符的模式,用于 Spring 中的资源(对于 example,classpath*:/com/foo/**/sql/*-data.sql
)。如果使用 pattern,则脚本在其 URL 或文件名的词法 order 中运行。
数据库初始化程序的默认行为是无条件地运行提供的脚本。这可能并不总是您想要的 - 例如,如果您针对已经包含测试数据的数据库运行脚本。通过首先创建表的 common pattern(如前所示)然后插入数据,可以减少意外删除数据的可能性。如果表已经存在,则第一个 step 会失败。
但是,为了更好地控制现有数据的创建和删除,XML 命名空间提供了一些其他选项。第一个是 flag 来打开和关闭初始化。您可以根据环境设置它(例如从系统 properties 或环境 bean 中提取 boolean value)。以下 example 从系统 property 获取 value:
1 | <jdbc:initialize-database data-source="dataSource" |
1 | 从名为INITIALIZE_DATABASE 的系统 property 获取enabled 的 value。 |
控制现有数据发生情况的第二个选择是更容忍失败。为此,您可以控制初始化程序忽略它从脚本执行的 SQL 中的某些错误的能力,如下面的示例所示:
1 | <jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS"> |
在前面的示例中,我们说我们希望脚本有时会针对空数据库运行,并且脚本中有一些DROP
statements 因此会失败。因此失败的 SQL DROP
statements 将被忽略,但其他失败将导致 exception。如果您的 SQL 方言不支持DROP … IF EXISTS
(或类似),但您希望在 re-creating 之前无条件地删除所有测试数据,这将非常有用。在这种情况下,第一个脚本通常是一组DROP
statements,后跟一组CREATE
statements。
ignore-failures
选项可以设置为NONE
(默认值),DROPS
(忽略失败的丢弃)或ALL
(忽略所有失败)。
如果脚本中根本不存在;
字符,则每个语句应由;
或新 line 分隔。您可以按脚本控制全局或脚本,如下面的 example 所示:
1 | <jdbc:initialize-database data-source="dataSource" separator="@@"> (1) |
1 | 将分隔符脚本设置为@@ 。 |
2 | 将db-schema.sql 的分隔符设置为; 。 |
在这个 example 中,两个test-data
脚本使用@@
作为语句分隔符,只有db-schema.sql
使用;
。此 configuration 指定默认分隔符为@@
并覆盖db-schema
脚本的默认值。
如果您需要比从 XML 命名空间获得的更多控制,可以直接使用DataSourceInitializer
并将其定义为 application 中的 component。
初始化依赖于数据库的其他组件
一个大的 class 的 applications(那些在 Spring context 启动之后才使用数据库的那些)可以使用数据库初始化程序而没有进一步的复杂化。如果您的 application 不是其中之一,您可能需要阅读本节的 rest。
数据库初始化程序依赖于DataSource
实例并运行其初始化回调中提供的脚本(类似于 XML bean 定义中的init-method
,component 中的@PostConstruct
方法或实现InitializingBean
的 component 中的afterPropertiesSet()
方法)。如果其他 beans 依赖于相同的数据源并在初始化回调中使用数据源,则可能存在问题,因为数据尚未初始化。 common example 是一个缓存,它急切地初始化并在 application 启动时从数据库加载数据。
要解决此问题,您有两个选择:将缓存初始化策略更改为稍后阶段或确保首先初始化数据库初始化程序。
如果 application 在您的控件中而不是其他方式,则更改缓存初始化策略可能很容易。关于如何实现这一点的一些建议包括:
- 在第一次使用时使缓存初始化,这可以改善 application startup time。
- 让缓存或初始化缓存的单独 component 实现
Lifecycle
或SmartLifecycle
。当 application context 启动时,您可以通过设置autoStartup
flag 自动启动SmartLifecycle
,并且可以通过在封闭的 context 上调用ConfigurableApplicationContext.start()
来手动启动Lifecycle
。 - 使用 Spring
ApplicationEvent
或类似的自定义观察器机制来触发缓存初始化。 总是在 context 准备好使用时发布(在所有 beans 初始化之后),所以这通常是一个有用的 hook(这是SmartLifecycle
默认工作的方式)。
确保首先初始化数据库初始化程序也很容易。关于如何实现这一点的一些建议包括:
- 依赖 Spring
BeanFactory
的默认行为,即 beans 在 registration order 中初始化。您可以通过采用@ - 将
DataSource
和使用它的业务组件分开并通过将它们放在单独的ApplicationContext
实例中来控制它们的启动 order(对于 example,parent context 包含DataSource
,child context 包含业务组件)。这个结构在 Spring web applications 中是 common,但可以更普遍地应用。
Object关系映射(ORM)数据访问
本节介绍使用 Object 关系映射(ORM)时的数据访问。
使用Spring介绍ORM
Spring Framework 支持与 Java Persistence API(JPA)的 integration,并支持本机 Hibernate 用于资源 management,数据访问 object(DAO)implementations 和 transaction 策略。例如,对于 Hibernate,有 first-class 支持几个方便的 IoC features,可以解决许多典型的 Hibernate integration 问题。您可以通过依赖注入为 OR(object 关系)映射工具配置所有支持的 features。他们可以参与 Spring 的资源和 transaction management,它们符合 Spring 的通用 transaction 和 DAO exception 层次结构。推荐的 integration 样式是针对普通的 Hibernate 或 JPA API 来编码 DAO。
当您创建数据访问应用程序时,Spring 会为您选择的 ORM 层添加重要的增强功能。您可以根据需要利用尽可能多的 integration 支持,并且您应该将此集成工作与构建类似基础架构的成本和风险进行比较 in-house。您可以像使用 library 一样使用大部分 ORM 支持,无论技术如何,因为所有内容都设计为一组可重用的 JavaBeans。 Spring IoC 容器中的 ORM 有助于配置和部署。因此,本节中的大多数示例都显示了 Spring 容器中的 configuration。
使用 Spring Framework 创建 ORM DAO 的好处包括:
- 更容易测试. Spring 的 IoC 方法可以轻松交换 Hibernate
SessionFactory
实例,JDBCDataSource
实例,transaction managers 和映射 object implementations(如果需要)的 implementations 和 configuration 位置。这反过来使得单独测试每个 persistence-related code 更容易。 - 公共数据访问 exceptions. Spring 可以从 ORM 工具中包装 exceptions,将它们从专有(可能已检查)的 exceptions 转换为 common runtime
DataAccessException
层次结构。这个 feature 允许您处理大多数持久性 exceptions,它们只是在适当的层中,而不会产生令人烦恼的样板捕获,抛出和 exception 声明。您仍然可以根据需要捕获和处理 exceptions。请记住,JDBC exceptions(包括 DB-specific 方言)也会转换为相同的层次结构,这意味着您可以在一致的编程 model 中使用 JDBC 执行某些操作。 - 常规资源 management. Spring application 上下文可以处理 Hibernate
SessionFactory
实例,JPAEntityManagerFactory
实例,JDBCDataSource
实例和其他相关资源的位置和 configuration。这使得这些值易于管理和更改。 Spring 提供高效,简单,安全的持久性资源处理。对于 example,使用 Hibernate 的相关 code 通常需要使用相同的 HibernateSession
来确保效率和正确的 transaction 处理。 Spring 通过 HibernateSessionFactory
公开当前Session
,可以很容易地透明地创建和绑定Session
到当前线程。因此,对于任何本地或 JTA transaction 环境,Spring 解决了典型 Hibernate 使用的许多慢性问题。 - 集成 transaction management.您可以通过
@Transactional
annotation 或通过在 XML configuration 文件中显式配置 transaction AOP 建议,使用声明性的 aspect-oriented 编程(AOP)样式方法拦截器包装 ORM code。在这两种情况下,都会为您处理 transaction 语义和 exception 处理(回滚等)。如资源和 Transaction Management中所述,您还可以交换各种 transaction managers,而不会影响 ORM-related code。例如,您可以在本地 transactions 和 JTA 之间切换,在两种方案中都可以使用相同的完整服务(例如声明性 transactions)。此外,JDBC-related code 可以与您用于执行 ORM 的 code 进行事务性完全集成。这对于不适合 ORM 的数据访问(例如批处理和 BLOB 流)很有用,但仍需要与 ORM 操作共享 common transactions。
要获得更全面的 ORM 支持,包括对 MongoDB 等替代数据库技术的支持,您可能需要查看Spring Data项目套件。如果您是 JPA 用户,https://spring.io的入门使用 JPA 访问数据指南提供了很好的介绍。
一般ORM集成注意事项
本节重点介绍适用于所有 ORM 技术的注意事项。 Hibernate部分提供了更多详细信息,并在具体的 context 中显示了这些 features 和配置。
Spring 的 ORM integration 的主要目标是清晰的 application 分层(使用任何数据访问和 transaction 技术)以及 application objects 的松散耦合 - 不再需要对数据访问或 transaction 策略的业务服务依赖,不再需要 hard-coded 资源查找,不再需要 hard-to-replace 单身人士,没有更多的定制服务注册。目标是使用一种简单而一致的方法来连接 application objects,使它们保持可重用性并尽可能地避免容器依赖。所有单独的数据访问 features 都可以单独使用,但可以很好地与 Spring 的 application context 概念集成,提供 XML-based configuration 和 cross-referencing 无需 Spring-aware 的普通 JavaBean 实例。在典型的 Spring application 中,许多重要的 objects 是 JavaBeans:数据访问模板,数据访问 objects,transaction managers,使用数据访问的业务服务 objects 和 transaction managers,web 视图解析器,web 控制器使用业务服务,等等。
资源和事务管理
典型的业务应用程序混杂着重复的资源 management code。许多项目试图发明自己的解决方案,有时为了方便编程而牺牲正确的故障处理。 Spring 提出了适当资源处理的简单解决方案,即在 JDBC 的情况下通过模板化 IoC 并为 ORM 技术应用 AOP 拦截器。
基础结构提供适当的资源处理以及将特定 API exceptions 适当转换为未经检查的基础结构 exception 层次结构。 Spring 引入了 DAO exception 层次结构,适用于任何数据访问策略。对于直接 JDBC,上一节中提到的JdbcTemplate
class 提供连接处理和SQLException
到DataAccessException
层次的正确转换,包括将 database-specific SQL 错误代码转换为有意义的 exception classes。对于 ORM 技术,请参阅下一节以了解如何获得相同的 exception 转换优势。
当涉及 transaction management 时,JdbcTemplate
class 挂钩到 Spring transaction 支持,并通过各自的 Spring transaction managers 支持 JTA 和 JDBC transactions。对于支持的 ORM 技术,Spring 通过 Hibernate 和 JPA transaction managers 以及 JTA 支持提供 Hibernate 和 JPA 支持。有关 transaction 支持的详细信息,请参阅Transaction Management章节。
Exception转换
在 DAO 中使用 Hibernate 或 JPA 时,必须决定如何处理持久性技术的本机 exception classes。 DAO 会抛出HibernateException
或PersistenceException
的子类,具体取决于技术。这些 exceptions 都是运行时 exceptions,不必声明或捕获。您可能还需要处理IllegalArgumentException
和IllegalStateException
。这意味着调用者只能将 exceptions 视为通常致命的,除非他们想依赖持久性技术自己的 exception 结构。如果不将调用者绑定到 implementation 策略,则无法捕获特定原因(例如乐观锁定失败)。对于强 ORM-based 或不需要任何特殊 exception 处理(或两者)的应用程序,这个 trade-off 可能是可以接受的。但是,Spring 允许通过@Repository
annotation 透明地应用 exception 转换。以下示例(一个用于 Java configuration,一个用于 XML configuration)显示了如何执行此操作:
1 |
|
1 | <beans> |
后处理器自动查找所有 exception 转换器(PersistenceExceptionTranslator
接口的 implementations),并建议所有标记有@Repository
annotation 的 beans,以便发现的转换器可以拦截并在抛出的 exceptions 上应用适当的转换。
总之,您可以基于普通持久性技术的 API 和 annotations 实现 DAO,同时仍然可以从 Spring 的自定义 exception 层次结构中受益于 Spring-managed transactions,依赖注入和透明 exception 转换(如果需要)。
Hibernate
我们从 Spring 环境中的Hibernate 5开始,用它来演示 Spring 对集成 OR 映射器的方法。本节详细介绍了许多问题,并展示了 DAO implementations 和 transaction demarcation 的不同变体。大多数这些模式可以直接转换为所有其他支持的 ORM 工具。然后,本章后面的部分将介绍其他 ORM 技术并展示简要示例。
从 Spring Framework 5.0 开始,Spring 需要 Hibernate ORM 4.3 或更高版本用于 JPA 支持,甚至 Hibernate ORM 5.0 用于针对本机 Hibernate Session API 进行编程。请注意,Hibernate 团队不再维护 5.1 之前的任何版本,很可能很快就会专注于 5.3.
Spring容器中的SessionFactory设置
为避免将 application objects 绑定到 hard-coded 资源查找,您可以在 Spring 容器中将资源(例如 JDBC DataSource
或 Hibernate SessionFactory
)定义为 beans。需要访问资源的 Application objects 通过 bean references 接收 references 到这些预定义实例,如下一节中的 DAO 定义所示。
以下摘自 XML application context 定义显示了如何在其上设置 JDBC DataSource
和 Hibernate SessionFactory
:
1 | <beans> |
从本地 Jakarta Commons _DBCP BasicDataSource
切换到 JNDI-located DataSource
(通常由 application 服务器管理)只是 configuration 的问题,如下面的 example 所示:
1 | <beans> |
您还可以使用 Spring 的JndiObjectFactoryBean
/<jee:jndi-lookup>
来访问 JNDI-located SessionFactory
来检索并公开它。但是,这通常不是 EJB context 之外的 common。
Spring 还提供
LocalSessionFactoryBuilder
变体,与@Bean
style configuration 和编程设置无缝集成(不涉及FactoryBean
)。
LocalSessionFactoryBean
和LocalSessionFactoryBuilder
都支持后台引导,Hibernate 初始化 running _ parallel 与给定引导程序执行程序(例如SimpleAsyncTaskExecutor
)上的 application 引导程序线程。在LocalSessionFactoryBean
上,这可以通过bootstrapExecutor
property 获得。在程序的LocalSessionFactoryBuilder
上,有一个重载的buildSessionFactory
方法,它接受一个 bootstrap executor 参数。
从 Spring Framework 5.1 开始,这样的原生 Hibernate 设置还可以在本机 Hibernate 访问旁边公开 JPA EntityManagerFactory
以进行标准 JPA 交互。有关详细信息,请参阅JPA 的原生 Hibernate 设置。
基于PlainHibernateAPI实现DAO
Hibernate 有一个称为上下文会话的 feature,其中 Hibernate 本身管理每个 transaction 的一个当前Session
。这大致相当于 Spring 同步 Hibernate Session
每 transaction。相应的 DAO implementation 类似于以下 example,基于 plain Hibernate API:
1 | public class ProductDaoImpl implements ProductDao { |
除了将SessionFactory
保存在实例变量中之外,此样式类似于 Hibernate reference 文档和示例的样式。我们强烈建议在 Hibernate 的 CaveatEmptor sample application 中对 old-school static
HibernateUtil
class 进行这样的 instance-based 设置。 (一般情况下,除非绝对 necessary.),否则不要在static
变量中保留任何资源
前面的 DAO example 遵循依赖注入 pattern。它非常适合 Spring IoC 容器,就像它对 Spring 的HibernateTemplate
进行编码一样。您还可以在普通 Java 中设置这样的 DAO(例如,在单元测试中)。为此,请实例化它并使用所需的工厂 reference 调用setSessionFactory(..)
。作为 Spring bean 定义,DAO 将类似于以下内容:
1 | <beans> |
这种 DAO 风格的主要优点是它仅依赖于 Hibernate API。不需要任何 Spring class 的 import。这从 non-invasiveness 的角度来看很吸引人,并且对于 Hibernate 开发人员来说可能会更自然。
但是,DAO 抛出普通HibernateException
(未经检查,因此不必声明或捕获),这意味着调用者只能将 exceptions 视为通常致命 - 除非他们想要依赖 Hibernate 自己的 exception 层次结构。如果不将调用者绑定到 implementation 策略,则无法捕获特定原因(例如乐观锁定失败)。对于强烈 Hibernate-based 的应用程序,不需要任何特殊的 exception 处理,或两者兼而有之,这种权衡可能是可以接受的。
幸运的是,Spring 的LocalSessionFactoryBean
支持 Hibernate 的SessionFactory.getCurrentSession()
方法用于任何 Spring transaction 策略,返回当前的 Spring-managed transactional Session
,即使是HibernateTransactionManager
。该方法的标准行为仍然是 return 当前Session
与正在进行的 JTA transaction 相关联,如果有的话。无论您使用 Spring 的JtaTransactionManager
,EJB 容器托管 transactions(CMT)还是 JTA,此行为都适用。
总之,您可以基于普通的 Hibernate API 实现 DAO,同时仍然可以参与 Spring-managed transactions。
声明式事务划分
我们建议您使用 Spring 的声明性 transaction 支持,它允许您使用 AOP transaction 拦截器替换 Java code 中的显式 transaction 分界 API calls。您可以使用 Java annotations 或 XML 在 Spring 容器中配置此 transaction 拦截器。这种声明式 transaction 功能可以让您保持业务服务不受重复的 transaction demarcation code 的影响,并专注于添加业务逻辑,这是您的 application 的真正值。
在您继续之前,如果您还没有这样做,我们强烈建议您阅读声明式 transaction management。
您可以使用@Transactional
annotations 注释服务层,并指示 Spring 容器查找这些注释并为这些带注释的方法提供 transactional 语义。以下 example 显示了如何执行此操作:
1 | public class ProductServiceImpl implements ProductService { |
在容器中,您需要设置PlatformTransactionManager
实现(作为 bean)和<tx:annotation-driven/>
条目,在运行时选择@Transactional
处理。以下 example 显示了如何执行此操作:
1 |
|
编程式事务划分
您可以在数据访问服务任意数量的操作之上,在 application 的更高 level 中划分 transactions。对周围业务服务的实施也没有限制。它只需要 Spring PlatformTransactionManager
。同样,后者可以来自任何地方,但最好是通过setTransactionManager(..)
方法作为 bean reference。此外,productDAO
应该由setProductDao(..)
方法设置。以下一对片段在 Spring application context 中显示 transaction manager 和业务服务定义,为业务方法 implementation 显示 example:
1 | <beans> |
Spring 的TransactionInterceptor
允许使用回调 code 抛出任何已检查的 application exception,而TransactionTemplate
仅限于回调中未经检查的 exceptions。 TransactionTemplate
在未经检查的 application exception 的情况下触发回滚,或者 transaction 被 application 标记为 rollback-only(通过设置TransactionStatus
)。默认情况下,TransactionInterceptor
的行为方式相同,但每个方法允许可配置的回滚 policies。
事务管理策略
TransactionTemplate
和TransactionInterceptor
将实际的 transaction 处理委托给PlatformTransactionManager
实例(可以是HibernateTransactionManager
(对于单个 Hibernate SessionFactory
),通过使用ThreadLocal
Session
)或JtaTransactionManager
(委托容器的 JTA 子系统)为 Hibernate applications。您甚至可以使用自定义PlatformTransactionManager
implementation。从本机 Hibernate transaction management 切换到 JTA(例如,当面对 application 的某些部署面临分布式 transaction 要求时)只是 configuration 的问题。您可以使用 Spring 的 JTA transaction implementation 替换 Hibernate transaction manager。 transaction 划分和数据访问 code 无需更改即可正常工作,因为它们使用通用的 transaction management API。
对于跨多个 Hibernate session 工厂的分布式 transactions,您可以将JtaTransactionManager
组合为 transaction 策略和多个LocalSessionFactoryBean
定义。然后每个 DAO 将一个特定的SessionFactory
reference 传递到其对应的 bean property。如果所有基础 JDBC 数据源都是 transactional 容器,则业务服务可以跨越任意数量的 DAO 和任意数量的 session 工厂划分 transactions,而无需特别考虑,因为它使用JtaTransactionManager
作为策略。
HibernateTransactionManager
和JtaTransactionManager
都允许使用 Hibernate 进行正确的 JVM-level 缓存处理,无需 container-specific transaction manager 查找或 JCA 连接器(如果不使用 EJB 来启动 transactions)。
HibernateTransactionManager
可以_portport Hibernate JDBC Connection
到普通 JDBC 访问 code 以获取特定的DataSource
。如果您只访问一个数据库,则此功能允许在没有 JTA 的情况下完全混合 Hibernate 和 JDBC 数据访问进行 high-level transaction 划分。如果通过LocalSessionFactoryBean
class 的dataSource
property 设置 passed-in SessionFactory
和DataSource
,HibernateTransactionManager
会自动将 Hibernate transaction 公开为 JDBC transaction。或者,您可以通过HibernateTransactionManager
class 的dataSource
property 明确指定 transactions 应该公开的DataSource
。
比较Container-managed和本地定义的资源
您可以在 container-managed JNDI SessionFactory
和本地定义的之间切换,而无需更改 application code 的单个 line。是将资源定义保留在容器中还是本地保存在 application 中主要是您使用的 transaction 策略的问题。与 Spring-defined local SessionFactory
相比,手动注册的 JNDI SessionFactory
不提供任何好处。部署SessionFactory
到 Hibernate 的 JCA 连接器提供了参与 Java EE 服务器的 management 基础结构的附加 value,但不会添加超出该值的实际 value。
Spring 的 transaction 支持不绑定到容器。使用除 JTA 之外的任何策略进行配置时,transaction 支持也适用于 stand-alone 或测试环境。特别是在 single-database transactions 的典型情况下,Spring 的 single-resource local transaction 支持是 JTA 的轻量级和强大的替代品。当您使用本地 EJB stateless session beans 来驱动 transactions 时,您既依赖于 EJB 容器又依赖于 JTA,即使您只访问单个数据库并仅使用 stateless session beans 通过 container-managed transactions 提供声明性 transactions。以编程方式直接使用 JTA 还需要 Java EE 环境。 JTA 不仅涉及 JTA 本身和 JNDI DataSource
实例的容器依赖性。对于 non-Spring,JTA-driven Hibernate transactions,您必须使用 Hibernate JCA 连接器或额外的 Hibernate transaction code 与TransactionManagerLookup
配置正确的 JVM-level 缓存。
Spring-driven transactions 可以与本地定义的 Hibernate SessionFactory
一样工作,就像它们使用本地 JDBC DataSource
一样,前提是它们访问单个数据库。因此,当您具有分布式 transaction 要求时,只需使用 Spring 的 JTA transaction 策略。 JCA 连接器需要 container-specific 部署步骤,并且(显然)首先需要 JCA 支持。与使用本地资源定义和 Spring-driven transactions 部署简单的 web application 相比,此 configuration 需要更多的工作。此外,如果您使用的是 WebLogic Express,则通常需要容器的 Enterprise Edition,而不是提供 JCA。 Spring application 与本地资源和 transactions span 单个数据库在任何 Java EE web 容器(没有 JTA,JCA 或 EJB)中工作,例如 Tomcat,Resin,甚至是普通的 Jetty。此外,您可以轻松地在桌面应用程序或测试套件中重用这样的中间层。
考虑到所有事情,如果你不使用 EJB,坚持使用本地SessionFactory
设置和 Spring 的HibernateTransactionManager
或JtaTransactionManager
。您可以获得所有好处,包括正确的 transactional JVM-level 缓存和分布式 transactions,而不会给容器部署带来不便。通过 JCA 连接器对 Hibernate SessionFactory
进行 JNDI 注册只有在与 EJB 一起使用时才会添加 value。
带有Hibernate的虚假ApplicationServer警告
在一些具有非常严格的XADataSource
实现(当前只有一些 WebLogic Server 和 WebSphere 版本)的 JTA 环境中,当配置 Hibernate 而不考虑该环境的 JTA PlatformTransactionManager
object 时,虚假警告或 exceptions 可以显示在 application server log 中。这些警告或 exceptions 指示正在访问的连接不再有效或 JDBC 访问不再有效,可能是因为 transaction 不再是 active。作为示例,这是 WebLogic 的实际 exception:
1 | java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No |
您可以通过让 Hibernate 知道它同步的 JTA PlatformTransactionManager
实例(以及 Spring)来解决此警告。您有两种选择:
- 如果在 application context 中,您已经直接获取 JTA
PlatformTransactionManager
object(可能是从 JNDI 到JndiObjectFactoryBean
或<jee:jndi-lookup>
)并将其提供给_Spample 的JtaTransactionManager
,最简单的方法是指定 bean 定义此 JTA 的 referencePlatformTransactionManager
实例作为LocalSessionFactoryBean.
property 的 valueLocalSessionFactoryBean.
Spring 然后使 object 可用于 Hibernate。 - 更有可能的是,你还没有 JTA
PlatformTransactionManager
实例,因为 Spring 的JtaTransactionManager
可以自己找到它。因此,您需要配置 Hibernate 以直接查找 JTAPlatformTransactionManager
。您可以通过在 Hibernate configuration 中配置 application server-specificTransactionManagerLookup
class 来完成此操作,如 Hibernate 手册中所述。
本节的其余部分描述了在 Hibernate 意识到 JTA PlatformTransactionManager
的情况下发生的 events 的顺序。
当 Hibernate 没有配置任何 JTA PlatformTransactionManager
的意识时,JTA transaction 提交时会发生以下 events:
- JTA transaction 提交。
- Spring 的
JtaTransactionManager
与 JTA transaction 同步,因此 JTA transaction manager 通过afterCompletion
回调调用它。 - 在其他活动中,这种同步可以通过 Spring 到 Hibernate 触发回调,通过 Hibernate 的
afterTransactionCompletion
回调(用于清除 Hibernate 缓存),然后在 Hibernate session 上调用close()
,这会导致 Hibernate 尝试close()
JDBC 连接。 - 在某些环境中,此
Connection.close()
调用会触发警告或错误,因为 application 服务器不再认为Connection
可用,因为 transaction 已经提交。
当 Hibernate 配置了 JTA PlatformTransactionManager
的意识时,JTA transaction 提交时会发生以下 events:
- JTA transaction 已准备好提交。
- Spring 的
JtaTransactionManager
与 JTA transaction 同步,因此 transaction 通过 JTA transaction manager 的beforeCompletion
回调来回调。 - Spring 意识到 Hibernate 本身与 JTA transaction 同步,其行为与前一个场景不同。假设 Hibernate
Session
需要完全关闭,Spring 现在关闭它。 - JTA transaction 提交。
- Hibernate 与 JTA transaction 同步,因此 transaction 由 JTA transaction manager 通过
afterCompletion
回调调用,并可以正确清除其缓存。
JPA
在org.springframework.orm.jpa
包下可用的 Spring JPA 以类似于与 Hibernate 的 integration 的方式提供对Java Persistence API的全面支持,同时知道 order 中的基础 implementation 以提供额外的 features。
Spring环境中JPA设置的三个选项
Spring JPA 支持提供了三种设置 JPA EntityManagerFactory
的方法,该方法由 application 用于获取实体 manager。
- 使用 LocalEntityManagerFactoryBean
- 从 JNDI 获取 EntityManagerFactory
- 使用 LocalContainerEntityManagerFactoryBean
使用 LocalEntityManagerFactoryBean
您只能在简单的部署环境中使用此选项,例如 stand-alone applications 和 integration 测试。
LocalEntityManagerFactoryBean
创建EntityManagerFactory
适用于简单的部署环境,其中 application 仅使用 JPA 进行数据访问。工厂 bean 使用 JPA PersistenceProvider
auto-detection 机制(根据 JPA 的 Java SE 引导),并且在大多数情况下,要求您仅指定持久性单元 name。以下 XML example 配置了这样的 bean:
1 | <beans> |
这种形式的 JPA 部署是最简单和最有限的。您不能引用现有的 JDBC DataSource
bean 定义,也不存在对 global transactions 的支持。此外,持久 class 的编织(byte-code 转换)是 provider-specific,通常需要在启动时指定特定的 JVM 代理。此选项仅适用于为其设计 JPA 规范的 stand-alone applications 和测试环境。
从JNDI获取EntityManagerFactory
部署到 Java EE 服务器时,可以使用此选项。检查服务器的文档,了解如何将自定义 JPA 提供程序部署到服务器中,从而允许使用与服务器默认提供程序不同的提供程序。
从 JNDI 获取EntityManagerFactory
(对于 Java EE 环境中的 example),需要更改 XML 配置,如下面的 example 所示:
1 | <beans> |
此操作假定标准 Java EE 引导。 Java EE 服务器 auto-detects 持久性单元(实际上, applicationjars 中的META-INF/persistence.xml
files)和 Java EE 部署描述符中的persistence-unit-ref
条目(用于 example,web.xml
),并为这些持久性单元定义环境命名 context 位置。
在这种情况下,整个持久性单元部署(包括持久 classes 的编织(byte-code 转换))由 Java EE 服务器决定。 JDBC DataSource
是通过META-INF/persistence.xml
文件中的 JNDI 位置定义的。 EntityManager
transactions 与服务器的 JTA 子系统集成在一起。 Spring 仅使用获得的EntityManagerFactory
,通过依赖注入将其传递给 application objects 并管理持久性单元的 transactions(通常通过JtaTransactionManager
)。
如果在同一个 application 中使用多个持久性单元,则此类 JNDI-retrieved 持久性单元的 bean 名称应匹配 application 用于引用它们的持久性单元名称(对于 example,在@PersistenceUnit
和@PersistenceContext
注释中)。
使用LocalContainerEntityManagerFactoryBean
您可以在 Spring-based application 环境中将此选项用于完整的 JPA 功能。这包括 web 容器,例如 Tomcat,stand-alone applications 和具有复杂持久性要求的 integration 测试。
如果你想专门配置 Hibernate 设置,一个直接的选择是使用 Hibernate 5.2 或 5.3 并设置一个原生的 Hibernate
LocalSessionFactoryBean
而不是一个普通的 JPALocalContainerEntityManagerFactoryBean
,让它与 JPA 访问 code 以及本机 Hibernate 访问 code 进行交互。有关详细信息,请参阅用于 JPA 交互的原生 Hibernate 设置。
LocalContainerEntityManagerFactoryBean
完全控制EntityManagerFactory
configuration,适用于需要 fine-grained 自定义的环境。 LocalContainerEntityManagerFactoryBean
基于persistence.xml
文件,提供的dataSourceLookup
策略和指定的loadTimeWeaver
创建PersistenceUnitInfo
实例。因此,可以使用 JNDI 之外的自定义数据源并控制编织进程。以下 example 显示了LocalContainerEntityManagerFactoryBean
的典型 bean 定义:
1 | <beans> |
以下 example 显示了一个典型的persistence.xml
文件:
1 | <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"> |
<exclude-unlisted-classes/>
快捷方式表示不应扫描带注释的实体 classes。显式的’true’value(<exclude-unlisted-classes>true</exclude-unlisted-classes/>
)也意味着没有扫描。<exclude-unlisted-classes>false</exclude-unlisted-classes/>
会触发扫描。但是,如果要进行实体 class 扫描,我们建议省略exclude-unlisted-classes
元素。
使用LocalContainerEntityManagerFactoryBean
是最强大的 JPA 设置选项,允许在 application 中灵活的本地配置。它支持指向现有 JDBC DataSource
的链接,支持 local 和 global transactions 等。但是,它还对运行时环境施加了要求,例如,如果持久性提供程序需要 byte-code 转换,则 weaving-capable class 加载程序的可用性。
此选项可能与 Java EE 服务器的 built-in JPA 功能冲突。在完整的 Java EE 环境中,请考虑从 JNDI 获取EntityManagerFactory
。或者,在LocalContainerEntityManagerFactoryBean
定义上指定自定义persistenceXmlLocation
(对于 example,META-INF/my-persistence.xml),并在 application jar files 中仅包含具有该 name 的描述符。由于 Java EE 服务器仅查找默认的META-INF/persistence.xml
files,因此它会忽略此类自定义持久性单元,因此可以避免与之前的 Spring-driven JPA 设置冲突。 (这适用于 Resin 3.1,适用于 example.)
什么时候需要 load-time 编织?
并非所有 JPA 提供程序都需要 JVM 代理。 Hibernate 是一个没有的示例。如果您的提供商不需要代理或您有其他选择,例如通过自定义编译器或 Ant 任务在 build time 应用增强功能,则不应使用 load-time weaver。
LoadTimeWeaver
接口是 Spring-provided class,它允许以特定方式插入 JPA ClassTransformer
实例,具体取决于环境是 web 容器还是 application 服务器。通过代理人挂钩ClassTransformers
通常效率不高。代理程序可以对整个虚拟机进行操作,并检查每个加载的 class,这在 production 服务器环境中通常是不受欢迎的。
Spring 为各种环境提供了许多LoadTimeWeaver
_implement,让ClassTransformer
实例仅应用于每个 class 加载器而不是每个 VM。
有关LoadTimeWeaver
__mplement 及其设置的更多信息,请参阅 AOP 章节中的Spring configuration,无论是通用的还是针对各种平台定制的(例如 Tomcat,WebLogic,GlassFish,Resin 和 JBoss)。
如Spring configuration中所述,您可以使用context:load-time-weaver
XML 元素的@EnableLoadTimeWeaving
annotation 配置 context-wide LoadTimeWeaver
。所有 JPA LocalContainerEntityManagerFactoryBean
实例都会自动选择这样的 global weaver。以下 example 显示了设置 load-time weaver 的首选方法,即提供 auto-detection 平台(WebLogic,GlassFish,Tomcat,Resin,JBoss 或 VM 代理)以及将 weaver 自动传播到所有 weaver-aware beans:
1 | <context:load-time-weaver/> |
但是,如果需要,您可以通过loadTimeWeaver
property 手动指定专用的 weaver,如下面的 example 所示:
1 | <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> |
无论如何配置 LTW,通过使用这种技术,依赖于检测的 JPA 应用程序可以在目标平台中运行(对于 example,Tomcat)而无需代理。当托管应用程序依赖于不同的 JPA implementations 时,这一点尤其重要,因为 JPA 变换器仅应用于 class-loader level,因此彼此隔离。
处理多个持久性单元
对于依赖于多个持久性单元位置的应用程序(存储在 classpath 中的各种 JARS 中,对于 example),Spring 提供PersistenceUnitManager
作为中央 repository 并避免持久性单元 discovery process,这可能很昂贵。默认 implementation 允许指定多个位置。解析这些位置,然后通过持久性单元 name 检索这些位置。 (默认情况下,搜索 classpath META-INF/persistence.xml
files.)以下 example 配置多个位置:
1 | <bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"> |
默认的 implementation 允许自定义PersistenceUnitInfo
实例(在它们被提供给 JPA 提供者之前),以声明方式(通过其 properties,它影响所有托管单元)或以编程方式(通过PersistenceUnitPostProcessor
,允许持久性单元选择)。如果未指定PersistenceUnitManager
,则会在LocalContainerEntityManagerFactoryBean
内部创建并使用一个PersistenceUnitManager
。
背景引导
LocalContainerEntityManagerFactoryBean
支持通过bootstrapExecutor
property 进行后台自举,如下面的 example 所示:
1 | <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> |
将实际的 JPA 提供程序引导传递给指定的执行程序,然后在 parallel 中运行到 application 引导程序线程。暴露的EntityManagerFactory
代理可以注入其他 application 组件,甚至能够响应EntityManagerFactoryInfo
configuration 检查。但是,一旦实际的 JPA 提供程序被其他组件访问(例如,调用createEntityManager
),那些 calls 会阻塞,直到后台引导完成。特别是,当您使用 Spring Data JPA 时,请确保为其 repositories 设置延迟引导。
基于JPA实现DAO:EntityManagerFactory和EntityManager
虽然
EntityManagerFactory
实例是 thread-safe,但EntityManager
实例不是。注入的 JPAEntityManager
的行为类似于从 application 服务器的 JNDI 环境中获取的EntityManager
,如 JPA 规范所定义。它将所有 calls 委托给当前 transactionalEntityManager
,如果有的话。否则,它会回退到每个操作新创建的EntityManager
,实际上使其使用 thread-safe。
通过使用注入的EntityManagerFactory
或EntityManager
,可以在没有任何 Spring 依赖项的情况下对普通 JPA 编写 code。如果启用PersistenceAnnotationBeanPostProcessor
,Spring 可以理解字段和方法 level 中的@PersistenceUnit
和@PersistenceContext
注释。以下 example 显示了一个使用@PersistenceUnit
annotation 的普通 JPA DAO implementation:
1 | public class ProductDaoImpl implements ProductDao { |
前面的 DAO 不依赖于 Spring,并且仍然可以很好地适应 Spring application context。此外,DAO 利用 annotations 要求注入默认的EntityManagerFactory
,如下面的 example bean 定义所示:
1 | <beans> |
作为显式定义PersistenceAnnotationBeanPostProcessor
的替代方法,请考虑在 application context configuration 中使用 Spring context:annotation-config
XML 元素。这样做会自动将所有 Spring 标准 post-processors 注册为 annotation-based configuration,包括CommonAnnotationBeanPostProcessor
等等。
考虑以下 example:
1 | <beans> |
这样一个 DAO 的主要问题是它总是通过工厂创建一个新的EntityManager
。您可以通过请求 transactional EntityManager
(也称为“共享 EntityManager”,因为它是实际 transactional EntityManager 的共享,thread-safe 代理)而不是工厂来避免这种情况。以下 example 显示了如何执行此操作:
1 | public class ProductDaoImpl implements ProductDao { |
@PersistenceContext
annotation 有一个名为type
的可选属性,默认为PersistenceContextType.TRANSACTION
。您可以使用此默认值来接收共享的EntityManager
代理。另一种选择PersistenceContextType.EXTENDED
是完全不同的事情。这导致 so-called 扩展EntityManager
,这不是 thread-safe,因此,不能在并发访问的 component 中使用,例如 Spring-managed singleton bean。扩展EntityManager
实例仅应用于有状态组件,例如,它们驻留在 session 中,EntityManager
的生命周期不与当前 transaction 绑定,而是完全取决于 application。
方法和 field-level 注射
您可以在 class 中的字段或方法上应用指示依赖注入(例如@PersistenceUnit
和@PersistenceContext
)的注释 - 因此表达式为“method-level injection”和“field-level injection”。 Field-level 注释简洁易用,而 method-level 注释允许进一步处理注入的依赖项。在这两种情况下,成员可见性(公共,受保护或私有)都无关紧要。
class-level 注释怎么样?
在 Java EE 平台上,它们用于依赖性声明,而不用于资源注入。
注入的EntityManager
是 Spring-managed(意识到正在进行的 transaction)。即使新的 DAO implementation 使用 method-level 注入EntityManager
而不是EntityManagerFactory
,但由于 annotation 用法,application context XML 不需要进行任何更改。
这种 DAO 风格的主要优点是它仅依赖于 Java Persistence API。不需要任何 Spring class 的 import。此外,当理解 JPA 注释时,Spring 容器会自动应用注入。从 non-invasiveness 的角度来看,这很有吸引力,对 JPA 开发人员来说感觉更自然。
Spring驱动的JPA事物
我们强烈建议您阅读声明式 transaction management(如果您还没有这样做),以便更详细地了解 Spring 的声明性 transaction 支持。
JPA 推荐的策略是通过 JPA 的本地 transaction 支持进行本地 transactions。 Spring 的JpaTransactionManager
提供了许多本地 JDBC transactions(例如 transaction-specific 隔离级别和 resource-level read-only 优化)对任何常规 JDBC 连接池(无 XA 要求)的功能。
Spring JPA 还允许配置JpaTransactionManager
将 JPA transaction 暴露给访问相同DataSource
的 JDBC 访问 code,前提是已注册的JpaDialect
支持检索底层 JDBC Connection
。 Spring 为 EclipseLink 和 Hibernate JPA implementations 提供方言。有关JpaDialect
机制的详细信息,请参阅下一节。
作为一个直接的选择,Spring 的本机
HibernateTransactionManager
能够与 JPA 访问 code 进行交互,从 Spring Framework 5.1 和 Hibernate 5.2/5.3 开始,适应几个 Hibernate 细节并提供 JDBC 交互。这与LocalSessionFactoryBean
设置相结合特别有意义。有关详细信息,请参阅JPA Interaction 的原生 Hibernate 设置。
了解JpaDialect和JpaVendorAdapter
作为高级 feature,JpaTransactionManager
和AbstractEntityManagerFactoryBean
的子类允许将自定义JpaDialect
传递到jpaDialect
bean property。 JpaDialect
implementation 可以启用 Spring 支持的以下高级 features,通常以 vendor-specific 方式:
- 应用特定的 transaction 语义(例如自定义隔离 level 或 transaction timeout)
- 检索 transactional JDBC
Connection
(用于暴露于 JDBC-based DAO) PersistenceExceptions
到 SpringDataAccessExceptions
的高级翻译
这对于特殊的 transaction 语义和 exception 的高级转换特别有用。默认 implementation(DefaultJpaDialect
)不提供任何特殊功能,如果需要前面列出的 features,则必须指定相应的方言。
作为一个更广泛的提供者适应设施,主要用于 Spring 的 full-featured
LocalContainerEntityManagerFactoryBean
设置,JpaVendorAdapter
将JpaDialect
的功能与其他 provider-specific 默认值相结合。指定HibernateJpaVendorAdapter
或EclipseLinkJpaVendorAdapter
分别是 Hibernate 或 EclipseLink 的 auto-configuringEntityManagerFactory
设置的最便捷方式。请注意,这些提供程序适配器主要设计用于 Spring-driven transaction management(即,与JpaTransactionManager
一起使用)。
有关其操作的更多详细信息以及如何在 Spring 的 JPA 支持中使用它们,请参阅JpaDialect和JpaVendorAdapter javadoc。
使用JTA事务管理设置JPA
作为JpaTransactionManager
的替代,Spring 还允许通过 JTA 进行 multi-resource transaction 协调,无论是在 Java EE 环境中还是与 stand-alone transaction 协调器(如 Atomikos)。除了选择 Spring 的JtaTransactionManager
而不是JpaTransactionManager
之外,您还需要采取进一步的措施:
- 底层 JDBC 连接池需要 XA-capable 并与 transaction 协调器集成。这在 Java EE 环境中通常很简单,通过 JNDI 公开不同类型的
DataSource
。有关详细信息,请参阅 application 服务器文档。类似地,独立的 transaction 协调器通常带有特殊的 XA-integratedDataSource
__mplementations。再次,检查其文档。 - 需要为 JTA 配置 JPA
EntityManagerFactory
设置。这是 provider-specific,通常通过特殊的 properties 在LocalContainerEntityManagerFactoryBean
上指定为jpaProperties
。在 Hibernate 的情况下,这些 properties 甚至是 version-specific。有关详细信息,请参阅 Hibernate 文档。 - Spring 的
HibernateJpaVendorAdapter
强制执行某些 Spring-oriented 默认值,例如连接释放模式on-close
,它与 Hibernate 在 Hibernate 5.0 中的默认值匹配,但在 5.1/5.2 中不再存在。对于 JTA 设置,要么不声明HibernateJpaVendorAdapter
开始或关闭其prepareConnection
flag。或者,将 Hibernate 5.2 的hibernate.connection.handling_mode
property 设置为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
以恢复 Hibernate 自己的默认值。有关 WebLogic 的相关说明,请参见带有 Hibernate 的虚假 Application Server 警告。 - 或者,考虑从 application 服务器本身获取
EntityManagerFactory
(即通过 JNDI 查找而不是本地声明的LocalContainerEntityManagerFactoryBean
)。 server-providedEntityManagerFactory
可能需要在服务器 configuration 中使用特殊定义(使部署不那么便携),但是为服务器的 JTA 环境设置了。
用于JPA交互的原生Hibernate设置和原生Hibernate事务
从 Spring Framework 5.1 和 Hibernate 5.2/5.3 开始,与HibernateTransactionManager
组合的原生LocalSessionFactoryBean
设置允许与@PersistenceContext
和其他 JPA 访问 code 进行交互。 Hibernate SessionFactory
本身实现了 JPA 的EntityManagerFactory
接口,而 Hibernate Session
句柄本身就是 JPA EntityManager
。 Spring 的 JPA 支持工具自动检测本机 Hibernate 会话。
因此,在许多场景中,这种本机 Hibernate 设置可以替代标准 JPA LocalContainerEntityManagerFactoryBean
和JpaTransactionManager
组合,允许在同一本地 transaction 内与@PersistenceContext EntityManager
(以及HibernateTemplate
)相邻进行交互。这样的设置还提供了更强的 Hibernate integration 和更多 configuration 灵活性,因为它不受 JPA bootstrap contracts 的限制。
在这种情况下,您不需要HibernateJpaVendorAdapter
configuration,因为 Spring 的本机 Hibernate 设置提供了更多 features(对于 example,自定义 Hibernate Integrator 设置,Hibernate 5.3 bean 容器 integration,以及 read-only transactions 的更强优化)。最后但并非最不重要的是,您还可以通过LocalSessionFactoryBuilder
表达本机 Hibernate 设置,与@Bean
style configuration 无缝集成(不涉及FactoryBean
)。
LocalSessionFactoryBean
和LocalSessionFactoryBuilder
支持后台自举,就像 JPALocalContainerEntityManagerFactoryBean
一样。有关介绍,请参阅背景引导。
在LocalSessionFactoryBean
上,可以通过bootstrapExecutor
property 获得。在程序LocalSessionFactoryBuilder
上,重载的buildSessionFactory
方法采用 bootstrap executor 参数。
使用Object-XML映射器编组XML
介绍
本章介绍 Spring 的 Object-XML Mapping 支持。 Object-XML Mapping(简称 O-X mapping)是将 XML 文档转换为 object 和从 object 转换 XML 文档的行为。此转换 process 也称为 XML 编组或 XML 序列化。本章可互换使用这些术语。
在 O-X mapping 的字段中,marshaller 负责将 object(图形)序列化为 XML。以类似的方式,unmarshaller 将 XML 反序列化为 object 图。此 XML 可以采用 DOM 文档,输入或输出流或 SAX 处理程序的形式。
使用 Spring 满足 O/X 映射需求的一些好处是:
易于配置
Spring 的 bean 工厂可以轻松配置 marshallers,而无需构建 JAXB context,JiBX binding 工厂等。你可以像 application context 中的任何其他 bean 一样配置 marshallers。此外,XML namespace-based configuration 可用于许多 marshallers,使 configuration 更简单。
一致的接口
Spring 的 O-X 映射通过两个 global 接口运行:编组和解组。这些抽象使您可以相对轻松地切换 O-X 映射框架,对执行编组的 classes 几乎不需要进行任何更改。这种方法的另一个好处是可以使用 mix-and-match 方法进行 XML 编组(例如,使用 JAXB 执行一些编组,一些使用 Castor 执行编组),让您使用每种技术的强度。
一致的Exception层次结构
Spring 提供从基础 O-X 映射工具的 exceptions 到其自己的 exception 层次结构的转换,其中XmlMappingException
作为根 exception。这些运行时 exceptions 包装原始 exception,以便不丢失任何信息。
Marshaller和Unmarshaller
如介绍中所述,marshaller 将 object 序列化为 XML,而 unmarshaller 将 XML 流反序列化为 object。本节介绍用于此目的的两个 Spring 接口。
了解Marshaller
Spring 抽象org.springframework.oxm.Marshaller
接口后面的所有编组操作,其主要方法如下:
1 | public interface Marshaller { |
Marshaller
接口有一个 main 方法,它将给定的 object 封送到给定的javax.xml.transform.Result
。结果是一个标记接口,它基本上代表 XML 输出抽象。具体的 implementations 包含各种 XML 表示,如下面的 table 表示:
结果实现 | 包装 XML 表示 |
---|---|
DOMResult |
org.w3c.dom.Node |
SAXResult |
org.xml.sax.ContentHandler |
StreamResult |
java.io.File ,java.io.OutputStream 或java.io.Writer |
尽管
marshal()
方法接受普通的 object 作为其第一个参数,但大多数Marshaller
implementations 都无法处理任意的 objects。相反,object class 必须映射到映射文件中,标记为 annotation,向 marshaller 注册,或者具有 common base class。请参阅本章后面的部分,以确定 O-X 技术如何管理它。
了解Unmarshaller
与Marshaller
类似,我们有org.springframework.oxm.Unmarshaller
接口,如下所示:
1 | public interface Unmarshaller { |
该接口还有一个方法,它从给定的javax.xml.transform.Source
(XML 输入抽象)读取并返回 object 读取。与Result
一样,Source
是一个标记接口,具有三个具体的 implementation。每个包装一个不同的 XML 表示,如下面的 table 表示:
来源实现 | 包装 XML 表示 |
---|---|
DOMSource |
org.w3c.dom.Node |
SAXSource |
org.xml.sax.InputSource ,org.xml.sax.XMLReader |
StreamSource |
java.io.File ,java.io.InputStream 或java.io.Reader |
即使有两个单独的编组接口(Marshaller
和Unmarshaller
),Spring-WS 中的所有_implement 都在一个 class 中实现。这意味着您可以连接一个 marshaller class 并将其作为 marshaller 和applicationContext.xml
中的 unmarshaller 引用。
了解XmlMappingException
Spring 将 exceptions 从底层的 O-X 映射工具转换为自己的 exception 层次结构,并将XmlMappingException
作为根 exception。这些运行时 exceptions 包装原始 exception,以便不会丢失任何信息。
此外,MarshallingFailureException
和UnmarshallingFailureException
提供了编组和解组操作之间的区别,即使底层的 O-X 映射工具不这样做。
O-X Mapping exception 层次结构如下图所示:
使用Marshaller和Unmarshaller
您可以在各种情况下使用 Spring 的 OXM。在下面的示例中,我们使用它来将 Spring-managed application 的设置编组为 XML 文件。在下面的示例中,我们使用一个简单的 JavaBean 来表示设置:
1 | public class Settings { |
application class 使用此 bean 来存储其设置。除了 main 方法之外,class 还有两个方法:saveSettings()
将设置 bean 保存到名为settings.xml
的文件中,loadSettings()
再次加载这些设置。以下main()
方法构造 Spring application context 并_call 这两个方法:
1 | import java.io.FileInputStream; |
Application
需要设置marshaller
和unmarshaller
property。我们可以使用以下applicationContext.xml
来实现:
1 | <beans> |
此 application context 使用 Castor,但我们可以使用本章后面介绍的任何其他 marshaller 实例。请注意,默认情况下,Castor 不需要任何进一步的 configuration,因此 bean 定义相当简单。另请注意,CastorMarshaller
同时实现了Marshaller
和Unmarshaller
,因此我们可以在 application 的marshaller
和unmarshaller
property 中引用castorMarshaller
bean。
此 sample application 生成以下settings.xml
文件:
1 |
|
XML配置命名空间
您可以使用 OXM 命名空间中的标记更简洁地配置 marshallers。要使这些标记可用,必须首先在 XML configuration 文件的前导码中引用相应的 schema。以下 example 显示了如何执行此操作:
1 |
|
1 | 参考oxm schema。 |
2 | 指定oxm schema 位置。 |
目前,schema 提供以下元素:
每个标签都在其各自的编组部分中进行了解释。但是,作为一个示例,JAXB2 marshaller 的 configuration 可能类似于以下内容:
1 | <oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/> |
JAXB
JAXB binding 编译器将 W3C XML Schema 转换为一个或多个 Java classes,一个jaxb.properties
文件,可能还有一些资源 files。 JAXB 还提供了一种从带注释的 Java classes 生成 schema 的方法。
Spring 支持 JAXB 2.0 API 作为 XML 编组策略,遵循Marshaller 和 Unmarshaller中描述的Marshaller
和Unmarshaller
接口。相应的 integration classes 位于org.springframework.oxm.jaxb
包中。
使用Jaxb2Marshaller
Jaxb2Marshaller
class 实现了 Spring 的Marshaller
和Unmarshaller
接口。它需要 context 路径才能运行。您可以通过设置contextPath
property 来设置 context 路径。 context 路径是包含 schema 派生 classes 的 colon-separated Java 包名称列表。它还提供classesToBeBound
property,允许您设置 marshaller 支持的_arlass 的 array。 Schema 验证是通过为 bean 指定一个或多个 schema 资源来执行的,如下面的 example 所示:
1 | <beans> |
XML配置命名空间
jaxb2-marshaller
元素配置org.springframework.oxm.jaxb.Jaxb2Marshaller
,如下面的 example 所示:
1 | <oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/> |
或者,您可以使用class-to-be-bound
child 元素提供要绑定到编组程序的 classes 列表:
1 | <oxm:jaxb2-marshaller id="marshaller"> |
以下 table 描述了可用的属性:
属性 | 描述 | 需要 |
---|---|---|
id |
编组员的身份证 | 没有 |
contextPath |
JAXB Context 路径 | 没有 |
JiBX
JiBX framework 提供了类似于 Hibernate 为 ORM 提供的解决方案:binding 定义定义了 Java objects 如何转换为 XML 或从 XML 转换的规则。在准备 binding 并编译 classes 之后,JiBX binding 编译器增强 class files 并添加 code 以处理从或 XML 转换 classes 的实例。
有关 JiBX 的更多信息,请参阅JiBX web 网站。 Spring integration classes 位于org.springframework.oxm.jibx
包中。
使用JibxMarshaller
JibxMarshaller
class 实现Marshaller
和Unmarshaller
接口。要进行操作,需要 class 的 name 编组,您可以使用targetClass
property 进行设置。 (可选)您可以通过设置bindingName
property 来设置 binding name。在下面的示例中,我们绑定Flights
class:
1 | <beans> |
配置为单个 class。如果要编组多个 classes,则必须使用不同的targetClass
property 值配置多个JibxMarshaller
实例。
XML配置命名空间
jibx-marshaller
标记配置org.springframework.oxm.jibx.JibxMarshaller
,如下面的 example 所示:
1 | <oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/> |
以下 table 描述了可用的属性:
属性 | 描述 | 需要 |
---|---|---|
id |
编组员的身份证 | 没有 |
target-class |
此 marshaller 的目标 class | 是 |
bindingName |
这个编组使用的 binding name | 没有 |
XStream
XStream 是一个简单的 library,可以将 objects 序列化为 XML,然后再返回。它不需要任何映射并生成干净的 XML。
有关 XStream 的更多信息,请参阅XStream web 网站。 Spring integration classes 位于org.springframework.oxm.xstream
包中。
使用XStreamMarshaller
XStreamMarshaller
不需要任何 configuration,可以直接在 application context 中配置。要进一步自定义 XML,可以设置别名 map,它由映射到 classes 的 string 别名组成,如下面的 example 所示:
1 | <beans> |
默认情况下,XStream 允许任意 classes 被解组,这可能导致不安全的 Java 序列化效果。因此,我们不建议使用
XStreamMarshaller
从外部源(即 Web)解组 XML,因为这可能导致安全漏洞。
如果您选择使用XStreamMarshaller
从外部源解组 XML,请在XStreamMarshaller
上设置supportedClasses
property,如下面的 example 所示:
1 | <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"> |
这样做可确保只有已注册的 classes 才有资格进行解组。
此外,您可以注册定制转换器)以确保只有您支持的 classes 可以被解组。除了显式支持应支持的域 classes 的转换器之外,您可能还希望在列表中添加CatchAllConverter
作为最后一个转换器。因此,不会调用具有较低优先级和可能的安全漏洞的默认 XStream 转换器。
请注意,XStream 是 XML 序列化 library,而不是数据 binding library。因此,它具有有限的命名空间支持。因此,它不适合在 Web services 中使用。
附录
XML模式
附录的这一部分列出了用于数据访问的 XML 模式,包括以下内容:
txSchema
tx
标签处理 Spring 对 transactions 的全面支持中所有 beans 的配置。这些标签在标题为Transaction Management的章节中介绍。
我们强烈建议您查看 Spring 发行版附带的
'spring-tx.xsd'
文件。此文件包含 Spring 的 transaction configuration 的 XML Schema,并涵盖tx
命名空间中的所有各种元素,包括属性默认值和类似信息。该文件以内联方式记录,因此,为了遵守 DRY(不要重复自己)原则,此处不再重复这些信息。
为了完整性,要使用tx
schema 中的元素,您需要在 Spring XML configuration 文件的顶部包含以下前导码。以下代码段中的文本引用了正确的 schema,以便tx
命名空间中的标记可供您使用:
1 |
|
1 | 声明tx 名称空间的使用。 |
2 | 指定位置(使用其他 schema 位置)。 |
通常,当您使用
tx
命名空间中的元素时,您还使用aop
命名空间中的元素(因为 Spring 中的声明性 transaction 支持是通过使用 AOP 实现的)。前面的 XML 片段包含schema 引用所需的相关 lines,以便aop
命名空间中的元素可供您使用。
jdbcSchema
jdbc
元素使您可以快速配置嵌入式数据库或初始化现有数据源。这些元素分别记录在嵌入式数据库支持和初始化 DataSource中。
要使用jdbc
schema 中的元素,您需要在 Spring XML configuration 文件的顶部添加以下前导码。以下代码段中的文本引用了正确的 schema,以便jdbc
命名空间中的元素可供您使用:
1 |
|
1 | 声明jdbc 名称空间的使用。 |
2 | 指定位置(使用其他 schema 位置)。 |