0%

SpringFramework官方文档翻译-数据访问

数据访问

事务管理

对于事务的全面支持是使用 Spring Framework 的最主要原因之一。Spring Framework 为事务管理提供了统一的抽象,这带来了如下好处:

  • 跨越不同 transaction API 的一致编程模型,例如 Java Transaction API(JTA),JDBC,Hibernate 和 Java Persistence API(JPA)。
  • 声明式事务管理的支持。
  • 一种相比于复杂事务API(例如JTA)要更简单的编程式事务管理。
  • 与 Spring 的数据访问抽象的完美整合。

以下部分描述了 Spring Framework 的事务特性和术语:

(该章还包括对最佳实践的讨论,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 TransactionsJOTM)是其他选项。当然,您可能需要其他 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
2
3
4
5
6
7
8
public interface PlatformTransactionManager {

TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
}

尽管可以在应用代码中以编程方式使用它,但它主要是服务提供者接口(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface TransactionStatus extends SavepointManager {

boolean isNewTransaction();

boolean hasSavepoint();

void setRollbackOnly();

boolean isRollbackOnly();

void flush();

boolean isCompleted();

}

无论是选择声明式还是编程式事务管理,定义正确的PlatformTransactionManager都是绝对必要的。通常通过依赖注入定义其实现。

PlatformTransactionManager implementations 通常需要了解它们工作的环境:JDBC,JTA,Hibernate 等。以下示例显示如何定义本地PlatformTransactionManager implementation(在本例中,使用 plain JDBC.)

您可以通过 creating bean 来定义 JDBC DataSource,类似于以下内容:

1
2
3
4
5
6
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

然后,相关的PlatformTransactionManager bean 定义具有DataSource定义的 reference。它应该类似于以下 example:

1
2
3
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

如果在 Java EE 容器中使用 JTA,则使用通过 JNDI 获得的容器DataSource和 Spring 的JtaTransactionManager。以下 example 显示了 JTA 和 JNDI 查找 version 的外观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd">

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

<!-- other <bean/> definitions here -->

</beans>

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 声明sessionFactorytxManager beans:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>

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

如果使用 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 DataSourceHibernateTransactionManager到 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 JdbcTemplatejdbc.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 代理上调用方法的概念视图:

TX

声明式事务实现示例

考虑以下接口及其伴随 implementation。此 example 使用FooBar classes 作为占位符,以便您可以专注于 transaction 用法,而无需关注特定的域 model。出于本示例的目的,DefaultFooService class 在每个实现的方法的主体中抛出UnsupportedOperationException实例的事实是好的。通过该行为,您可以看到 transactions 被创建,然后回滚以响应UnsupportedOperationException实例。以下清单显示了FooService接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// the service interface that we want to make transactional

package x.y.service;

public interface FooService {

Foo getFoo(String fooName);

Foo getFoo(String fooName, String barName);

void insertFoo(Foo foo);

void updateFoo(Foo foo);

}

以下 example 显示了前面接口的 implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package x.y.service;

public class DefaultFooService implements FooService {

public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}

public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}

public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}

public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}

}

假设FooService接口的前两个方法getFoo(String)getFoo(String, String)必须在具有 read-only 语义的 transaction 的 context 中执行,并且其他方法insertFoo(Foo)updateFoo(Foo)必须在具有 read-write 语义的 transaction 的 context 中执行。以下 configuration 将在接下来的几段中详细解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>

<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>

<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- other <bean/> definitions here -->

</beans>

检查前面的 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 具有 name transactionManager,则可以省略 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
2
3
4
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>

在前面的示例中,假设所有服务接口都在x.y.service包中定义。有关详细信息,请参阅AOP 部分

现在我们已经分析了 configuration,你可能会问自己,“所有这些 configuration 实际上做了什么?”

前面显示的 configuration 用于围绕从fooService bean 定义创建的 object 创建 transactional 代理。代理配置了 transactional 通知,以便在代理上调用适当的方法时,transaction 被启动,挂起,标记为 read-only,依此类推,具体取决于与该方法关联的 transaction configuration。考虑以下测试驱动前面显示的 configuration 的程序:

1
2
3
4
5
6
7
8
public final class Boot {

public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
FooService fooService = (FooService) ctx.getBean("fooService");
fooService.insertFoo (new Foo());
}
}

running 前面程序的输出应类似于以下内容(Log4J 输出和 DefaultFooService class 的 insertFoo(..)方法抛出的 UnsupportedOperationException 中的堆栈跟踪已被截断,以便清楚):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [[emailprotected]] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [[emailprotected]]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

回滚声明式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
2
3
4
5
6
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

如果您不希望在抛出 exception 时回滚 transaction,您还可以指定“no rollback rules”。以下 example 告诉 Spring Framework 的 transaction 基础设施提交服务员 transaction,即使面对未处理的InstrumentNotFoundException

1
2
3
4
5
6
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

当 Spring Framework 的 transaction 基础结构捕获 exception 并且它查询配置的回滚规则以确定是否标记 transaction 进行回滚时,最强匹配规则获胜。因此,在以下 configuration 的情况下,除了InstrumentNotFoundException之外的任何 exception 都会导致响尾符 transaction 的回滚:

1
2
3
4
5
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>

您还可以以编程方式指示所需的回滚。虽然很简单,但这个 process 非常具有侵入性,并且会将 code 紧密地耦合到 Spring Framework 的 transaction 基础架构。以下 example 显示了如何以编程方式指示所需的回滚:

1
2
3
4
5
6
7
8
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}

如果可能的话,强烈建议您使用声明性方法进行回滚。如果您绝对需要程序化回滚,则可以使用程序化回滚,但它的使用方式可以实现干净的 POJO-based architecture。

为不同的Beans配置不同的Transactional语义

考虑具有多个服务层 objects 的场景,并且您希望对它们中的每一个应用完全不同的 transactional configuration。您可以通过定义具有不同pointcutadvice-ref属性值的不同<aop:advisor/>元素来实现。

作为比较,首先假设您的所有服务层 classes 都在根x.y.service包中定义。要使所有 beans 成为在该包(或子包中)中定义的 classes 实例并且名称以Service结尾,并且具有默认的 transactional configuration,您可以编写以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<aop:config>

<aop:pointcut id="serviceOperation"
expression="execution(* x.y.service..*Service.*(..))"/>

<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

</aop:config>

<!-- these two beans will be transactional... -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>

<!-- ... and these two beans won't -->
<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->

<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

以下 example 显示了如何使用完全不同的 transactional 设置配置两个不同的 beans:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<aop:config>

<aop:pointcut id="defaultServiceOperation"
expression="execution(* x.y.service.*Service.*(..))"/>

<aop:pointcut id="noTxServiceOperation"
expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

</aop:config>

<!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- this bean will also be transactional, but with totally different transactional settings -->
<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

<tx:advice id="defaultTxAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<tx:advice id="noTxAdvice">
<tx:attributes>
<tx:method name="*" propagation="NEVER"/>
</tx:attributes>
</tx:advice>

<!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

设置

本节总结了您可以使用<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,`gethandleonEvent`等)。
propagation 没有 REQUIRED Transaction 传播行为。
isolation 没有 DEFAULT Transaction isolation level。仅适用于REQUIREDREQUIRES_NEW的传播设置。
timeout 没有 -1 Transaction timeout(秒)。仅适用于传播REQUIREDREQUIRES_NEW
read-only 没有 Read-write 与 read-only transaction。仅适用于REQUIREDREQUIRES_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
2
3
4
5
6
7
8
9
10
11
12
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

Foo getFoo(String fooName);

Foo getFoo(String fooName, String barName);

void insertFoo(Foo foo);

void updateFoo(Foo foo);
}

如上所述在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required --> (1)

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- other <bean/> definitions here -->

</beans>
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-classfalse或者省略了该属性,则会创建标准 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-classfalse或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理的讨论,请参阅aop-proxying types.)

@EnableTransactionManagement<tx:annotation-driven/>仅在定义它们的同一 application context 中的 beans 上查找@Transactional。这意味着,如果您将 annotation-driven configuration 放在WebApplicationContextDispatcherServlet,它只会在您的控制器而不是您的服务中检查@Transactional beans。有关更多信息,请参见MVC

在评估方法的 transactional 设置时,派生的位置优先。在以下 example 的情况下,DefaultFooService class 在 class level 中使用 read-only transaction 的设置进行注释,但同一 class 中updateFoo(Foo)方法上的@Transactional annotation 优先于 class level 中定义的 transactional 设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

public Foo getFoo(String fooName) {
// do something
}

// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
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。仅适用于REQUIREDREQUIRES_NEW的传播值。
timeout int(以粒度为单位) 可选的 transaction 超时。仅适用于REQUIREDREQUIRES_NEW的传播值。
readOnly boolean Read-write 与 read-only transaction。仅适用于REQUIREDREQUIRES_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
2
3
4
5
6
7
8
public class TransactionalService {

@Transactional("order")
public void setSomething(String name) { ... }

@Transactional("account")
public void doSomething() { ... }
}

以下清单显示了 bean 声明:

1
2
3
4
5
6
7
8
9
10
11
<tx:annotation-driven/>

<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>

<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>

在这种情况下,TransactionalService run 上的两个方法在单独的 transaction managers 下,由orderaccount限定符区分。如果未找到特定限定的PlatformTransactionManager bean,则仍使用默认的<tx:annotation-driven>目标 bean name,transactionManager

自定义复合注解

如果您发现在许多不同方法上重复使用与@Transactional相同的属性,则Spring 的 meta-annotation 支持允许您为特定用例定义自定义快捷方式注释。对于 example,请考虑以下 annotation 定义:

1
2
3
4
5
6
7
8
9
10
11
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

前面的注释允许我们编写上一节中的 example,如下所示:

1
2
3
4
5
6
7
8
public class TransactionalService {

@OrderTx
public void setSomething(String name) { ... }

@AccountTx
public void doSomething() { ... }
}

在前面的示例中,我们使用语法来定义 transaction manager 限定符,但我们也可以包含传播行为,回滚规则,超时和其他 features。

Transaction传播

本节描述了 Spring 中 transaction 传播的一些语义。请注意,本节不是 transaction 传播的简介。相反,它详细介绍了 Spring 中有关 transaction 传播的一些语义。

在 Spring-managed transactions 中,请注意物理和逻辑 transactions 之间的区别,以及传播设置如何应用于此差异。

了解PROPAGATION_REQUIRED

需要 tx 道具

如果还没有 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

tx prop 需要新

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

private int order;

// allows us to control the ordering of advice
public int getOrder() {
return this.order;
}

public void setOrder(int order) {
this.order = order;
}

// this method is the around advice
public Object profile(ProceedingJoinPoint call) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(call.toShortString());
returnValue = call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
return returnValue;
}
}

建议的 ordering 通过Ordered接口控制。有关建议 ordering 的详细信息,请参阅建议 ordering

以下 configuration 创建一个fooService bean,在所需的 order 中应用了 profiling 和 transactional 方面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- this is the aspect -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- execute before the transactional advice (hence the lower order number) -->
<property name="order" value="1"/>
</bean>

<tx:annotation-driven transaction-manager="txManager" order="200"/>

<aop:config>
<!-- this advice will execute around the transactional advice -->
<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>
</aop:config>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

</beans>

您可以以类似的方式配置任意数量的其他方面。

以下 example 创建与前两个示例相同的设置,但使用纯 XML 声明方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="fooService" class="x.y.service.DefaultFooService"/>

<!-- the profiling advice -->
<bean id="profiler" class="x.y.SimpleProfiler">
<!-- execute before the transactional advice (hence the lower order number) -->
<property name="order" value="1"/>
</bean>

<aop:config>
<aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
<!-- will execute after the profiling advice (c.f. the order attribute) -->

<aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
<!-- order value is higher than the profiling aspect -->

<aop:aspect id="profilingAspect" ref="profiler">
<aop:pointcut id="serviceMethodWithReturnValue"
expression="execution(!void x.y..*Service.*(..))"/>
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
</aop:aspect>

</aop:config>

<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

前面的 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,我们将向您展示如何以编程方式执行此操作。

在继续之前,您可能需要分别阅读使用 @TransactionalAOP

以下 example 显示了如何创建 transaction manager 并配置AnnotationTransactionAspect以使用它:

1
2
3
4
5
// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

使用此 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleService implements Service {

// single TransactionTemplate shared amongst all methods in this instance
private final TransactionTemplate transactionTemplate;

// use constructor-injection to supply the PlatformTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}

public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// the code in this method executes in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}

如果没有 return value,则可以使用带有匿名 class 的方便的TransactionCallbackWithoutResult class,如下所示:

1
2
3
4
5
6
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});

回调中的 Code 可以通过调用提供的TransactionStatus object 上的setRollbackOnly()方法来回滚 transaction,如下所示:

1
2
3
4
5
6
7
8
9
10
11
transactionTemplate.execute(new TransactionCallbackWithoutResult() {

protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
指定Transaction设置

您可以在TransactionTemplate上以编程方式或在 configuration 中指定 transaction 设置(例如传播模式,隔离 level,超时等)。默认情况下,TransactionTemplate实例具有默认 transactional 设置。以下 example 显示了特定TransactionTemplate:的 transactional 设置的编程自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SimpleService implements Service {

private final TransactionTemplate transactionTemplate;

public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);

// the transaction settings can be set here explicitly if so desired
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); // 30 seconds
// and so forth...
}
}

以下 example 通过使用 Spring XML configuration 定义带有一些自定义 transactional 设置的TransactionTemplate

1
2
3
4
5
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>"

然后,您可以_将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。然后,通过使用TransactionDefinitionTransactionStatus objects,您可以启动 transactions,回滚和提交。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);

在编程式和声明式之间选择事务管理

只有少数 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
2
3
4
5
6
7
8
@Component
public class MyComponent {

@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}

@TransactionalEventListener annotation 公开了一个phase属性,该属性允许您自定义 listener 应绑定到的 transaction 的阶段。有效阶段是BEFORE_COMMITAFTER_COMMIT(默认),AFTER_ROLLBACKAFTER_COMPLETION,它们聚合 transaction 完成(无论是提交还是回滚)。

如果没有 transaction running,则根本不会调用 listener,因为我们无法遵守所需的语义。但是,您可以通过将 annotation 的fallbackExecution属性设置为true来覆盖该行为。

特定于应用服务器的集成

Spring 的 transaction 抽象通常是 application server-agnostic。此外,Spring 的JtaTransactionManager class(可以选择对 JTA UserTransactionTransactionManagerobjects 执行 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 必须关心处理HibernateExceptionsPersistenceExceptions本身,最好分别委托给SessionFactoryUtilsconvertHibernateAccessException(..)convertJpaAccessException()方法。这些方法将 exceptions 转换为与org.springframework.dao exception 层次结构中的 exceptions 兼容的 exceptions。由于PersistenceExceptions未经检查,它们也可能被抛出(尽管在 exceptions 方面牺牲了通用的 DAO 抽象)。

下图显示了 Spring 提供的 exception 层次结构。 (请注意,图像中详细说明的 class 层次结构仅显示整个DataAccessException hierarchy.)的子集

DataAccessException

Annotations用于配置DAO或Repository类

保证数据访问 Objects(DAO)或 repositories 提供 exception 转换的最佳方法是使用@Repository annotation。此 annotation 还允许 component 扫描支持查找和配置您的 DAO 和 repositories,而无需为它们提供 XML configuration 条目。以下 example 显示了如何使用@Repository annotation:

1
2
3
4
@Repository (1)
public class SomeMovieFinder implements MovieFinder {
// ...
}
1 @Repository annotation。

任何 DAO 或 repository implementation 都需要访问持久性资源,具体取决于所使用的持久性技术。例如,JDBC-based repository 需要访问 JDBC DataSource,而 JPA-based repository 需要访问EntityManager。完成此操作的最简单方法是使用@Autowired@Inject@Resource@PersistenceContext 注释之一注入此资源依赖项。以下 example 适用于 JPA repository:

1
2
3
4
5
6
7
8
9
@Repository
public class JpaMovieFinder implements MovieFinder {

@PersistenceContext
private EntityManager entityManager;

// ...

}

如果使用经典的 Hibernate API,则可以 inject SessionFactory,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Repository
public class HibernateMovieFinder implements MovieFinder {

private SessionFactory sessionFactory;

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

// ...

}

我们在这里展示的最后一个例子是典型的 JDBC 支持。您可以将DataSource注入初始化方法,您可以使用此DataSource创建JdbcTemplate和其他数据访问支持 classes(例如SimpleJdbcCall和其他)。以下 example 自动装配DataSource

1
2
3
4
5
6
7
8
9
10
11
12
13
@Repository
public class JdbcMovieFinder implements MovieFinder {

private JdbcTemplate jdbcTemplate;

@Autowired
public void init(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

// ...

}

有关如何配置 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之外,新的SimpleJdbcInsertSimpleJdbcCall方法优化了数据库元数据,而 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 语句参数时,此方法可提供更好的文档和易用性。
  • SimpleJdbcInsertSimpleJdbcCall优化数据库元数据以限制必要的 configuration 数量。此方法简化了编码,因此您只需要提供 table 或过程的 name,并提供与列名匹配的参数的 map。仅当数据库提供足够的元数据时,这才有效。如果数据库未提供此元数据,则必须提供参数的显式 configuration。
  • RDBMS objects,包括MappingSqlQuerySqlUpdateStoredProcedure,要求您在 data-access 层初始化期间创建可重用和 thread-safe objects。此方法以 JDO Query 为模型,其中您定义查询 string,声明参数和编译查询。一旦这样做,就可以使用各种参数值多次调用 execute 方法。

包层次结构

Spring Framework 的 JDBC 抽象 framework 由四个不同的包组成:

  • coreorg.springframework.jdbc.core包包含JdbcTemplate class 及其各种回调接口,以及各种相关的 classes。名为org.springframework.jdbc.core.simple的子包包含SimpleJdbcInsertSimpleJdbcCall classes。另一个名为org.springframework.jdbc.core.namedparam的子包包含NamedParameterJdbcTemplate class 和相关的支持 classes。请参见使用 JDBC Core Classes 控制基本 JDBC 处理和错误处理JDBC 批处理操作使用 SimpleJdbc Classes 简化 JDBC 操作
  • datasourceorg.springframework.jdbc.datasource包中包含一个用于轻松DataSource访问的实用程序 class 和各种简单的DataSource __mplementations,可用于测试和运行 Java EE 容器外的未修改的 JDBC code。名为org.springfamework.jdbc.datasource.embedded的子包通过使用 Java 数据库引擎(如 HSQL,H2 和 Derby)为创建嵌入式数据库提供支持。见控制数据库连接嵌入式数据库支持

objectorg.springframework.jdbc.object包中包含 classes,它们将 RDBMS 查询,更新和存储过程表示为 thread-safe,可重用的 objects。见将 JDBC 操作建模为 Java Objects。这种方法由 JDO 建模,尽管查询返回的 objects 自然地与数据库断开连接。这个 higher-level 的 JDBC 抽象取决于org.springframework.jdbc.core包中的 lower-level 抽象。

supportorg.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

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 提供的ConnectionPreparedStatementCreator回调接口创建一个预准备语句,提供 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
2
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");

以下查询查找String

1
2
3
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
new Object[]{1212L}, String.class);

以下查询查找并填充单个域 object:

1
2
3
4
5
6
7
8
9
10
11
Actor actor = this.jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
new Object[]{1212L},
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});

以下查询查找并填充了许多域 objects:

1
2
3
4
5
6
7
8
9
10
List<Actor> actors = this.jdbcTemplate.query(
"select first_name, last_name from t_actor",
new RowMapper<Actor>() {
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
});

如果 code 的最后两个片段实际存在于同一个 application 中,那么删除两个RowMapper匿名内部 classes 中存在的重复并将它们提取到一个 class(通常是一个static嵌套的 class)然后可以被引用是有意义的。根据需要通过 DAO 方法。例如,最好按如下方式编写前面的 code 代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
public List<Actor> findAllActors() {
return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}

private static final class ActorMapper implements RowMapper<Actor> {

public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}
使用JdbcTemplate更新(INSERT,UPDATE 和 DELETE)

您可以使用update(..)方法执行 insert,update 和 delete 操作。参数值通常作为变量 arguments 提供,或者作为 object array 提供。

以下 example 插入一个新条目:

1
2
3
this.jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");

以下 example 更新现有条目:

1
2
3
this.jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);

以下 example 删除条目:

1
2
3
this.jdbcTemplate.update(
"delete from actor where id = ?",
Long.valueOf(actorId));
其他JdbcTemplate操作

您可以使用execute(..)方法来运行任意 SQL。因此,该方法通常用于 DDL 语句。它过多地采用了带回调接口,binding 变量数组等的变体。以下 example 创建一个 table:

1
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");

以下 example 调用存储过程:

1
2
3
this.jdbcTemplate.update(
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
Long.valueOf(unionId));

更复杂的存储过程支持是稍后报道

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
2
3
4
5
6
7
8
9
10
public class JdbcCorporateEventDao implements CorporateEventDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

以下 example 显示了相应的 XML configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

</beans>

显式 configuration 的替代方法是使用 component-scanning 和 annotation 支持依赖注入。在这种情况下,您可以使用@Repository(使其成为 component-scanning 的候选者)注释 class,并使用@Autowired注释DataSource setter 方法。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {

private JdbcTemplate jdbcTemplate;

@Autowired (2)
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource); (3)
}

// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 使用@Repository注释 class。
2 使用@Autowired注释DataSource setter 方法。
3 使用DataSource创建一个新的JdbcTemplate

以下 example 显示了相应的 XML configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- Scans within the base package of the application for @Component classes to configure as beans -->
<context:component-scan base-package="org.springframework.docs.test" />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

</beans>

如果你使用 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

String sql = "select count(*) from T_ACTOR where first_name = :first_name";

SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);

return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

注意在分配给sql变量的 value 中使用命名参数表示法,以及插入namedParameters变量(类型MapSqlParameterSource)的相应 value。

或者,您可以使用由NamedParameterJdbcOperations公开的Map -based style.The 剩余方法将命名参数及其相应值传递给NamedParameterJdbcTemplate实例,并由NamedParameterJdbcTemplate class 实现,遵循类似的 pattern,此处不予介绍。

以下 example 显示了Map -based 样式的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

String sql = "select count(*) from T_ACTOR where first_name = :first_name";

Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);

return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

NamedParameterJdbcTemplate相关的一个很好的 feature(并且存在于同一个 Java 包中)是SqlParameterSource接口。您已经在之前的一个 code 片段(MapSqlParameterSource class)中看到了此接口的实例实例。 SqlParameterSourceNamedParameterJdbcTemplate的命名参数值的来源。 MapSqlParameterSource class 是一个简单的 implementation,是一个围绕java.util.Map的适配器,其中键是参数名称,值是参数值。

另一个SqlParameterSource implementation 是BeanPropertySqlParameterSource class。此 class 包装任意 JavaBean(即,遵循JavaBean 约定的 class 实例),并使用包装的 JavaBean 的 properties 作为命名参数值的来源。

以下 example 显示了一个典型的 JavaBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Actor {

private Long id;
private String firstName;
private String lastName;

public String getFirstName() {
return this.firstName;
}

public String getLastName() {
return this.lastName;
}

public Long getId() {
return this.id;
}

// setters omitted...

}

以下 example 使用NamedParameterJdbcTemplate来_return 前面 example 中显示的 class 成员的计数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActors(Actor exampleActor) {

// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";

SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);

return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.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
2
3
4
5
6
7
8
9
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {

protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
if (sqlex.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, sqlex);
}
return null;
}
}

在前面的 example 中,特定错误 code(-12345)被翻译,而其他错误则由默认翻译器实现翻译。要使用此自定义转换程序,必须通过方法setExceptionTranslator将其传递给JdbcTemplate,并且必须将此JdbcTemplate用于需要此转换程序的所有数据访问处理。以下 example 显示了如何使用此自定义转换器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {

// create a JdbcTemplate and set data source
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);

// create a custom translator and set the DataSource for the default translation lookup
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
tr.setDataSource(dataSource);
this.jdbcTemplate.setExceptionTranslator(tr);

}

public void updateShippingCharge(long orderId, long pct) {
// use the prepared JdbcTemplate for this update
this.jdbcTemplate.update("update orders" +
" set shipping_charge = shipping_charge * ? / 100" +
" where id = ?", pct, orderId);
}

自定义转换器在 order 中传递数据源以查找sql-error-codes.xml中的错误代码。

执行Statements

运行 SQL 语句只需要很少的 code。您需要DataSourceJdbcTemplate,包括随JdbcTemplate提供的便捷方法。以下 example 显示了为创建新 table 的最小但功能齐全的 class 所需要包含的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAStatement {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public void doExecute() {
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
}

执行Queries

一些查询方法 return 一个 value。要从一行检索计数或特定 value,请使用queryForObject(..)。后者将返回的 JDBC Type转换为作为参数传入的 Java class。如果类型转换无效,则抛出InvalidDataAccessApiUsageException。以下 example 包含两个查询方法,一个用于int,另一个用于查询String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class RunAQuery {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int getCount() {
return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
}

public String getName() {
return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
}
}

除了单个结果查询方法之外,还有几个方法_返回一个列表,其中包含查询返回的每一行的条目。最通用的方法是queryForList(..),它返回List,其中每个元素都是Map,每列包含一个条目,使用 name 作为 key。如果向前面的 example 添加方法以检索所有行的列表,则可能如下所示:

1
2
3
4
5
6
7
8
9
private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public List<Map<String, Object>> getList() {
return this.jdbcTemplate.queryForList("select * from mytable");
}

返回的列表将类似于以下内容:

1
[{name=Bob, id=1}, {name=Mary, id=2}]

更新数据库

以下 example 更新某个主 key 的列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAnUpdate {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public void setName(int id, String name) {
this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
}
}

在前面的示例中,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] {"id"});
ps.setString(1, name);
return ps;
}
},
keyHolder);

// keyHolder.getKey() now contains the generated key

控制数据库连接

本节包括:

使用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
2
3
4
5
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");

以下 example 显示了相应的 XML configuration:

1
2
3
4
5
6
7
8
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

接下来的两个示例显示了 DBCP 和 C3P0 的基本连接和 configuration。要了解有助于控制池 features 的更多选项,请参阅相应连接池 implementations 的产品文档。

以下 example 显示了 DBCP configuration:

1
2
3
4
5
6
7
8
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

以下 example 显示了 C3P0 configuration:

1
2
3
4
5
6
7
8
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

使用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,例如JdbcTemplateDataSourceUtils

有关更多详细信息,请参阅类 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[] batchUpdate(final List<Actor> actors) {
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, actors.get(i).getFirstName());
ps.setString(2, actors.get(i).getLastName());
ps.setLong(3, actors.get(i).getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
}

// ... additional methods
}

如果您处理更新流或从文件读取,则可能具有首选批量大小,但最后一批可能没有该数量的条目。在这种情况下,您可以使用InterruptibleBatchPreparedStatementSetter接口,该接口允许您在输入源耗尽时中断批处理。 isBatchExhausted方法允许您发出批次结束的信号。

使用Objects列表的批处理操作

JdbcTemplateNamedParameterJdbcTemplate都提供了另一种提供批量更新的方法。您可以将调用中的所有参数值作为列表提供,而不是实现特殊的批处理接口。 framework 循环遍历这些值并使用内部预处理语句 setter。 API 会有所不同,具体取决于您是否使用命名参数。对于命名参数,为SqlParameterSource提供 array,为批处理的每个成员提供一个条目。您可以使用SqlParameterSourceUtils.createBatch便捷方法创建此 array,传入 bean-style objects 的 array(使用与参数对应的 getter 方法),String -keyed Map实例(包含相应参数作为值),或两者的混合。

以下 example 显示了使用命名参数的批量更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class JdbcActorDao implements ActorDao {

private NamedParameterTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int[] batchUpdate(List<Actor> actors) {
return this.namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
SqlParameterSourceUtils.createBatch(actors));
}

// ... additional methods
}

对于使用经典?占位符的 SQL 语句,您传入包含带有更新值的 object array 的列表。此 object array 必须在 SQL 语句中为每个占位符分配一个条目,并且它们必须与 SQL 语句中定义的顺序相同。

以下 example 与前面的 example 相同,只是它使用经典的 JDBC ?占位符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(), actor.getLastName(), actor.getId()};
batch.add(values);
}
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
}

// ... additional methods
}

我们之前描述的所有批处理更新方法都返回int array,其中包含每个批处理条目的受影响行数。 JDBC 驱动程序报告此计数。如果计数不可用,则 JDBC 驱动程序返回_val的 value。

在这种情况下,通过在底层PreparedStatement上自动设置值,每个 value 的相应 JDBC 类型需要从给定的 Java 类型派生。虽然这通常很有效,但可能存在问题(例如,Map-contained null值)。 Spring,默认情况下,calls ParameterMetaData.getParameterType在这种情况下,使用 JDBC 驱动程序可能会很昂贵。如果遇到 performance 问题,您应该使用最近的驱动程序 version 并考虑将spring.jdbc.getParameterType.ignore property 设置为true(作为 JVM 系统 property 或 class 路径根目录中的spring.properties文件) - 对于 example,如 Oracle 12c 上报告的那样(SPR-16139)。

或者,您可以考虑通过’BatchPreparedStatementSetter’(如前所示),通过给定基于’List ‘的调用的显式类型 array,通过自定义’MapSqlParameterSource’上的’registerSqlType’calls 显式指定相应的 JDBC 类型实例,或通过’BeanPropertySqlParameterSource’,即使对于 null value,它也从 Java-declared property 类型派生 SQL 类型。

多批次批处理操作

批量更新的前一个示例处理的批量非常大,您希望将它们分成几个较小的批次。您可以使用前面提到的方法通过对batchUpdate方法进行多次 calls 来完成此操作,但现在有一种更方便的方法。除了 SQL 语句之外,此方法还包含Collection个 objects,其中包含参数,每个批次要进行的更新次数,以及ParameterizedPreparedStatementSetter来设置预准备语句的参数值。 framework 循环提供的值并将 update calls 分解为指定大小的批处理。

以下 example 显示批量更新使用批量大小 100:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public int[][] batchUpdate(final Collection<Actor> actors) {
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors,
100,
new ParameterizedPreparedStatementSetter<Actor>() {
public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
ps.setString(1, argument.getFirstName());
ps.setString(2, argument.getLastName());
ps.setLong(3, argument.getId().longValue());
}
});
return updateCounts;
}

// ... additional methods
}

此调用的批处理更新方法返回int数组的 array,其中包含每个批处理的 array 条目,每个更新的受影响行数为 array。 top level array 的长度表示执行的批次数,第二 level array 的长度表示该批次中的更新数。每个批次中的更新数量应该是为所有批次提供的批量大小(最后一个批次可能更少),具体取决于提供的更新对象的总数。每个更新语句的更新计数是 JDBC 驱动程序报告的更新计数。如果计数不可用,则 JDBC 驱动程序返回_val的 value。

使用SimpleJdbc类简化JDBC操作

SimpleJdbcInsertSimpleJdbcCall classes 通过利用可通过 JDBC 驱动程序检索的数据库元数据来提供简化的 configuration。这意味着您可以更少地预先配置,但如果您希望提供 code 中的所有详细信息,则可以覆盖或关闭元数据处理。

使用SimpleJdbcInsert插入数据

我们首先使用最少量的配置选项查看SimpleJdbcInsert类。您应该在数据访问层的初始化方法中实例化SimpleJdbcInsert。对于此 example,初始化方法是setDataSource方法。您不需要继承SimpleJdbcInsert 类。相反,您可以使用withTableName方法创建新实例并设置 table name。该类的配置方法遵循返回SimpleJdbcInsert实例的fluid样式,该样式返回SimpleJdbcInsert的实例,它允许您链接所有 configuration 方法。以下 example 仅使用一个 configuration 方法(稍后我们将显示多个方法的示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}

public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}

// ... additional methods
}

这里使用的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

使用第二种方法运行 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

insert 的执行与依赖元数据确定要使用的列相同。

使用SqlParameterSource提供参数值

使用Map提供参数值工作正常,但它不是最方便使用的 class。 Spring 提供了几个SqlParameterSource接口的 implementations,您可以使用它。第一个是BeanPropertySqlParameterSource,如果你有一个包含你的值的 JavaBean-compliant class,这是一个非常方便的 class。它使用相应的 getter 方法来提取参数值。以下 example 显示了如何使用BeanPropertySqlParameterSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

另一种选择是类似于MapMapSqlParameterSource,但提供了一种可以链接的更方便的addValue方法。以下 example 显示了如何使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}

public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}

// ... additional methods
}

如您所见,configuration 是相同的。只有执行 code 必须更改为使用这些替代输入 classes。

使用SimpleJdbcCall调用存储过程

SimpleJdbcCall class 使用数据库中的元数据来查找inout参数的名称,这样您就不必显式声明它们。如果您愿意这样做,或者您有没有自动映射到 Java class 的参数(例如ARRAYSTRUCT),则可以声明参数。第一个 example 显示了一个简单的过程,该过程仅从 MySQL 数据库返回VARCHARDATE格式的标量值。 example 过程读取指定的 actor 条目,并以out参数的形式返回first_namelast_namebirth_date列。以下列表显示了第一个 example:

1
2
3
4
5
6
7
8
9
10
CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;

in_id参数包含您正在查找的 actor 的idout参数 return 从 table 读取的数据。

您可以以类似于声明SimpleJdbcInsert的方式声明SimpleJdbcCall。您应该在 data-access 层的初始化方法中实例化和配置 class。与StoredProcedure class 相比,您无需创建子类,也无需声明可在数据库元数据中查找的参数。以下configuration 的示例使用前面的存储过程(除了DataSource之外,唯一的 configuration 选项是存储过程的 name):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}

public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}

// ... additional methods
}

为执行调用而编写的 code 涉及 creating 包含 IN 参数的SqlParameterSource。您必须_输入为输入 value 提供的 name 与存储过程中声明的参数 name 的 name。该案例不必 match,因为您使用元数据来确定应如何在存储过程中引用数据库对象。存储过程的源中指定的内容不一定是存储在数据库中的方式。某些数据库将名称转换为全部大写,而其他数据库使用小写或使用指定的大小写。

execute方法接受 IN 参数并返回Map,其中包含由 name 键入的任何out参数,如存储过程中指定的那样。在这种情况下,它们是out_first_nameout_last_nameout_birth_date

execute方法的最后一部分创建一个Actor实例,用于 return 检索的数据。同样,使用out参数的名称非常重要,因为它们在存储过程中声明。此外,结果 map 中存储的out参数名称中的情况与数据库中out参数名称的情况相匹配,这可能因数据库而异。为了使 code 更具可移植性,您应该进行 case-insensitive 查找或指示 Spring 使用LinkedCaseInsensitiveMap。要执行后者,您可以创建自己的JdbcTemplate并将setResultsMapCaseInsensitive property 设置为true。然后,您可以将此自定义的JdbcTemplate实例传递给SimpleJdbcCall的构造函数。以下 example 显示了此 configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}

// ... additional methods
}

通过执行此操作,可以避免在用于返回的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadActor;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}

// ... additional methods
}

两个示例的执行和结束结果是相同的。第二个 example 明确指定所有细节,而不是依赖于元数据。

如何定义SqlParameters

要为SimpleJdbc classes 以及 RDBMS 操作 classes(在将 JDBC 操作建模为 Java Objects中涵盖)定义参数,可以使用SqlParameter或其子类之一。为此,通常在构造函数中指定参数 name 和 SQL 类型。 SQL 类型是使用java.sql.Types常量指定的。在本章的前面,我们看到了类似于以下内容的声明:

1
2
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

SqlParameter的第一个 line 声明一个 IN 参数。您可以使用 IN 参数为存储过程 calls 和查询使用SqlQuery及其子类(在了解 SqlQuery中涵盖)。

第二个 line(带SqlOutParameter)声明了一个out参数,用于存储过程调用。还有SqlInOutParameter for InOut参数(为过程提供 IN value 并且 return value 的参数)。

仅声明为SqlParameterSqlInOutParameter的参数用于提供输入值。这与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
2
3
4
5
6
7
8
9
CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;

要调用此 function,我们再次在初始化方法中创建SimpleJdbcCall,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JdbcActorDao implements ActorDao {

private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall funcGetActorName;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}

public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}

// ... additional methods
}

使用的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
2
3
4
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

要调用此过程,可以声明RowMapper。因为要 map 的 class 遵循 JavaBean 规则,所以可以使用通过在newInstance方法中将所需的 class 传递给 map 而创建的BeanPropertyRowMapper。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JdbcActorDao implements ActorDao {

private SimpleJdbcCall procReadAllActors;

public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor.class));
}

public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
}

// ... additional methods
}

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 是MappingSqlQueryWithParametersUpdatableSqlQuery

使用MappingSqlQuery

MappingSqlQuery是一个可重用的查询,其中具体的子类必须实现 abstract mapRow(..)方法,以将提供的ResultSet的每一行转换为指定类型的 object。以下 example 显示了一个自定义查询,该查询将t_actor关系中的数据映射到Actor class 的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ActorMappingQuery extends MappingSqlQuery<Actor> {

public ActorMappingQuery(DataSource ds) {
super(ds, "select id, first_name, last_name from t_actor where id = ?");
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}

@Override
protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}

}

class 使用Actor类型扩展MappingSqlQuery参数化。此客户查询的构造函数将DataSource作为唯一参数。在此构造函数中,您可以使用DataSource和应该执行的 SQL 来调用超类上的构造函数,以检索此查询的行。此 SQL 用于创建PreparedStatement,因此它可能包含在执行期间传递的任何参数的占位符。您必须使用传入SqlParameterdeclareParameter方法声明每个参数。 SqlParameter采用 name,以及java.sql.Types中定义的 JDBC 类型。定义所有参数后,可以调用compile()方法,以便可以准备语句并稍后运行 run。这个 class 在编译之后是 thread-safe,因此,当初始化 DAO 时,这些实例被创建为 long,它们可以作为实例变量保存并重用。以下 example 显示了如何定义这样的 class:

1
2
3
4
5
6
7
8
9
10
private ActorMappingQuery actorMappingQuery;

@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}

public Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}

前面 example 中的方法使用作为唯一参数传入的id检索客户。由于我们只想返回一个 object,因此我们使用id作为参数调用findObject便捷方法。如果我们有一个返回 objects 列表并获取其他参数的查询,我们将使用execute方法之一,它将 array 参数值作为 varargs 传入。以下 example 显示了这样一种方法:

1
2
3
4
public List<Actor> searchForActors(int age, String namePattern) {
List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
return actors;
}

使用SqlUpdate

SqlUpdate class 封装了 SQL 更新。与查询一样,更新 object 是可重用的,并且与所有RdbmsOperation classes 一样,更新可以具有参数并在 SQL 中定义。这个 class 提供了许多update(..)方法,类似于查询 objects 的execute(..)方法。 SQLUpdate class 是具体的。它可以是子类 - 例如,添加自定义更新方法。但是,您不必为SqlUpdate class 创建子类,因为可以通过设置 SQL 和声明参数来轻松地对其进行参数化。以下 example 创建名为execute的自定义更新方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class UpdateCreditRating extends SqlUpdate {

public UpdateCreditRating(DataSource ds) {
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
declareParameter(new SqlParameter("id", Types.NUMERIC));
compile();
}

/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
public int execute(int id, int rating) {
return update(rating, id);
}
}

使用StoredProcedure

StoredProcedure class 是 RDBMS 存储过程的 object 抽象的超类。这个 class 是abstract,它的各种execute(..)方法都有protected访问权限,阻止了通过提供更严格 typing 的子类以外的用法。

继承的sql property 是 RDBMS 中存储过程的 name。

要为StoredProcedure class 定义参数,可以使用SqlParameter或其子类之一。您必须在构造函数中指定参数 name 和 SQL 类型,如下面的 code 片段所示:

1
2
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class StoredProcedureDao {

private GetSysdateProcedure getSysdate;

@Autowired
public void init(DataSource dataSource) {
this.getSysdate = new GetSysdateProcedure(dataSource);
}

public Date getSysdate() {
return getSysdate.execute();
}

private class GetSysdateProcedure extends StoredProcedure {

private static final String SQL = "sysdate";

public GetSysdateProcedure(DataSource dataSource) {
setDataSource(dataSource);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}

public Date execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
Map<String, Object> results = execute(new HashMap<String, Object>());
Date sysdate = (Date) results.get("date");
return sysdate;
}
}

}

以下的示例有两个输出参数(在本例中为 Oracle REF 游标):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class TitlesAndGenresStoredProcedure extends StoredProcedure {

private static final String SPROC_NAME = "AllTitlesAndGenres";

public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}

public Map<String, Object> execute() {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(new HashMap<String, Object>());
}
}

注意TitlesAndGenresStoredProcedure构造函数中使用的declareParameter(..)方法的重载变体是如何传递RowMapper implementation 实例的。这是重用现有功能的一种非常方便和强大的方法。接下来的两个示例为两个RowMapper 实现提供了 code。

对于提供的ResultSet中的每一行,TitleMapper class maps 到Title域 object,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;

public final class TitleMapper implements RowMapper<Title> {

public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}

对于提供的ResultSet中的每一行,GenreMapper class maps 到Genre域 object,如下所示:

1
2
3
4
5
6
7
8
9
10
11
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;

public final class GenreMapper implements RowMapper<Genre> {

public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}

要将参数传递给在 RDBMS 的定义中具有一个或多个输入参数的存储过程,您可以 code 一个强类型的execute(..)方法,该方法将委托给超类中的无类型execute(Map)方法,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class TitlesAfterDateStoredProcedure extends StoredProcedure {

private static final String SPROC_NAME = "TitlesAfterDate";
private static final String CUTOFF_DATE_PARAM = "cutoffDate";

public TitlesAfterDateStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
compile();
}

public Map<String, Object> execute(Date cutoffDate) {
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}

参数和数据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,BeanPropertySqlParameterSourceMapSqlParameterSource。它们都具有为任何命名参数值注册 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。

LobCreatorLobHandler为 LOB 输入和输出提供以下支持:

  • BLOB
  • byte[]getBlobAsBytessetBlobAsBytes
  • InputStreamgetBlobAsBinaryStreamsetBlobAsBinaryStream
  • CLOB
  • StringgetClobAsStringsetClobAsString
  • InputStreamgetClobAsAsciiStreamsetClobAsAsciiStream
  • ReadergetClobAsCharacterStreamsetClobAsCharacterStream

下一个 example 显示了如何创建和插入 BLOB。稍后我们将展示如何从数据库中读取它。

此 example 使用AbstractLobCreatingPreparedStatementCallbackAbstractLobCreatingPreparedStatementCallback的 implementation。它实现了一个方法setValues。此方法提供了一个LobCreator,我们用它来设置 SQL insert 语句中 LOB 列的值。

对于这个例子,我们假设有一个变量lobHandler,它已经被设置为DefaultLobHandler的一个实例。您通常通过依赖注入设置此 value。

以下 example 显示了如何创建和插入 BLOB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);

jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(lobHandler) { (1)
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
ps.setLong(1, 1L);
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); (2)
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); (3)
}
}
);

blobIs.close();
clobReader.close();
1 传入lobHandler(在此 example 中)是一个普通的DefaultLobHandler
2 使用方法setClobAsCharacterStream传入 CLOB 的内容。
3 使用方法setBlobAsBinaryStream传入 BLOB 的内容。

如果从DefaultLobHandler.getLobCreator()返回的LobCreator上调用setBlobAsBinaryStreamsetClobAsAsciiStreamsetClobAsCharacterStream方法,则可以选择为contentLength参数指定负值。如果指定的内容长度为负,则DefaultLobHandler使用 set-stream 方法的 JDBC 4.0 变体而不使用 length 参数。否则,它将指定的长度传递给驱动程序。

请参阅用于验证它是否支持在不提供内容长度的情况下流式传输 LOB 的 JDBC 驱动程序的文档。

现在是 time 从数据库中读取 LOB 数据。同样,您使用JdbcTemplate与相同的实例变量lobHandler和 reference 到DefaultLobHandler。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
8
9
10
11
List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
new RowMapper<Map<String, Object>>() {
public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
Map<String, Object> results = new HashMap<String, Object>();
String clobText = lobHandler.getClobAsString(rs, "a_clob"); (1)
results.put("CLOB", clobText);
byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); (2)
results.put("BLOB", blobBytes);
return results;
}
});
1 使用方法getClobAsString来检索 CLOB 的内容。
2 使用方法getBlobAsBytes来检索 BLOB 的内容。

传入IN条款的ListsofValues

SQL 标准允许基于包含变量值列表的表达式来选择行。典型的 example 将是select * from T_ACTOR where id in (1, 2, 3)。 JDBC 标准不直接支持准备的 statements 这个变量列表。您不能声明可变数量的占位符。您需要准备好所需占位符数量的多种变体,或者一旦知道需要多少占位符,就需要动态生成 SQL string。 NamedParameterJdbcTemplateJdbcTemplate中提供的命名参数支持采用后一种方法。您可以将值作为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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestItemStoredProcedure extends StoredProcedure {

public TestItemStoredProcedure(DataSource dataSource) {
...
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
new SqlReturnType() {
public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String typeName) throws SQLException {
STRUCT struct = (STRUCT) cs.getObject(colIndx);
Object[] attr = struct.getAttributes();
TestItem item = new TestItem();
item.setId(((Number) attr[0]).longValue());
item.setDescription((String) attr[1]);
item.setExpirationDate((java.util.Date) attr[2]);
return item;
}
}));
...
}

您可以使用SqlTypeValue将 Java object 的 value(例如TestItem)传递给存储过程。 SqlTypeValue接口有一个必须实现的方法(名为createTypeValue)。传入 active 连接,您可以使用它来创建 database-specific objects,例如StructDescriptor实例或ArrayDescriptor实例。以下 example 创建StructDescriptor实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final TestItem testItem = new TestItem(123L, "A test item",
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));

SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
Struct item = new STRUCT(itemDescriptor, conn,
new Object[] {
testItem.getId(),
testItem.getDescription(),
new java.sql.Date(testItem.getExpirationDate().getTime())
});
return item;
}
};

您现在可以将此SqlTypeValue添加到Map,其中包含存储过程的execute调用的输入参数。

SqlTypeValue的另一个用途是将 array 值传递给 Oracle 存储过程。 Oracle 有自己的内部ARRAY class,在这种情况下必须使用它,您可以使用SqlTypeValue创建 Oracle ARRAY的实例并使用 Java ARRAY中的值填充它,如下面的示例所示:

1
2
3
4
5
6
7
8
9
final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
return idArray;
}
};

嵌入式数据库支持

org.springframework.jdbc.datasource.embedded包提供对嵌入式 Java 数据库引擎的支持。本机提供对HSQLH2Derby的支持。您还可以使用可扩展 API 来插入新的嵌入式数据库类型和DataSource 实现。

为何使用嵌入式数据库?

嵌入式数据库在项目的开发阶段非常有用,因为它具有轻量级特性。优点包括易于配置,快速启动 time,可测试性以及在开发过程中快速发展 SQL 的能力。

使用SpringXML创建嵌入式数据库

如果要在 Spring ApplicationContext中将嵌入式数据库实例公开为 bean,则可以在spring-jdbc命名空间中使用embedded-database标记:

1
2
3
4
<jdbc:embedded-database id="dataSource" generate-name="true">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>

前面的 configuration 创建一个嵌入式 HSQL 数据库,该数据库使用 SQL 填充 classpath 根目录中的schema.sqltest-data.sql资源。此外,作为最佳实践,将为嵌入式数据库分配唯一生成的 name。嵌入式数据库作为类型的 bean 可用于 Spring 容器,然后可根据需要将其注入数据访问 objects。

以编程方式创建嵌入式数据库

EmbeddedDatabaseBuilder class 提供了一个 fluent API,用于以编程方式构建嵌入式数据库。当您需要在 stand-alone 环境或 stand-alone integration 测试中创建嵌入式数据库时,可以使用此方法,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()

有关所有支持选项的更多详细信息,请参阅用于 EmbeddedDatabaseBuilder 的 javadoc

您还可以使用通过 Java configuration 创建嵌入式数据库,如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class DataSourceConfig {

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("schema.sql")
.addScripts("user_data.sql", "country_data.sql")
.build();
}
}

选择嵌入式数据库类型

本节介绍如何选择 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DataAccessIntegrationTestTemplate {

private EmbeddedDatabase db;

@Before
public void setUp() {
// creates an HSQL in-memory database populated from default scripts
// classpath:schema.sql and classpath:data.sql
db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.addDefaultScripts()
.build();
}

@Test
public void testDataAccess() {
JdbcTemplate template = new JdbcTemplate(db);
template.query( /* ... */ );
}

@After
public void tearDown() {
db.shutdown();
}

}

为嵌入式数据库生成唯一名称

如果测试套件无意中尝试重新创建同一数据库的其他实例,则开发团队经常会遇到嵌入式数据库的错误。如果 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
2
3
4
<jdbc:initialize-database data-source="dataSource">
<jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
<jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
</jdbc:initialize-database>

前面的 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
2
3
4
<jdbc:initialize-database data-source="dataSource"
enabled="#{systemProperties.INITIALIZE_DATABASE}"> (1)
<jdbc:script location="..."/>
</jdbc:initialize-database>
1 从名为INITIALIZE_DATABASE的系统 property 获取enabled的 value。

控制现有数据发生情况的第二个选择是更容忍失败。为此,您可以控制初始化程序忽略它从脚本执行的 SQL 中的某些错误的能力,如下面的示例所示:

1
2
3
<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
<jdbc:script location="..."/>
</jdbc:initialize-database>

在前面的示例中,我们说我们希望脚本有时会针对空数据库运行,并且脚本中有一些DROP statements 因此会失败。因此失败的 SQL DROP statements 将被忽略,但其他失败将导致 exception。如果您的 SQL 方言不支持DROP … IF EXISTS(或类似),但您希望在 re-creating 之前无条件地删除所有测试数据,这将非常有用。在这种情况下,第一个脚本通常是一组DROP statements,后跟一组CREATE statements。

ignore-failures选项可以设置为NONE(默认值),DROPS(忽略失败的丢弃)或ALL(忽略所有失败)。

如果脚本中根本不存在;字符,则每个语句应由;或新 line 分隔。您可以按脚本控制全局或脚本,如下面的 example 所示:

1
2
3
4
5
<jdbc:initialize-database data-source="dataSource" separator="@@"> (1)
<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> (2)
<jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
<jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
</jdbc:initialize-database>
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 实现LifecycleSmartLifecycle。当 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实例,JDBC DataSource实例,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实例,JPA EntityManagerFactory实例,JDBC DataSource实例和其他相关资源的位置和 configuration。这使得这些值易于管理和更改。 Spring 提供高效,简单,安全的持久性资源处理。对于 example,使用 Hibernate 的相关 code 通常需要使用相同的 Hibernate Session来确保效率和正确的 transaction 处理。 Spring 通过 Hibernate SessionFactory公开当前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 提供连接处理和SQLExceptionDataAccessException层次的正确转换,包括将 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 会抛出HibernateExceptionPersistenceException的子类,具体取决于技术。这些 exceptions 都是运行时 exceptions,不必声明或捕获。您可能还需要处理IllegalArgumentExceptionIllegalStateException。这意味着调用者只能将 exceptions 视为通常致命的,除非他们想依赖持久性技术自己的 exception 结构。如果不将调用者绑定到 implementation 策略,则无法捕获特定原因(例如乐观锁定失败)。对于强 ORM-based 或不需要任何特殊 exception 处理(或两者)的应用程序,这个 trade-off 可能是可以接受的。但是,Spring 允许通过@Repository annotation 透明地应用 exception 转换。以下示例(一个用于 Java configuration,一个用于 XML configuration)显示了如何执行此操作:

1
2
3
4
5
6
@Repository
public class ProductDaoImpl implements ProductDao {

// class body here...

}
1
2
3
4
5
6
7
8
<beans>

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

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

</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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<beans>

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

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

</beans>

从本地 Jakarta Commons _DBCP BasicDataSource切换到 JNDI-located DataSource(通常由 application 服务器管理)只是 configuration 的问题,如下面的 example 所示:

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

您还可以使用 Spring 的JndiObjectFactoryBean/<jee:jndi-lookup>来访问 JNDI-located SessionFactory来检索并公开它。但是,这通常不是 EJB context 之外的 common。

Spring 还提供LocalSessionFactoryBuilder变体,与@Bean style configuration 和编程设置无缝集成(不涉及FactoryBean)。

LocalSessionFactoryBeanLocalSessionFactoryBuilder都支持后台引导,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ProductDaoImpl implements ProductDao {

private SessionFactory sessionFactory;

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

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

除了将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
2
3
4
5
6
7
<beans>

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

</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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ProductServiceImpl implements ProductService {

private ProductDao productDao;

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

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

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

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

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

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

<tx:annotation-driven/>

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

</beans>

编程式事务划分

您可以在数据访问服务任意数量的操作之上,在 application 的更高 level 中划分 transactions。对周围业务服务的实施也没有限制。它只需要 Spring PlatformTransactionManager。同样,后者可以来自任何地方,但最好是通过setTransactionManager(..)方法作为 bean reference。此外,productDAO应该由setProductDao(..)方法设置。以下一对片段在 Spring application context 中显示 transaction manager 和业务服务定义,为业务方法 implementation 显示 example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<beans>

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

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

</beans>
public class ProductServiceImpl implements ProductService {

private TransactionTemplate transactionTemplate;
private ProductDao productDao;

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

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

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

Spring 的TransactionInterceptor允许使用回调 code 抛出任何已检查的 application exception,而TransactionTemplate仅限于回调中未经检查的 exceptions。 TransactionTemplate在未经检查的 application exception 的情况下触发回滚,或者 transaction 被 application 标记为 rollback-only(通过设置TransactionStatus)。默认情况下,TransactionInterceptor的行为方式相同,但每个方法允许可配置的回滚 policies。

事务管理策略

TransactionTemplateTransactionInterceptor将实际的 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作为策略。

HibernateTransactionManagerJtaTransactionManager都允许使用 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 SessionFactoryDataSourceHibernateTransactionManager会自动将 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 的HibernateTransactionManagerJtaTransactionManager。您可以获得所有好处,包括正确的 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
2
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.

您可以通过让 Hibernate 知道它同步的 JTA PlatformTransactionManager实例(以及 Spring)来解决此警告。您有两种选择:

  • 如果在 application context 中,您已经直接获取 JTA PlatformTransactionManager object(可能是从 JNDI 到JndiObjectFactoryBean<jee:jndi-lookup>)并将其提供给_Spample 的JtaTransactionManager,最简单的方法是指定 bean 定义此 JTA 的 reference PlatformTransactionManager实例作为LocalSessionFactoryBean. property 的 value LocalSessionFactoryBean. Spring 然后使 object 可用于 Hibernate。
  • 更有可能的是,你还没有 JTA PlatformTransactionManager实例,因为 Spring 的JtaTransactionManager可以自己找到它。因此,您需要配置 Hibernate 以直接查找 JTA PlatformTransactionManager。您可以通过在 Hibernate configuration 中配置 application server-specific TransactionManagerLookup 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

您只能在简单的部署环境中使用此选项,例如 stand-alone applications 和 integration 测试。

LocalEntityManagerFactoryBean创建EntityManagerFactory适用于简单的部署环境,其中 application 仅使用 JPA 进行数据访问。工厂 bean 使用 JPA PersistenceProvider auto-detection 机制(根据 JPA 的 Java SE 引导),并且在大多数情况下,要求您仅指定持久性单元 name。以下 XML example 配置了这样的 bean:

1
2
3
4
5
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</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
2
3
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</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而不是一个普通的 JPA LocalContainerEntityManagerFactoryBean,让它与 JPA 访问 code 以及本机 Hibernate 访问 code 进行交互。有关详细信息,请参阅用于 JPA 交互的原生 Hibernate 设置

LocalContainerEntityManagerFactoryBean完全控制EntityManagerFactory configuration,适用于需要 fine-grained 自定义的环境。 LocalContainerEntityManagerFactoryBean基于persistence.xml文件,提供的dataSourceLookup策略和指定的loadTimeWeaver创建PersistenceUnitInfo实例。因此,可以使用 JNDI 之外的自定义数据源并控制编织进程。以下 example 显示了LocalContainerEntityManagerFactoryBean的典型 bean 定义:

1
2
3
4
5
6
7
8
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>

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

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

<exclude-unlisted-classes/>快捷方式表示不应扫描带注释的实体 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
2
3
4
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>

但是,如果需要,您可以通过loadTimeWeaver property 手动指定专用的 weaver,如下面的 example 所示:

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

无论如何配置 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
<value>classpath:/my/package/**/custom-persistence.xml</value>
<value>classpath*:META-INF/persistence.xml</value>
</list>
</property>
<property name="dataSources">
<map>
<entry key="localDataSource" value-ref="local-db"/>
<entry key="remoteDataSource" value-ref="remote-db"/>
</map>
</property>
<!-- if no datasource is specified, use this one -->
<property name="defaultDataSource" ref="remoteDataSource"/>
</bean>

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

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

背景引导

LocalContainerEntityManagerFactoryBean支持通过bootstrapExecutor property 进行后台自举,如下面的 example 所示:

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

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

基于JPA实现DAO:EntityManagerFactory和EntityManager

虽然EntityManagerFactory实例是 thread-safe,但EntityManager实例不是。注入的 JPA EntityManager的行为类似于从 application 服务器的 JNDI 环境中获取的EntityManager,如 JPA 规范所定义。它将所有 calls 委托给当前 transactional EntityManager,如果有的话。否则,它会回退到每个操作新创建的EntityManager,实际上使其使用 thread-safe。

通过使用注入的EntityManagerFactoryEntityManager,可以在没有任何 Spring 依赖项的情况下对普通 JPA 编写 code。如果启用PersistenceAnnotationBeanPostProcessor,Spring 可以理解字段和方法 level 中的@PersistenceUnit@PersistenceContext 注释。以下 example 显示了一个使用@PersistenceUnit annotation 的普通 JPA DAO implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProductDaoImpl implements ProductDao {

private EntityManagerFactory emf;

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

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

前面的 DAO 不依赖于 Spring,并且仍然可以很好地适应 Spring application context。此外,DAO 利用 annotations 要求注入默认的EntityManagerFactory,如下面的 example bean 定义所示:

1
2
3
4
5
6
7
8
<beans>

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

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

</beans>

作为显式定义PersistenceAnnotationBeanPostProcessor的替代方法,请考虑在 application context configuration 中使用 Spring context:annotation-config XML 元素。这样做会自动将所有 Spring 标准 post-processors 注册为 annotation-based configuration,包括CommonAnnotationBeanPostProcessor等等。

考虑以下 example:

1
2
3
4
5
6
7
8
<beans>

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

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

</beans>

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

1
2
3
4
5
6
7
8
9
10
11
public class ProductDaoImpl implements ProductDao {

@PersistenceContext
private EntityManager em;

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

@PersistenceContext 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,JpaTransactionManagerAbstractEntityManagerFactoryBean的子类允许将自定义JpaDialect传递到jpaDialect bean property。 JpaDialect implementation 可以启用 Spring 支持的以下高级 features,通常以 vendor-specific 方式:

  • 应用特定的 transaction 语义(例如自定义隔离 level 或 transaction timeout)
  • 检索 transactional JDBC Connection(用于暴露于 JDBC-based DAO)
  • PersistenceExceptions到 Spring DataAccessExceptions的高级翻译

这对于特殊的 transaction 语义和 exception 的高级转换特别有用。默认 implementation(DefaultJpaDialect)不提供任何特殊功能,如果需要前面列出的 features,则必须指定相应的方言。

作为一个更广泛的提供者适应设施,主要用于 Spring 的 full-featured LocalContainerEntityManagerFactoryBean设置,JpaVendorAdapterJpaDialect的功能与其他 provider-specific 默认值相结合。指定HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter分别是 Hibernate 或 EclipseLink 的 auto-configuring EntityManagerFactory设置的最便捷方式。请注意,这些提供程序适配器主要设计用于 Spring-driven transaction management(即,与JpaTransactionManager一起使用)。

有关其操作的更多详细信息以及如何在 Spring 的 JPA 支持中使用它们,请参阅JpaDialectJpaVendorAdapter 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-integrated DataSource __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-provided EntityManagerFactory可能需要在服务器 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 LocalContainerEntityManagerFactoryBeanJpaTransactionManager组合,允许在同一本地 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)。

LocalSessionFactoryBeanLocalSessionFactoryBuilder支持后台自举,就像 JPA LocalContainerEntityManagerFactoryBean一样。有关介绍,请参阅背景引导

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
2
3
4
5
6
7
public interface Marshaller {

/**
* Marshal the object graph with the given root into the provided Result.
*/
void marshal(Object graph, Result result) throws XmlMappingException, IOException;
}

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.Filejava.io.OutputStreamjava.io.Writer

尽管marshal()方法接受普通的 object 作为其第一个参数,但大多数Marshaller implementations 都无法处理任意的 objects。相反,object class 必须映射到映射文件中,标记为 annotation,向 marshaller 注册,或者具有 common base class。请参阅本章后面的部分,以确定 O-X 技术如何管理它。

了解Unmarshaller

Marshaller类似,我们有org.springframework.oxm.Unmarshaller接口,如下所示:

1
2
3
4
5
6
7
public interface Unmarshaller {

/**
* Unmarshal the given provided Source into an object graph.
*/
Object unmarshal(Source source) throws XmlMappingException, IOException;
}

该接口还有一个方法,它从给定的javax.xml.transform.Source(XML 输入抽象)读取并返回 object 读取。与Result一样,Source是一个标记接口,具有三个具体的 implementation。每个包装一个不同的 XML 表示,如下面的 table 表示:

来源实现 包装 XML 表示
DOMSource org.w3c.dom.Node
SAXSource org.xml.sax.InputSourceorg.xml.sax.XMLReader
StreamSource java.io.Filejava.io.InputStreamjava.io.Reader

即使有两个单独的编组接口(MarshallerUnmarshaller),Spring-WS 中的所有_implement 都在一个 class 中实现。这意味着您可以连接一个 marshaller class 并将其作为 marshaller 和applicationContext.xml中的 unmarshaller 引用。

了解XmlMappingException

Spring 将 exceptions 从底层的 O-X 映射工具转换为自己的 exception 层次结构,并将XmlMappingException作为根 exception。这些运行时 exceptions 包装原始 exception,以便不会丢失任何信息。

此外,MarshallingFailureExceptionUnmarshallingFailureException提供了编组和解组操作之间的区别,即使底层的 O-X 映射工具不这样做。

O-X Mapping exception 层次结构如下图所示:

oxm exceptions

使用Marshaller和Unmarshaller

您可以在各种情况下使用 Spring 的 OXM。在下面的示例中,我们使用它来将 Spring-managed application 的设置编组为 XML 文件。在下面的示例中,我们使用一个简单的 JavaBean 来表示设置:

1
2
3
4
5
6
7
8
9
10
11
12
public class Settings {

private boolean fooEnabled;

public boolean isFooEnabled() {
return fooEnabled;
}

public void setFooEnabled(boolean fooEnabled) {
this.fooEnabled = fooEnabled;
}
}

application class 使用此 bean 来存储其设置。除了 main 方法之外,class 还有两个方法:saveSettings()将设置 bean 保存到名为settings.xml的文件中,loadSettings()再次加载这些设置。以下main()方法构造 Spring application context 并_call 这两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class Application {

private static final String FILE_NAME = "settings.xml";
private Settings settings = new Settings();
private Marshaller marshaller;
private Unmarshaller unmarshaller;

public void setMarshaller(Marshaller marshaller) {
this.marshaller = marshaller;
}

public void setUnmarshaller(Unmarshaller unmarshaller) {
this.unmarshaller = unmarshaller;
}

public void saveSettings() throws IOException {
FileOutputStream os = null;
try {
os = new FileOutputStream(FILE_NAME);
this.marshaller.marshal(settings, new StreamResult(os));
} finally {
if (os != null) {
os.close();
}
}
}

public void loadSettings() throws IOException {
FileInputStream is = null;
try {
is = new FileInputStream(FILE_NAME);
this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
} finally {
if (is != null) {
is.close();
}
}
}

public static void main(String[] args) throws IOException {
ApplicationContext appContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
Application application = (Application) appContext.getBean("application");
application.saveSettings();
application.loadSettings();
}
}

Application需要设置marshallerunmarshaller property。我们可以使用以下applicationContext.xml来实现:

1
2
3
4
5
6
7
<beans>
<bean id="application" class="Application">
<property name="marshaller" ref="castorMarshaller" />
<property name="unmarshaller" ref="castorMarshaller" />
</bean>
<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
</beans>

此 application context 使用 Castor,但我们可以使用本章后面介绍的任何其他 marshaller 实例。请注意,默认情况下,Castor 不需要任何进一步的 configuration,因此 bean 定义相当简单。另请注意,CastorMarshaller同时实现了MarshallerUnmarshaller,因此我们可以在 application 的marshallerunmarshallerproperty 中引用castorMarshaller bean。

此 sample application 生成以下settings.xml文件:

1
2
<?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>

XML配置命名空间

您可以使用 OXM 命名空间中的标记更简洁地配置 marshallers。要使这些标记可用,必须首先在 XML configuration 文件的前导码中引用相应的 schema。以下 example 显示了如何执行此操作:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm" (1)
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm.xsd"> (2)
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中描述的MarshallerUnmarshaller接口。相应的 integration classes 位于org.springframework.oxm.jaxb包中。

使用Jaxb2Marshaller

Jaxb2Marshaller class 实现了 Spring 的MarshallerUnmarshaller接口。它需要 context 路径才能运行。您可以通过设置contextPath property 来设置 context 路径。 context 路径是包含 schema 派生 classes 的 colon-separated Java 包名称列表。它还提供classesToBeBound property,允许您设置 marshaller 支持的_arlass 的 array。 Schema 验证是通过为 bean 指定一个或多个 schema 资源来执行的,如下面的 example 所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<beans>
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>org.springframework.oxm.jaxb.Flight</value>
<value>org.springframework.oxm.jaxb.Flights</value>
</list>
</property>
<property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
</bean>

...

</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
2
3
4
5
<oxm:jaxb2-marshaller id="marshaller">
<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
...
</oxm:jaxb2-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 实现MarshallerUnmarshaller接口。要进行操作,需要 class 的 name 编组,您可以使用targetClass property 进行设置。 (可选)您可以通过设置bindingName property 来设置 binding name。在下面的示例中,我们绑定Flights class:

1
2
3
4
5
6
<beans>
<bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
<property name="targetClass">org.springframework.oxm.jibx.Flights</property>
</bean>
...
</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
2
3
4
5
6
7
8
9
10
<beans>
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="aliases">
<props>
<prop key="Flight">org.springframework.oxm.xstream.Flight</prop>
</props>
</property>
</bean>
...
</beans>

默认情况下,XStream 允许任意 classes 被解组,这可能导致不安全的 Java 序列化效果。因此,我们不建议使用XStreamMarshaller从外部源(即 Web)解组 XML,因为这可能导致安全漏洞。

如果您选择使用XStreamMarshaller从外部源解组 XML,请在XStreamMarshaller上设置supportedClasses property,如下面的 example 所示:

1
2
3
4
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="supportedClasses" value="org.springframework.oxm.xstream.Flight"/>
...
</bean>

这样做可确保只有已注册的 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
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" (1)
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd (2)
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- bean definitions here -->

</beans>
1 声明tx名称空间的使用。
2 指定位置(使用其他 schema 位置)。

通常,当您使用tx命名空间中的元素时,您还使用aop命名空间中的元素(因为 Spring 中的声明性 transaction 支持是通过使用 AOP 实现的)。前面的 XML 片段包含schema 引用所需的相关 lines,以便aop命名空间中的元素可供您使用。

jdbcSchema

jdbc元素使您可以快速配置嵌入式数据库或初始化现有数据源。这些元素分别记录在嵌入式数据库支持初始化 DataSource中。

要使用jdbc schema 中的元素,您需要在 Spring XML configuration 文件的顶部添加以下前导码。以下代码段中的文本引用了正确的 schema,以便jdbc命名空间中的元素可供您使用:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" (1)
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> (2)

<!-- bean definitions here -->

</beans>
1 声明jdbc名称空间的使用。
2 指定位置(使用其他 schema 位置)。