参考文档的这一部分涉及数据访问和 数据访问层与业务层或服务层之间的交互。

Spring对事务管理的全面支持有一定的介绍, 其次是全面覆盖各种数据访问框架和技术 Spring Framework 与之集成。

1. 交易管理

全面的交易支持是使用 Spring 的最令人信服的理由之一 框架。Spring Framework 为事务提供了一致的抽象 提供以下好处的管理:

  • 跨不同事务 API(如 Java)的一致编程模型 事务 API (JTA)、JDBC、Hibernate 和 Java 持久性 API (JPA)。

  • 支持声明式事务管理

  • 用于编程事务管理的更简单的 API 而不是复杂的事务 API,例如 JTA。

  • 与 Spring 的数据访问抽象完美集成。

以下各节介绍了 Spring Framework 的事务特性和 技术:

本章还包括对最佳实践、应用服务器集成、 以及常见问题的解决方案

1.1. Spring 框架事务支持模型的优势

传统上,Java EE 开发人员在事务管理方面有两种选择: 全局或本地交易,两者都有很大的局限性。全球 接下来的两节将回顾本地事务管理,然后是 讨论 Spring Framework 的事务管理支持如何解决 全局和本地事务模型的局限性。

1.1.1. 全局事务

全局事务允许您使用多个事务资源,通常 关系数据库和消息队列。应用程序服务器管理全局 通过 JTA 进行交易,这是一个繁琐的 API(部分原因是其 异常模型)。此外,JTA通常需要从以下来源采购 JNDI,这意味着您还需要使用 JNDI 才能使用 JTA。用途 的全局事务限制了应用程序代码的任何潜在重用,就像 JTA 一样 通常仅在应用程序服务器环境中可用。UserTransaction

以前,使用全局事务的首选方法是通过 EJB CMT (容器管理事务)。CMT 是声明性事务的一种形式 管理(与程序化事务管理不同)。EJB CMT 消除了对与事务相关的 JNDI 查找的需要,尽管使用 EJB 本身需要使用 JNDI。它消除了大部分但不是全部的写作需求 用于控制事务的 Java 代码。显着的缺点是 CMT 与 JTA 相关联 和应用程序服务器环境。此外,它仅在选择时可用 在 EJB 中实现业务逻辑(或者至少在事务性 EJB 外观后面)。这 一般来说,EJB 的负面影响是如此之大,以至于这不是一个有吸引力的命题, 尤其是在面对令人信服的声明式事务管理替代方案时。

1.1.2. 本地交易

本地事务是特定于资源的,例如与 JDBC 关联的事务 连接。本地事务可能更易于使用,但有一个明显的缺点: 它们不能跨多个事务资源工作。例如,管理 使用 JDBC 连接的事务不能在全局 JTA 事务中运行。因为 应用程序服务器不参与事务管理,它不能帮助确保 跨多个资源的正确性。(值得注意的是,大多数应用程序都使用 单个事务资源。另一个缺点是本地交易是侵入性的 到编程模型。

1.1.3. Spring Framework 的一致编程模型

Spring 解决了全局和本地事务的缺点。它让 应用程序开发人员在任何环境中都使用一致的编程模型。 您只需编写一次代码,它就可以从不同的事务管理中受益 不同环境中的策略。Spring Framework 提供了声明式和 程序化事务管理。大多数用户更喜欢声明式事务 管理,我们建议在大多数情况下进行管理。

通过编程事务管理,开发人员可以使用 Spring Framework 事务抽象,可以在任何底层事务基础结构上运行。 对于首选的声明性模型,开发人员通常很少或不编写代码 与事务管理相关,因此不依赖于 Spring Framework 交易 API 或任何其他交易 API。

您是否需要一个应用程序服务器来进行事务管理?

Spring Framework 的事务管理支持改变了传统的规则 当企业 Java 应用程序需要应用程序服务器时。

特别是,您不需要纯粹用于声明性事务的应用程序服务器 通过 EJB。事实上,即使您的应用服务器具有强大的 JTA 功能, 你可能会认为 Spring Framework 的声明式事务提供了更多的功能和 比 EJB CMT 更高效的编程模型。

通常,仅当应用程序需要时,才需要应用程序服务器的 JTA 功能 跨多个资源处理事务,这对许多人来说不是必需的 应用。许多高端应用程序使用单个、高度可扩展的数据库(例如 Oracle RAC) 代替。独立事务管理器(如 Atomikos TransactionsJOTM) 是其他选项。当然,您可能需要其他应用程序服务器功能,例如 Java 消息服务 (JMS) 和 Java EE 连接器体系结构 (JCA)。

Spring Framework 允许您选择何时将应用程序扩展到完全 已装入应用程序服务器。使用 EJB 的唯一替代方法的日子已经一去不复返了 CMT 或 JTA 是使用本地事务(例如 JDBC 连接上的事务)编写代码 如果您需要该代码在全局容器管理中运行,则将面临大量返工 交易。使用 Spring Framework,只有 配置文件需要更改(而不是您的代码)。

1.2. 理解 Spring Framework 事务抽象

Spring 事务抽象的关键是事务策略的概念。一个 事务策略由 一个 定义,具体来说是命令式的接口 事务管理和反应式界面 事务管理。以下清单显示了 API 的定义:TransactionManagerorg.springframework.transaction.PlatformTransactionManagerorg.springframework.transaction.ReactiveTransactionManagerPlatformTransactionManager

public interface PlatformTransactionManager extends TransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这主要是一个服务提供程序接口 (SPI),尽管您可以从应用程序代码中以编程方式使用它。因为是一个接口,所以它可以很容易地被嘲笑或存根为 必要。它与查找策略(如 JNDI)无关。 实现的定义与任何其他对象(或 Bean)一样 在 Spring Framework IoC 容器中。仅此一项好处就使 Spring Framework 事务是一个有价值的抽象,即使你使用 JTA 也是如此。您可以测试 事务代码比直接使用 JTA 要容易得多。PlatformTransactionManagerPlatformTransactionManager

同样,根据 Spring 的理念,可以抛出 通过接口的任何方法都未选中( 是,它扩展了类)。交易基础设施 失败几乎总是致命的。在极少数情况下,应用程序代码实际上可以 从事务失败中恢复,应用程序开发人员仍然可以选择捕获 并处理 .突出的一点是,开发人员不会被迫这样做。TransactionExceptionPlatformTransactionManagerjava.lang.RuntimeExceptionTransactionException

该方法返回一个对象,具体取决于参数。返回的可能表示 新交易或可以表示现有交易,如果匹配交易 存在于当前调用堆栈中。在后一种情况下,这意味着,与 Java EE 事务上下文中,a 与 执行。getTransaction(..)TransactionStatusTransactionDefinitionTransactionStatusTransactionStatus

从 Spring Framework 5.2 开始,Spring 还为 使用响应式类型或 Kotlin 协程的反应式应用程序。以下 列表显示了由以下定义的交易策略:org.springframework.transaction.ReactiveTransactionManager

public interface ReactiveTransactionManager extends TransactionManager {

    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;

    Mono<Void> commit(ReactiveTransaction status) throws TransactionException;

    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

反应式事务管理器主要是一个服务提供者接口(SPI), 尽管您可以从 应用程序代码。因为是一个接口,所以它可以很容易地 必要时嘲笑或存根。ReactiveTransactionManager

该接口指定:TransactionDefinition

  • 传播:通常,事务范围内的所有代码都在 该交易。但是,如果出现以下情况,则可以指定行为 当事务上下文已存在时,将运行事务方法。为 例如,代码可以继续在现有事务中运行(常见情况),或者 可以暂停现有事务并创建新事务。春天 提供 EJB CMT 中熟悉的所有事务传播选项。阅读 关于 Spring 中事务传播的语义,请参阅事务传播

  • 隔离:此事务与其他事务的工作隔离的程度 交易。例如,此事务能否看到来自其他事务的未提交写入 交易?

  • 超时:此事务在超时并自动回滚之前运行多长时间 由底层交易基础设施。

  • 只读状态:当代码读取但 不修改数据。只读事务在某些方面可能是一个有用的优化 情况,例如当您使用 Hibernate 时。

这些设置反映了标准的事务概念。如有必要,请参阅参考资料 讨论事务隔离级别和其他核心事务概念。 理解这些概念对于使用 Spring Framework 或任何 事务管理解决方案。

该接口为事务代码提供了一种简单的方法 控制交易执行,查询交易状态。概念应该是 熟悉,因为它们对所有事务 API 都是通用的。以下列表显示了该接口:TransactionStatusTransactionStatus

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

    @Override
    boolean isNewTransaction();

    boolean hasSavepoint();

    @Override
    void setRollbackOnly();

    @Override
    boolean isRollbackOnly();

    void flush();

    @Override
    boolean isCompleted();
}

无论您选择声明式还是程序化事务管理,都可以在 Spring,定义正确的实现是绝对必要的。 通常通过依赖关系注入来定义此实现。TransactionManager

TransactionManager实现通常需要了解 它们的工作原理:JDBC、JTA、Hibernate 等。以下示例演示如何 定义本地实现(在本例中,使用 plain JDBC。PlatformTransactionManager

您可以通过创建类似于以下内容的 Bean 来定义 JDBC:DataSource

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

然后,相关的 Bean 定义具有对该定义的引用。它应类似于以下示例:PlatformTransactionManagerDataSource

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

如果在 Java EE 容器中使用 JTA,则使用容器 ,获得 通过 JNDI,结合 Spring 的 .以下示例 显示了 JTA 和 JNDI 查找版本的样子:DataSourceJtaTransactionManager

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://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>

不需要知道(或任何其他 特定资源),因为它使用容器的全局事务管理 基础设施。JtaTransactionManagerDataSource

前面的 Bean 定义使用标记 从命名空间。有关更多信息,请参阅 JEE 架构dataSource<jndi-lookup/>jee
如果使用 JTA,则无论使用事务管理器定义,都应相同 您使用的数据访问技术,无论是 JDBC、Hibernate JPA 还是任何其他受支持 科技。这是因为JTA事务是全局事务,这 可以登记任何事务性资源。

在所有 Spring 事务设置中,应用程序代码不需要更改。您可以更改 如何仅通过更改配置来管理事务,即使该更改意味着 从本地交易转向全球交易,反之亦然。

1.2.1. Hibernate 事务设置

您还可以轻松使用 Hibernate 本地事务,如以下示例所示。 在这种情况下,您需要定义一个 Hibernate ,您的 应用程序代码可用于获取 Hibernate 实例。LocalSessionFactoryBeanSession

Bean 定义类似于前面所示的本地 JDBC 示例 因此,以下示例中未显示。DataSource

如果(由任何非 JTA 事务管理器使用)通过 JNDI 并由 Java EE 容器管理,它应该是非事务性的,因为 Spring Framework(而不是 Java EE 容器)管理事务。DataSource

在本例中,Bean 属于 Type 类型。在 与需要引用 一样,需要引用 。以下 示例声明和 bean:txManagerHibernateTransactionManagerDataSourceTransactionManagerDataSourceHibernateTransactionManagerSessionFactorysessionFactorytxManager

<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 容器管理的 JTA 事务,那么应该使用 与前面的JDBC的JTA示例相同,如下所示 示例显示。另外,建议让 Hibernate 通过其 事务协调器及其连接释放模式配置:JtaTransactionManager

<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}
            hibernate.transaction.coordinator_class=jta
            hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
        </value>
    </property>
</bean>

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

或者,您可以将 传递给 your 以强制执行相同的默认值:JtaTransactionManagerLocalSessionFactoryBean

<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>
    <property name="jtaTransactionManager" ref="txManager"/>
</bean>

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

1.3. 将资源与事务同步

如何创建不同的事务管理器以及它们如何链接到相关资源 需要同步到事务(例如,到 JDBC 、 到 Hibernate 、 等等)现在应该很清楚了。本节介绍应用程序如何编写代码 (直接或间接地,通过使用持久性 API,例如 JDBC、Hibernate 或 JPA) 确保正确创建、重用和清理这些资源。该部分 还讨论了如何(可选)通过 相关。DataSourceTransactionManagerDataSourceHibernateTransactionManagerSessionFactoryTransactionManager

1.3.1. 高级同步方法

首选方法是使用 Spring 最高级别的基于模板的持久性 集成 API,或者将本机 ORM API 与事务感知工厂 Bean 一起使用,或者 用于管理本机资源工厂的代理。这些交易感知解决方案 内部处理资源创建和重用、清理、可选事务 资源同步和异常映射。因此,用户数据访问代码确实如此 不必解决这些任务,但可以完全专注于非样板 持久性逻辑。通常,您使用本机 ORM API 或采用模板方法 使用 .这些解决方案将在后续中详细介绍 部分。JdbcTemplate

1.3.2. 低级同步方法

诸如(用于 JDBC)、(用于 JPA)、(用于 Hibernate)等类存在于较低级别。当您需要 应用程序代码,用于直接处理本机持久性 API 的资源类型, 您可以使用这些类来确保获得正确的 Spring Framework 托管实例, 事务是(可选)同步的,流程中发生的异常是 正确映射到一致的 API。DataSourceUtilsEntityManagerFactoryUtilsSessionFactoryUtils

例如,在JDBC的情况下,而不是传统的JDBC方法调用 上的方法,可以改用 Spring 的类,如下所示:getConnection()DataSourceorg.springframework.jdbc.datasource.DataSourceUtils

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有事务已经有一个同步(链接)到它的连接,则 返回实例。否则,方法调用将触发新 连接,(可选)同步到任何现有事务并建立 可在同一事务中重复使用。如前所述,任何都包装在 Spring Framework 中,一个 Spring Framework 的未检查类型的层次结构。这种方法 为您提供的信息比从 和 确保跨数据库甚至跨不同持久性技术的可移植性。SQLExceptionCannotGetJdbcConnectionExceptionDataAccessExceptionSQLException

这种方法在没有 Spring 事务管理(事务 synchronization 是可选的),因此无论您是否使用 Spring for 事务管理。

当然,一旦你使用了 Spring 的 JDBC 支持、JPA 支持或 Hibernate 支持, 您通常不喜欢使用或其他帮助程序类, 因为你比直接通过Spring抽象工作要快乐得多 与相关的 API。例如,如果使用 Spring 或包来简化 JDBC 的使用,则会发生正确的连接检索 在幕后,您无需编写任何特殊代码。DataSourceUtilsJdbcTemplatejdbc.object

1.3.3.TransactionAwareDataSourceProxy

在最底层存在着类。这是一个 proxy for a target ,它包装目标以增加对 Spring 管理的事务。在这方面,它类似于 事务性JNDI ,由Java EE服务器提供。TransactionAwareDataSourceProxyDataSourceDataSourceDataSource

您几乎永远不需要或想要使用此类,除非存在此类 必须调用代码并传递标准 JDBC 接口实现。在 在这种情况下,此代码可能是可用的,但正在参与 Spring 托管 交易。可以使用更高级别的代码编写新代码 前面提到的抽象。DataSource

1.4. 声明式事务管理

大多数 Spring Framework 用户选择声明式事务管理。此选项具有 对应用程序代码的影响最小,因此最符合 非侵入式轻型容器。

Spring Framework 的声明式事务管理是通过 Spring 实现的 面向方面的编程 (AOP)。但是,随着事务方面代码的到来 与 Spring Framework 发行版一起使用,并且可以以样板方式使用 AOP 通常不必理解概念即可有效使用此代码。

Spring Framework 的声明式事务管理类似于 EJB CMT,因为 您可以指定事务行为(或缺少事务行为),直至单个方法级别。 如果出现以下情况,则可以在事务上下文中进行调用 必要。两种类型的事务管理之间的区别是:setRollbackOnly()

  • 与 JTA 绑定的 EJB CMT 不同,Spring Framework 的声明性事务 管理适用于任何环境。它可以处理 JTA 事务或本地 使用 JDBC、JPA 或 Hibernate 通过调整配置来处理事务 文件。

  • 您可以将 Spring Framework 声明式事务管理应用于任何类, 而不仅仅是像 EJB 这样的特殊类。

  • Spring Framework 提供了声明式回滚规则,这是一个没有 EJB 的功能 等效。提供了对回滚规则的编程和声明性支持。

  • Spring Framework 允许您使用 AOP 自定义事务行为。 例如,您可以在事务回滚的情况下插入自定义行为。你 还可以添加任意建议以及交易建议。使用 EJB CMT,您可以 不能影响容器的事务管理,但 除外。setRollbackOnly()

  • Spring Framework 不支持跨 远程调用,就像高端应用程序服务器一样。如果您需要此功能,我们 建议使用 EJB。但是,在使用此类功能之前,请仔细考虑, 因为,通常情况下,人们不希望事务跨越远程调用。

回滚规则的概念很重要。它们允许您指定哪些例外 (和可抛出物)应导致自动回滚。您可以在 配置,而不是 Java 代码。所以,虽然你仍然可以打电话 要回滚当前事务的对象,最常见的是 可以指定必须始终导致回滚的规则。这 此选项的显著优点是业务对象不依赖于 交易基础设施。例如,它们通常不需要导入 Spring 事务 API 或其他 Spring API。setRollbackOnly()TransactionStatusMyApplicationException

尽管 EJB 容器的缺省行为会自动回滚 系统异常(通常是运行时异常),EJB CMT 不会回滚 应用程序异常(即已检查异常)时自动执行事务 除 )。虽然 Spring 的默认行为 声明式事务管理遵循 EJB 约定(回滚仅自动回滚) 在未经检查的异常中),自定义此行为通常很有用。java.rmi.RemoteException

1.4.1. 理解 Spring Framework 的声明式事务实现

仅仅告诉你用注解来注释你的类是不够的,添加到你的配置中, 并希望您了解这一切是如何运作的。为了提供更深入的理解,这 部分解释了 Spring Framework 声明式事务的内部工作原理 与事务相关的问题上下文中的基础设施。@Transactional@EnableTransactionManagement

关于 Spring Framework 的声明式,需要掌握的最重要的概念 事务支持是通过 AOP 代理启用的,并且事务 建议由元数据(目前基于XML或注释)驱动。AOP的组合 事务元数据生成一个使用 结合适当的实现来推动交易 围绕方法调用。TransactionInterceptorTransactionManager

Spring Framework 为 命令式和反应式编程模型。拦截器检测所需的风味 通过检查方法返回类型进行事务管理。返回响应式的方法 诸如 或 Kotlin(或其子类型)之类的类型符合响应式条件 事务管理。所有其他返回类型,包括使用 命令式事务管理。TransactionInterceptorPublisherFlowvoid

事务管理风格会影响需要哪个事务管理器。祈使的 事务需要 ,而反应式事务使用实现。PlatformTransactionManagerReactiveTransactionManager

@Transactional通常与由 管理的线程绑定事务一起使用,将事务公开给 当前执行线程。注意:这不会传播到新启动的线程 在方法中。PlatformTransactionManager

使用 Reactor 上下文管理的反应式事务 而不是线程本地属性。因此,所有参与的数据访问 操作需要在同一反应式管道的相同 Reactor 上下文中执行。ReactiveTransactionManager

下图显示了在事务代理上调用方法的概念视图:

德克萨斯州

1.4.2. 声明式事务实现示例

请考虑以下接口及其伴随的实现。此示例使用 和 类作为占位符,以便您可以专注于事务 在不关注特定域模型的情况下使用。就此示例而言, 该类在每个实现方法的主体中抛出实例这一事实很好。该行为可让您看到 正在创建的事务,然后回滚以响应实例。以下列表显示了该接口:FooBarDefaultFooServiceUnsupportedOperationExceptionUnsupportedOperationExceptionFooService

爪哇岛
Kotlin
// 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);

}

以下示例显示了上述接口的实现:

爪哇岛
Kotlin
package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}

假设接口的前两个方法 和 必须在只读事务的上下文中运行 语义和其他方法,以及 ,必须 在具有读写语义的事务上下文中运行。以下 在接下来的几段中将详细解释配置:FooServicegetFoo(String)getFoo(String, String)insertFoo(Foo)updateFoo(Foo)

<!-- 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 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 TransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

检查上述配置。它假设您要创建一个服务对象, 豆,事务性。要应用的事务语义是封装的 在定义中。该定义为“所有方法 从 are 开始,在只读事务的上下文中运行,并且所有 其他方法是使用默认的事务语义运行”。标记的属性设置为将驱动事务的 Bean 的名称(在本例中为 Bean)。fooService<tx:advice/><tx:advice/>gettransaction-manager<tx:advice/>TransactionManagertxManager

您可以在事务性建议中省略该属性 () 如果 Bean 名称是您想要的 wire in 的名称为 。如果 bean 要连接具有任何其他名称,则必须显式使用该属性,如前面的示例所示。transaction-manager<tx:advice/>TransactionManagertransactionManagerTransactionManagertransaction-manager

该定义确保 Bean 定义的事务性建议在程序中的适当点运行。首先,定义一个 与接口中定义的任何操作的执行相匹配的切入点 ().然后,使用 顾问。结果表明,在执行 , 定义的建议是 RUN。<aop:config/>txAdviceFooServicefooServiceOperationtxAdvicefooServiceOperationtxAdvice

元素中定义的表达式是 AspectJ 切入点 表达。有关切入点的更多详细信息,请参阅 AOP 部分 Spring 中的表达式。<aop:pointcut/>

一个常见的要求是使整个服务层具有事务性。最好的方法 这样做是为了更改切入点表达式以匹配 服务层。以下示例演示如何执行此操作:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
在前面的示例中,假定定义了所有服务接口 在包装中。有关更多详细信息,请参阅 AOP 部分x.y.service

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

前面所示的配置用于围绕对象创建事务代理 这是从 Bean 定义创建的。代理配置了 事务性建议,以便在代理上调用适当的方法时, 事务已启动、挂起、标记为只读等,具体取决于 与该方法关联的事务配置。请考虑以下程序 该测试驱动前面所示的配置:fooService

爪哇岛
Kotlin
public final class Boot {

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

运行上述程序的输出应类似于以下内容(Log4J 为清楚起见,类的方法抛出的输出和堆栈跟踪已被截断):UnsupportedOperationExceptioninsertFoo(..)DefaultFooService

<!-- 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 [org.apache.commons.dbcp.PoolableConnection@a53de4] 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 [org.apache.commons.dbcp.PoolableConnection@a53de4]
[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)

若要使用响应式事务管理,代码必须使用响应式类型。

Spring Framework 使用 来确定一个方法是否 返回类型为反应式。ReactiveAdapterRegistry

下面的列表显示了以前使用的 的修改版本,但 这一次,代码使用响应式类型:FooService

爪哇岛
Kotlin
// the reactive service interface that we want to make transactional

package x.y.service;

public interface FooService {

    Flux<Foo> getFoo(String fooName);

    Publisher<Foo> getFoo(String fooName, String barName);

    Mono<Void> insertFoo(Foo foo);

    Mono<Void> updateFoo(Foo foo);

}

以下示例显示了上述接口的实现:

爪哇岛
Kotlin
package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Flux<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Publisher<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}

命令式事务管理与响应式事务管理共享相同的事务语义 边界和事务属性定义。命令式的主要区别 而反应性事务是后者的递延性质。 使用事务运算符修饰返回的反应式类型以开始和清理 交易。因此,调用事务性反应式方法会延迟实际 事务管理到激活反应式处理的订阅类型 类型。TransactionInterceptor

反应式事务管理的另一个方面与数据转义有关,这是一个 编程模型的自然结果。

命令式事务的方法返回值是从事务方法返回的 成功终止方法后,使部分计算的结果不会逃逸 方法闭包。

响应式事务方法返回一个响应式包装器类型,该类型表示 计算序列以及开始和完成计算的承诺。

A 可以在事务正在进行但不一定完成时发出数据。 因此,依赖于成功完成整个事务的方法需要 以确保完成和缓冲调用代码的结果。Publisher

1.4.3. 回滚声明式事务

上一节概述了如何指定事务设置的基础知识 类,通常是服务层类,在应用程序中以声明方式存在。本节 描述如何在简单的声明式中控制事务的回滚 XML配置中的时尚。有关以声明方式控制回滚语义的详细信息 对于注释,请参阅@Transactional设置@Transactional

向 Spring Framework 的事务基础结构指示的推荐方式 要回滚事务的工作是抛出一个 from 代码 当前正在事务上下文中执行。Spring Framework 的 事务基础结构代码在冒泡时捕获任何未经处理的代码 调用堆栈,并确定是否将事务标记为回滚。ExceptionException

在其默认配置中,Spring Framework 的事务基础结构代码 仅在运行时未检查的异常情况下将事务标记为回滚。 也就是说,当引发的异常是 的实例或子类时。 (默认情况下,实例也会导致回滚)。已检查的异常 从事务方法引发不会导致默认的回滚 配置。RuntimeExceptionError

您可以准确配置哪些类型将事务标记为回滚, 通过指定回滚规则来包括选中的异常。Exception

回滚规则

回滚规则确定在给定异常时是否应回滚事务 抛出,规则基于模式。模式可以是完全限定的类 name 或异常类型的完全限定类名的子字符串(必须是 的子类 ),目前不支持通配符。例如,值 or 将匹配及其子类。Throwable"javax.servlet.ServletException""ServletException"javax.servlet.ServletException

回滚规则可以通过 和 属性在 XML 中配置,这些属性允许将模式指定为字符串。使用@Transactional时,回滚规则可能 通过 / 和 / 属性进行配置,这些属性允许模式 分别指定为引用或字符串。当异常类型为 指定为类引用,其完全限定名称将用作模式。 因此,是等价的 自。rollback-forno-rollback-forrollbackFornoRollbackForrollbackForClassNamenoRollbackForClassNameClass@Transactional(rollbackFor = example.CustomException.class)@Transactional(rollbackForClassName = "example.CustomException")

您必须仔细考虑模式的具体程度以及是否包含包 信息(非强制性)。例如,将匹配近 任何事情,并且可能会隐藏其他规则。 如果打算为所有选中的异常定义规则,那将是正确的。拥有更多独特性 异常名称,例如可能不需要使用 异常模式的完全限定类名。"Exception""java.lang.Exception""Exception""BaseBusinessException"

此外,回滚规则可能会导致类似名称的意外匹配 异常和嵌套类。这是因为抛出的异常是 如果引发异常的名称,则视为给定回滚规则的匹配项 包含为回滚规则配置的异常模式。例如,给定一个 配置为匹配的规则,该规则将匹配 名为异常(与同一包中的异常,但带有附加后缀)或名为异常(声明为嵌套类的异常) 在 )。com.example.CustomExceptioncom.example.CustomExceptionV2CustomExceptioncom.example.CustomException$AnotherExceptionCustomException

以下 XML 代码片段演示了如何为选中的 特定于应用程序的类型,通过属性提供异常模式Exceptionrollback-for

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

如果不希望在抛出异常时回滚事务,也可以 指定“无回滚”规则。以下示例告诉 Spring Framework 的 事务基础设施,即使在面对 未处理 :InstrumentNotFoundException

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

当 Spring Framework 的事务基础结构捕获异常并查阅时 配置的回滚规则,用于确定是否将事务标记为回滚, 最强的匹配规则获胜。因此,在以下配置的情况下,任何 导致回滚的异常 伴随交易:InstrumentNotFoundException

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

还可以以编程方式指示所需的回滚。虽然简单,但这个过程 具有很强的侵入性,并将您的代码与 Spring Framework 的事务紧密耦合 基础设施。下面的示例演示如何以编程方式指示必需的 反转:

爪哇岛
Kotlin
public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

强烈建议您使用声明性方法进行回滚(如果有的话) 可能。如果您绝对需要,可以使用编程回滚,但它 使用与实现干净的基于 POJO 的架构背道而驰。

1.4.4. 为不同的 Bean 配置不同的事务语义

请考虑以下场景:您拥有多个服务层对象,并且希望 对它们中的每一个应用完全不同的事务配置。你可以这样做 通过定义具有不同属性值的不同元素。<aop:advisor/>pointcutadvice-ref

作为比较点,首先假设所有服务层类都是 在根包中定义。使所有 Bean 都是类的实例 在该包(或子包)中定义,并且名称以 have 结尾 默认事务配置,您可以编写以下内容:x.y.serviceService

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

    <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 TransactionManager omitted... -->

</beans>

以下示例显示了如何配置两个完全不同的 bean 事务设置:

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

    <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 TransactionManager omitted... -->

</beans>

1.4.5. <tx:advice/>设置

本部分总结了可以使用以下命令指定的各种事务设置 标记。默认设置为:<tx:advice/><tx:advice/>

  • 传播设置为REQUIRED.

  • 隔离级别为DEFAULT.

  • 事务是可读写的。

  • 事务超时默认为基础事务的默认超时 如果不支持超时,则为 system 或 none。

  • 任何触发器都会回滚,而任何选中的都不会。RuntimeExceptionException

您可以更改这些默认设置。下表总结了标记的各种属性 嵌套在 和 标记中:<tx:method/><tx:advice/><tx:attributes/>

Table 1. <tx:method/> settings
属性 必填? 违约 描述

name

是的

要与事务属性关联的方法名称。这 通配符 (*) 字符可用于关联相同的事务属性 具有多种方法(例如、、、等)的设置 第四)。get*handle*on*Event

propagation

REQUIRED

事务传播行为。

isolation

DEFAULT

事务隔离级别。仅适用于 或 的传播设置。REQUIREDREQUIRES_NEW

timeout

-1

事务超时(秒)。仅适用于传播或 .REQUIREDREQUIRES_NEW

read-only

读写事务与只读事务。仅适用于 或 。REQUIREDREQUIRES_NEW

rollback-for

以逗号分隔的触发回滚的实例列表。例如。Exceptioncom.foo.MyBusinessException,ServletException

no-rollback-for

以逗号分隔的不触发回滚的实例列表。例如。Exceptioncom.foo.MyBusinessException,ServletException

1.4.6. 使用@Transactional

除了基于 XML 的声明性事务配置方法外,您还可以 使用基于注释的方法。直接在 Java 中声明事务语义 源代码使声明更接近受影响的代码。没有太多 过度耦合的危险,因为用于事务的代码是 无论如何,几乎总是以这种方式部署。

标准注解也支持作为 直接替换 Spring 自己的注解。请参考 JTA 1.2 文档 了解更多详情。javax.transaction.Transactional

使用注释提供的易用性是最好的 用一个示例进行说明,在下面的文本中对此进行了解释。 请考虑以下类定义:@Transactional

爪哇岛
Kotlin
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}

如上所述,在类级别使用,注释指示 声明类(及其子类)。或者,每种方法都可以是 单独注释。请参阅方法可见性和@Transactional 关于 Spring 认为哪些方法具有事务性方法的更多细节。请注意,类级别 注释不适用于类层次结构中上游的祖先类;在这种情况下, 继承的方法需要在本地重新声明才能参与 子类级注解。

当 POJO 类(如上面的类)在 Spring 上下文中被定义为 bean 时, 您可以通过类中的注释使 Bean 实例具有事务性。有关完整的详细信息,请参阅 javadoc@EnableTransactionManagement@Configuration

在 XML 配置中,标记提供了类似的便利:<tx:annotation-driven/>

<!-- 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 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 -->
    <!-- a TransactionManager is still required -->
    <tx:annotation-driven transaction-manager="txManager"/> (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 实例成为事务性的行。
如果要连接的 的 Bean 名称具有 name ,则可以省略标记中的属性。如果要依赖注入的 bean 具有任何其他名称,则必须使用该属性,如 前面的示例。transaction-manager<tx:annotation-driven/>TransactionManagertransactionManagerTransactionManagertransaction-manager

响应式事务方法使用响应式返回类型,而不是命令式 编程安排如下表所示:

爪哇岛
Kotlin
// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    @Override
    public Publisher<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Mono<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}

请注意,对于退回的 - 在以下方面有特殊的注意事项 Reactive Streams 取消信号。请参阅“取消信号”部分 “使用 TransactionalOperator”了解更多详情。Publisher

方法可见性和@Transactional

当您将事务代理与 Spring 的标准配置一起使用时,您应该应用 仅对具有可见性的方法进行注释。如果你这样做 annotate 、 或 package-visible 方法与注解,不会引发错误,但带注解的方法不会显示配置的 事务设置。如果需要对非公共方法进行注释,请考虑 以下段落适用于基于类的代理,或考虑使用 AspectJ 编译时或 加载时编织(稍后描述)。@Transactionalpublicprotectedprivate@Transactional

在课堂上使用时,或 对于基于类的代理,也可以通过以下方式使包可见方法成为事务性方法 注册自定义 Bean,如以下示例所示。 但请注意,基于接口的代理中的事务方法必须始终在代理接口中定义。@EnableTransactionManagement@ConfigurationprotectedtransactionAttributeSourcepublic

/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to false to enable support for
 * protected and package-private @Transactional methods in
 * class-based proxies.
 *
 * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
 */
@Bean
TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

Spring TestContext 框架通过以下方式支持非私有测试方法 违约。请参阅测试中的事务管理 章节。@Transactional

您可以将注释应用于接口定义、方法 在接口、类定义或类上的方法上。但是, 仅仅存在注解并不足以激活 事务行为。注释只是可以 被一些运行时基础结构使用,这些基础结构是 -aware 和 可以使用元数据来配置具有事务行为的相应 Bean。 在前面的示例中,该元素在 事务行为。@Transactional@Transactional@Transactional@Transactional<tx:annotation-driven/>

Spring 团队建议你只注解具体的类(和 具体类)与注解相反。 您当然可以将注释放在接口(或接口)上 方法),但这只有在使用基于接口时才像您预期的那样工作 代理。Java 注解不是从接口继承的,这一事实意味着, 如果您使用基于类的代理 () 或基于 weaving 的 方面(),代理无法识别事务设置 和编织基础结构,并且对象未包装在事务代理中。@Transactional@Transactionalproxy-target-class="true"mode="aspectj"
在代理模式(默认模式)中,只有外部方法调用通过 代理被截获。这意味着自调用(实际上,是 目标对象调用目标对象的另一个方法)不会导致实际 运行时的事务,即使调用的方法标有 。也 必须完全初始化代理才能提供预期的行为,因此不应 在初始化代码(例如,在方法中)依赖此功能。@Transactional@PostConstruct

如果出现以下情况,请考虑使用 AspectJ 模式(请参阅下表中的属性) 期望自调用也与事务一起包装。在这种情况下,有 首先没有代理。相反,目标类是编织的(即它的字节码 已修改)以支持任何类型的方法的运行时行为。mode@Transactional

Table 2. Annotation driven transaction settings
XML 属性 注释属性 违约 描述

transaction-manager

N/A(请参阅 TransactionManagementConfigurer javadoc)

transactionManager

要使用的事务管理器的名称。仅当事务名称为时才需要 manager 不是 ,如前面的示例所示。transactionManager

mode

mode

proxy

默认模式 () 使用 Spring 的 AOP 处理要代理的带注释的 bean 框架(如前所述,遵循代理语义,适用于方法调用 仅通过代理进入)。替代模式 () 将 影响 Spring 的 AspectJ 事务方面的类,修改目标类 应用于任何类型的方法调用的字节码。AspectJ 编织需要在类路径中以及具有加载时编织(或编译时 weaving) 启用。(有关如何设置加载时编织的详细信息,请参阅 Spring 配置proxyaspectjspring-aspects.jar

proxy-target-class

proxyTargetClass

false

仅适用于模式。控制创建的事务代理类型 对于使用注释进行注释的类。如果该属性设置为 ,则创建基于类的代理。 如果省略了 is 或 if 该属性,则为标准 JDK 创建基于接口的代理。(有关不同代理类型的详细检查,请参阅代理机制proxy@Transactionalproxy-target-classtrueproxy-target-classfalse

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于用 批注了 的 Bean 的事务建议的顺序。(有关AOP排序相关规则的更多信息 建议,请参阅建议订购。 没有指定的排序意味着 AOP 子系统确定建议的顺序。@Transactional

处理注解的默认建议模式是 , 它只允许通过代理拦截呼叫。本地呼叫 同一类不能以这种方式被拦截。对于更高级的拦截模式, 请考虑将模式与编译时或加载时编织结合使用。@Transactionalproxyaspectj
该属性控制事务代理的类型 为使用批注进行批注的类创建。如果设置为 ,则创建基于类的代理。如果是或省略属性,则为标准 JDK 创建基于接口的代理。(有关不同代理类型的讨论,请参阅代理机制proxy-target-class@Transactionalproxy-target-classtrueproxy-target-classfalse
@EnableTransactionManagement并仅在定义它们的同一应用程序上下文中查找 bean。 这意味着,如果将注释驱动的配置放在 for a 中,则它只会检查控制器中的 bean 而不是在您的服务中。有关详细信息,请参阅 MVC<tx:annotation-driven/>@TransactionalWebApplicationContextDispatcherServlet@Transactional

在评估事务设置时,最派生的位置优先 对于一种方法。在以下示例中,该类是 使用只读事务的设置在类级别进行批注,但同一类中方法的批注需要 优先于在类级别定义的事务设置。DefaultFooService@TransactionalupdateFoo(Foo)

爪哇岛
Kotlin
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // ...
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // ...
    }
}
@Transactional设置

注解是元数据,用于指定接口、类、 或方法必须具有事务语义(例如,“启动一个全新的只读 事务,暂停任何现有事务“)。 默认设置如下:@Transactional@Transactional

  • 传播设置为PROPAGATION_REQUIRED.

  • 隔离级别为ISOLATION_DEFAULT.

  • 事务是可读写的。

  • 事务超时默认为基础事务的默认超时 系统,如果不支持超时,则为无。

  • Any or 触发回滚,任何选中的 不。RuntimeExceptionErrorException

您可以更改这些默认设置。下表总结了各种 注解的属性:@Transactional

Table 3. @Transactional Settings
财产 类型 描述

价值

String

可选限定符,指定要使用的事务管理器。

transactionManager

String

的别名。value

label

标签数组,用于向交易添加富有表现力的描述。String

事务管理器可以评估标签,以将特定于实现的行为与实际事务相关联。

增殖

enum:Propagation

可选的传播设置。

isolation

enum:Isolation

可选隔离级别。仅适用于 或 的传播值。REQUIREDREQUIRES_NEW

timeout

int(以秒为单位)

可选事务超时。仅适用于 或 的传播值。REQUIREDREQUIRES_NEW

timeoutString

String(以秒为单位)

将“以秒为单位”指定为值的替代方法,例如,作为占位符。timeoutString

readOnly

boolean

读写事务与只读事务。仅适用于 或 的值。REQUIREDREQUIRES_NEW

rollbackFor

对象数组,必须派生自ClassThrowable.

必须导致回滚的异常类型的可选数组。

rollbackForClassName

异常名称模式的数组。

必须导致回滚的异常名称模式的可选数组。

noRollbackFor

对象数组,必须派生自ClassThrowable.

不得导致回滚的异常类型的可选数组。

noRollbackForClassName

异常名称模式的数组。

不得导致回滚的异常名称模式的可选数组。

有关更多详细信息,请参阅回滚规则 关于回滚规则的语义、模式和有关可能的无意警告 比赛。

目前,您无法明确控制事务的名称,其中“name” 指事务监视器中显示的事务名称(如果适用) (例如,WebLogic 的事务监视器)和日志记录输出。对于声明式 事务,事务名称始终是完全限定的类名 + + 事务建议类的方法名。例如,如果类的方法启动了一个事务,则 交易名称为:。.handlePayment(..)BusinessServicecom.example.BusinessService.handlePayment

多个事务管理器@Transactional

大多数 Spring 应用程序只需要一个事务管理器,但可能有 您希望在单个事务管理器中包含多个独立事务管理器的情况 应用。您可以使用批注的 or 属性来选择性地指定要使用的标识。这可以是 Bean 名称,也可以是限定符值 事务管理器 Bean。例如,使用限定符表示法,您可以 将以下 Java 代码与以下事务管理器 Bean 声明相结合 在应用程序上下文中:valuetransactionManager@TransactionalTransactionManager

爪哇岛
Kotlin
public class TransactionalService {

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

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

    @Transactional("reactive-account")
    public Mono<Void> doSomethingReactive() { ... }
}

下面的清单显示了 Bean 声明:

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

    <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
        ...
        <qualifier value="reactive-account"/>
    </bean>

在这种情况下,各个方法在单独的 事务管理器,由 、 和限定符区分。缺省目标 Bean 名称 , 如果未找到特定限定的 bean,则仍使用。TransactionalServiceorderaccountreactive-account<tx:annotation-driven>transactionManagerTransactionManager

自定义组合批注

如果您发现在许多不同的 on 上重复使用相同的属性 方法,Spring 的元注解支持可以让你 为您的特定用例定义自定义组合注释。例如,考虑 以下注释定义:@Transactional

爪哇岛
Kotlin
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

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

前面的注解让我们编写上一节中的示例,如下所示:

爪哇岛
Kotlin
public class TransactionalService {

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

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

在前面的示例中,我们使用语法来定义事务管理器限定符 和事务标签,但我们也可以包括传播行为, 回滚规则、超时和其他功能。

1.4.7. 事务传播

本节描述了 Spring 中事务传播的一些语义。注意 本节不是对事务传播的适当介绍。相反,它 详细介绍了 Spring 中有关事务传播的一些语义。

在 Spring 管理的事务中,请注意物理事务和 逻辑事务,以及传播设置如何应用于此差异。

理解PROPAGATION_REQUIRED
需要 TX 道具

PROPAGATION_REQUIRED强制执行物理事务,在本地为当前 范围,如果尚不存在交易或参与现有的“外部”交易 为更大的范围定义。这是常见调用堆栈安排中的良好默认值 在同一线程中(例如,委托给多个存储库方法的服务外观 其中所有底层资源都必须参与服务级别事务)。

默认情况下,参与事务加入外部作用域的特征, 静默忽略本地隔离级别、超时值或只读标志(如果有)。 考虑将标志切换到交易 经理,如果您希望在参与时拒绝隔离级别声明 具有不同隔离级别的现有事务。这种非宽松模式也 拒绝只读不匹配(即尝试参与的内部读写事务) 在只读外部作用域中)。validateExistingTransactionstrue

当传播设置为 时,逻辑事务范围 为应用设置的每个方法创建。每个这样的逻辑 事务范围可以单独确定仅回滚状态,具有 事务范围在逻辑上独立于内部事务范围。 在标准行为的情况下,所有这些作用域都是 映射到同一物理事务。因此,在内部设置了一个仅回滚的标记 事务范围确实会影响外部事务实际提交的机会。PROPAGATION_REQUIREDPROPAGATION_REQUIRED

但是,在内部事务作用域设置仅回滚标记的情况下, 外部事务尚未决定回滚本身,因此回滚(静默 由内部事务范围触发)是意外的。此时会抛出相应的消息。这是预期行为,因此 永远不会误导事务的调用者假设提交是 当它真的不是时执行。因此,如果一个内部事务(其中外部调用方 is not aware) 以静默方式将事务标记为仅回滚,外部调用方仍 调用 commit。外部调用方需要接收一个 to 清楚地表明执行的是回滚。UnexpectedRollbackExceptionUnexpectedRollbackException

理解PROPAGATION_REQUIRES_NEW
TX prop 需要新的

PROPAGATION_REQUIRES_NEW,与 相反,始终使用 每个受影响的交易范围都有独立的物理交易,从不 参与外部范围的现有事务。在这样的安排中, 基础资源事务是不同的,因此可以提交或回滚 独立地,外部事务不受内部事务回滚的影响 状态,并在完成后立即释放内部事务的锁。 这样独立的内部事务也可以声明自己的隔离级别、超时、 和只读设置,而不是继承外部事务的特征。PROPAGATION_REQUIRED

理解PROPAGATION_NESTED

PROPAGATION_NESTED使用具有多个保存点的单个物理事务 它可以回滚到。这种部分回滚让内部事务范围 触发其作用域的回滚,外部事务能够继续 尽管某些操作已回滚,但物理事务已回滚。此设置 通常映射到 JDBC 保存点,因此它仅适用于 JDBC 资源 交易。请参阅 Spring 的 DataSourceTransactionManager

1.4.8. 建议事务操作

假设您希望同时运行事务操作和一些基本的分析建议。 您如何在上下文中实现这一点?<tx:annotation-driven/>

调用该方法时,需要查看以下操作:updateFoo(Foo)

  • 配置的分析方面将启动。

  • 交易建议运行。

  • 建议对象上的方法将运行。

  • 事务提交。

  • 分析方面报告整个事务方法调用的确切持续时间。

本章不涉及任何非常详细的解释 AOP(除了它 适用于交易)。有关 AOP 的详细报道,请参阅 AOP 配置和 AOP。

以下代码显示了前面讨论的简单分析方面:

爪哇岛
Kotlin
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;
    }
}

建议的顺序 通过接口进行控制。有关建议订购的完整详细信息,请参阅建议订购Ordered

以下配置创建一个 bean,该 Bean 具有 profiling 和 按所需顺序应用于它的事务方面:fooService

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

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

    <!-- this is the aspect -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- run 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 runs 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>

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

下面的示例创建与前两个示例相同的设置,但使用纯 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

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

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- run 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.*(..))"/>
        <!-- runs after the profiling advice (cf. 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 TransactionManager here -->

</beans>

上述配置的结果是一个 bean,该 bean 具有 profiling 和 事务方面按该顺序应用于它。如果您需要分析建议 在进入和之前运行交易建议 交易建议 在出路时,您可以交换分析的价值 方面 Bean 的属性,使其高于交易建议的属性 订单价值。fooServiceorder

您可以以类似的方式配置其他方面。

1.4.9. 与 AspectJ 一起使用@Transactional

您还可以在 Spring 之外使用 Spring Framework 的支持 容器通过 AspectJ 方面。为此,首先要注释你的类 (以及可选的类方法)和注释, 然后将应用程序与文件中定义的应用程序链接(编织)。您还必须使用事务配置方面 经理。您可以使用 Spring Framework 的 IoC 容器来处理 依赖注入方面。配置事务的最简单方法 管理方面是使用元素并指定属性,如使用@Transactional中所述。因为 我们在这里重点介绍在 Spring 容器之外运行的应用程序,我们展示了 你如何以编程方式做到这一点。@Transactional@Transactionalorg.springframework.transaction.aspectj.AnnotationTransactionAspectspring-aspects.jar<tx:annotation-driven/>modeaspectj

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

以下示例演示如何创建事务管理器并配置事务管理器以使用它:AnnotationTransactionAspect

爪哇岛
Kotlin
// 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);
使用此方面时,必须对实现类(或方法 在该类中或两者中),而不是类实现的接口(如果有)。方面J 遵循 Java 的规则,即接口上的注解不会被继承。

类的注解指定默认事务语义 用于执行类中的任何公共方法。@Transactional

类中方法的注释将覆盖默认值 类注解(如果存在)给出的事务语义。您可以注释任何方法, 无论可见性如何。@Transactional

要将应用程序与 编织在一起,您必须构建 您的应用程序与 AspectJ (参见 AspectJ 开发 Guide)或使用加载时编织。请参阅加载时编织 Spring 框架中的 AspectJ,用于讨论使用 AspectJ 进行加载时编织。AnnotationTransactionAspect

1.5. 程序化事务管理

Spring Framework 提供了两种编程事务管理方法,方法是使用:

  • 或 .TransactionTemplateTransactionalOperator

  • 直接实现。TransactionManager

Spring 团队通常建议将 命令式流和反应式代码中的事务管理。 第二种方法类似于使用 JTA API,但有例外 处理不那么麻烦。TransactionTemplateTransactionalOperatorUserTransaction

1.5.1. 使用TransactionTemplate

采用与其他 Spring 模板相同的方法,例如 这。它使用回调方法(使应用程序代码不必 执行样板获取和释放事务资源),并导致 意图驱动的代码,因为你的代码只关注什么 你想做。TransactionTemplateJdbcTemplate

正如下面的示例所示,使用 绝对 将您与 Spring 的事务基础架构和 API 相结合。是否程序化 事务管理是否适合您的开发需求是您决定的 必须让自己。TransactionTemplate

必须在事务上下文中运行并显式使用 的应用程序代码类似于下一个示例。您,作为应用程序 开发人员,可以编写一个实现(通常表示为 匿名内部类),其中包含需要在上下文中运行的代码 交易。然后,可以将自定义的实例传递给 上公开的方法。以下示例演示如何执行此操作:TransactionTemplateTransactionCallbackTransactionCallbackexecute(..)TransactionTemplate

爪哇岛
Kotlin
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 runs in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果没有返回值,则可以使用方便的类 使用匿名类,如下所示:TransactionCallbackWithoutResult

爪哇岛
Kotlin
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

回调中的代码可以通过对提供的对象调用方法回滚事务,如下所示:setRollbackOnly()TransactionStatus

爪哇岛
Kotlin
transactionTemplate.execute(new TransactionCallbackWithoutResult() {

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

您可以指定事务设置(例如传播模式、隔离级别、 超时,依此类推)以编程方式或 配置。默认情况下,实例具有默认的事务设置。这 以下示例显示了事务设置的编程自定义 一个特定的TransactionTemplateTransactionTemplateTransactionTemplate:

爪哇岛
Kotlin
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...
    }
}

以下示例定义了一个带有一些自定义事务的 使用 Spring XML 配置进行设置:TransactionTemplate

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

然后,您可以根据需要将 注入任意数量的服务。sharedTransactionTemplate

最后,该类的实例是线程安全的,因为该实例 不要保持任何对话状态。 但是,实例确实如此 维护配置状态。因此,虽然许多类可以共享一个实例 的 ,如果一个类需要使用 不同的设置(例如,不同的隔离级别),您需要创建 两个不同的实例。TransactionTemplateTransactionTemplateTransactionTemplateTransactionTemplateTransactionTemplate

1.5.2. 使用TransactionalOperator

遵循类似于其他反应式的算子设计 运营商。它使用回调方法(将应用程序代码从执行 样板获取和释放事务资源),并生成代码 意图驱动,因为你的代码只关注你想做的事情。TransactionalOperator

正如下面的示例所示,使用 绝对 将您与 Spring 的事务基础架构和 API 相结合。是否程序化 事务管理是否适合您的开发需求是您有的决定 做你自己。TransactionalOperator

必须在事务上下文中运行并显式使用 类似于以下示例:TransactionalOperator

爪哇岛
Kotlin
public class SimpleService implements Service {

    // single TransactionalOperator shared amongst all methods in this instance
    private final TransactionalOperator transactionalOperator;

    // use constructor-injection to supply the ReactiveTransactionManager
    public SimpleService(ReactiveTransactionManager transactionManager) {
        this.transactionalOperator = TransactionalOperator.create(transactionManager);
    }

    public Mono<Object> someServiceMethod() {

        // the code in this method runs in a transactional context

        Mono<Object> update = updateOperation1();

        return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
    }
}

TransactionalOperator可以通过两种方式使用:

  • 使用 Project Reactor 类型 (mono.as(transactionalOperator::transactional))

  • 所有其他情况的回调样式 (transactionalOperator.execute(TransactionCallback<T>))

回调中的代码可以通过对提供的对象调用方法回滚事务,如下所示:setRollbackOnly()ReactiveTransaction

爪哇岛
Kotlin
transactionalOperator.execute(new TransactionCallback<>() {

    public Mono<Object> doInTransaction(ReactiveTransaction status) {
        return updateOperation1().then(updateOperation2)
                    .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
        }
    }
});
取消信号

在 Reactive Streams 中,a 可以取消其 并停止其 .Project Reactor 以及其他库(如 、、 等)中的操作员可以发出取消命令。没有办法 了解取消的原因,无论是由于错误还是仅仅由于缺乏 利息进一步消费。从 5.3 版开始,取消信号会导致回滚。 因此,重要的是要考虑事务下游使用的运算符。特别是在一个或其他多值的情况下, 必须使用完整输出才能完成事务。SubscriberSubscriptionPublishernext()take(long)timeout(Duration)PublisherFluxPublisher

指定事务设置

您可以指定事务设置(例如传播模式、隔离级别、 超时,依此类推)。默认情况下,实例具有默认事务设置。这 以下示例显示了特定TransactionalOperatorTransactionalOperatorTransactionalOperator:

爪哇岛
Kotlin
public class SimpleService implements Service {

    private final TransactionalOperator transactionalOperator;

    public SimpleService(ReactiveTransactionManager transactionManager) {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

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

        this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
    }
}

1.5.3. 使用TransactionManager

以下各节介绍命令式事务和反应式事务的编程用法 经理。

使用PlatformTransactionManager

对于命令式事务,您可以直接使用 a 来管理 交易。为此,请传递 u 的实现 通过 Bean 引用用于 Bean。然后,通过使用 和 对象,您可以启动事务、回滚和提交。这 以下示例演示如何执行此操作:org.springframework.transaction.PlatformTransactionManagerPlatformTransactionManagerTransactionDefinitionTransactionStatus

爪哇岛
Kotlin
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 {
    // put your business logic here
} catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);
使用ReactiveTransactionManager

在处理反应式事务时,您可以直接使用 交易。为此,请传递 u 的实现 通过 Bean 引用用于 Bean。然后,通过使用 和 对象,您可以启动事务、回滚和提交。这 以下示例演示如何执行此操作:org.springframework.transaction.ReactiveTransactionManagerReactiveTransactionManagerTransactionDefinitionReactiveTransaction

爪哇岛
Kotlin
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);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

    Mono<Object> tx = ...; // put your business logic here

    return tx.then(txManager.commit(status))
            .onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});

1.6. 在程序化事务管理和声明式事务管理之间进行选择

程序化事务管理通常是一个好主意,只有当你有一个小 事务操作数。例如,如果您有一个 Web 应用程序 仅某些更新操作需要事务,您可能不希望设置 使用 Spring 或任何其他技术的事务代理。在这种情况下,使用 可能是一个很好的方法。能够设置交易名称 显式也是只能使用编程方法才能完成的事情 到交易管理。TransactionTemplate

另一方面,如果您的应用程序具有许多事务操作, 声明式事务管理通常是值得的。它保持交易 管理脱离业务逻辑,配置难度不大。使用 Spring Framework,而不是 EJB CMT,声明式事务的配置成本 管理大大减少。

1.7. 事务绑定事件

从 Spring 4.2 开始,事件的侦听器可以绑定到事务的某个阶段。 典型示例是在事务成功完成时处理事件。 这样做可以让事件在当前 事务实际上对侦听器很重要。

您可以使用注释注册常规事件侦听器。 如果需要将其绑定到事务,请使用 . 执行此操作时,默认情况下,侦听器将绑定到事务的提交阶段。@EventListener@TransactionalEventListener

下一个示例演示了此概念。假设组件发布订单创建 事件,并且我们想要定义一个侦听器,该侦听器应该只处理该事件一次 已发布该消息的事务已成功提交。以下 示例设置这样的事件侦听器:

爪哇岛
Kotlin
@Component
public class MyComponent {

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

注释公开了一个属性,该属性允许您 自定义侦听器应绑定到的事务阶段。 有效阶段为 、(默认值)以及聚合事务完成的阶段(无论是提交还是回滚)。@TransactionalEventListenerphaseBEFORE_COMMITAFTER_COMMITAFTER_ROLLBACKAFTER_COMPLETION

如果没有事务在运行,则根本不会调用侦听器,因为我们无法遵循 所需的语义。但是,您可以通过将注释的属性设置为 来覆盖该行为。fallbackExecutiontrue

@TransactionalEventListener仅适用于由 管理的线程绑定事务。管理的反应式事务使用 Reactor 上下文而不是线程本地属性,因此从 事件侦听器,则没有可以参与的兼容活动事务。PlatformTransactionManagerReactiveTransactionManager

1.8. 特定于应用程序服务器的集成

Spring 的事务抽象通常与应用服务器无关。此外 Spring 的类(可以选择性地执行 JNDI 查找 JTA 和对象)自动检测 后一个对象,因应用程序服务器而异。访问 JTA 可以增强事务语义 — 特别是 支持交易暂停。有关详细信息,请参阅 JtaTransactionManager javadoc。JtaTransactionManagerUserTransactionTransactionManagerTransactionManager

Spring 是在 Java EE 应用程序上运行的标准选择 服务器,并且已知可以在所有常见的服务器上工作。高级功能,例如 事务暂停,也适用于许多服务器(包括 GlassFish、JBoss 和 Geronimo),无需任何特殊配置。但是,对于完全支持 事务暂停和进一步的高级集成,Spring 包括特殊的适配器 用于 WebLogic Server 和 WebSphere。下面将讨论这些适配器 部分。JtaTransactionManager

对于标准场景,包括 WebLogic Server 和 WebSphere,请考虑使用 方便的配置元素。配置后, 此元素会自动检测基础服务器并选择最佳服务器 可用于平台的事务管理器。这意味着您不需要明确 配置特定于服务器的适配器类(如以下各节所述)。 相反,它们是自动选择的,标准作为默认的后备。<tx:jta-transaction-manager/>JtaTransactionManager

1.8.1. IBM WebSphere

在 WebSphere 6.1.0.9 及更高版本上,推荐使用的 Spring JTA 事务管理器是 。这个特殊的适配器使用 IBM 的 API, 在 WebSphere Application Server 6.1.0.9 及更高版本中可用。使用此适配器, Spring 驱动的事务暂停(由 发起的暂停和恢复)是 IBM 的官方支持。WebSphereUowTransactionManagerUOWManagerPROPAGATION_REQUIRES_NEW

1.8.2. Oracle WebLogic Server

在 WebLogic Server 9.0 或更高版本上,通常使用 而不是 stock 类。这 特定于 WebLogic 的 normal 子类支持 在 WebLogic 管理的事务中充分发挥 Spring 事务定义的作用 环境,超出了标准的 JTA 语义。功能包括交易名称、 每个事务的隔离级别,以及在所有情况下正确恢复事务。WebLogicJtaTransactionManagerJtaTransactionManagerJtaTransactionManager

1.9. 常见问题的解决方案

本节介绍一些常见问题的解决方案。

1.9.1. 对特定的DataSource

根据您选择的 事务技术和要求。如果使用得当,Spring Framework 只是 提供简单易移植的抽象。如果使用全局 事务,则必须使用 类(或特定于应用程序服务器的子类 它)用于您的所有事务操作。否则,事务基础结构 尝试对容器实例等资源执行本地事务。这样的本地事务没有意义,一个好的应用服务器 将它们视为错误。PlatformTransactionManagerorg.springframework.transaction.jta.JtaTransactionManagerDataSource

1.10. 更多资源

有关 Spring Framework 的事务支持的更多信息,请参阅:

2. DAO 支持

Spring 中的数据访问对象 (DAO) 支持旨在使其易于使用 数据访问技术(如 JDBC、Hibernate 或 JPA)以一致的方式进行。这 允许您相当轻松地在上述持久性技术之间切换, 它还可以让您编写代码,而不必担心捕获异常 特定于每种技术。

2.1. 一致的异常层次结构

Spring 提供了从特定于技术的异常到它自己的异常类层次结构的便捷转换,该类层次结构具有 根异常。这些异常包装原始异常,以便永远不会 您可能会丢失有关可能出错的任何信息的任何风险。SQLExceptionDataAccessException

除了 JDBC 异常之外,Spring 还可以包装特定于 JPA 和 Hibernate 的异常, 将它们转换为一组集中的运行时异常。这使您可以处理大多数 仅在适当的层中出现不可恢复的持久性异常,而没有 DAO 中令人讨厌的样板捕获和抛出块和异常声明。 (不过,您仍然可以在需要的任何位置捕获和处理异常。如上所述, JDBC 异常(包括特定于数据库的方言)也会转换为相同的 层次结构,这意味着您可以在一致的 JDBC 中执行某些操作 编程模型。

前面的讨论适用于 Spring 支持中的各种模板类 用于各种 ORM 框架。如果使用基于侦听器的类,则应用程序必须 关心处理和本身,最好通过 分别委托给 的 或方法。这些方法转换异常 更改为与异常层次结构中的异常兼容的异常。如果不加以控制,它们也会被抛出 (不过,在例外方面牺牲了通用的 DAO 抽象)。HibernateExceptionsPersistenceExceptionsconvertHibernateAccessException(..)convertJpaAccessException(..)SessionFactoryUtilsorg.springframework.daoPersistenceExceptions

下图显示了 Spring 提供的异常层次结构。 (请注意,图中详述的类层次结构仅显示整个层次结构的子集。DataAccessException

DataAccessException

2.2. 用于配置 DAO 或存储库类的注解

保证数据访问对象 (DAO) 或存储库提供 异常翻译是使用注解。此注释还 让组件扫描支持查找和配置您的 DAO 和存储库 而不必为它们提供 XML 配置条目。以下示例显示 如何使用注解:@Repository@Repository

爪哇岛
Kotlin
@Repository (1)
public class SomeMovieFinder implements MovieFinder {
    // ...
}
1 批注。@Repository

任何 DAO 或存储库实现都需要访问持久性资源, 取决于所使用的持久性技术。例如,基于 JDBC 的存储库 需要访问 JDBC ,而基于 JPA 的存储库需要访问 .实现此目的的最简单方法是具有此资源依赖关系 通过使用 、 或 批注之一注入。以下示例适用于 JPA 存储库:DataSourceEntityManager@Autowired@Inject@Resource@PersistenceContext

爪哇岛
Kotlin
@Repository
public class JpaMovieFinder implements MovieFinder {

    @PersistenceContext
    private EntityManager entityManager;

    // ...
}

如果您使用经典的 Hibernate API,您可以按如下方式注入 。 示例显示:SessionFactory

爪哇岛
Kotlin
@Repository
public class HibernateMovieFinder implements MovieFinder {

    private SessionFactory sessionFactory;

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

    // ...
}

我们在这里展示的最后一个示例是典型的 JDBC 支持。您可以将 注入到初始化方法或构造函数中,您将在其中使用 这。以下示例自动连接:DataSourceJdbcTemplateSimpleJdbcCallDataSourceDataSource

爪哇岛
Kotlin
@Repository
public class JdbcMovieFinder implements MovieFinder {

    private JdbcTemplate jdbcTemplate;

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

    // ...
}
有关如何操作的详细信息,请参阅每种持久性技术的具体覆盖范围 配置应用程序上下文以利用这些注释。

3. 使用 JDBC 访问数据

Spring Framework JDBC 抽象提供的价值可能最好地体现在 下表中概述的操作顺序。下表显示了哪些操作 Spring 照顾以及哪些行动是你的责任。

Table 4. Spring JDBC - who does what?
行动 春天

定义连接参数。

X

打开连接。

X

指定 SQL 语句。

X

声明参数并提供参数值

X

准备并运行语句。

X

设置循环以循环访问结果(如果有)。

X

为每次迭代执行工作。

X

处理任何异常。

X

处理交易。

X

关闭连接、语句和结果集。

X

Spring Framework 负责处理所有可以使 JDBC 成为 乏味的 API。

3.1. 选择 JDBC 数据库访问方法

您可以在多种方法中进行选择,以构成 JDBC 数据库访问的基础。 除了三种风格的 之外,还有一种新的 和 方法优化了数据库元数据,而 RDBMS 对象样式采用了 更面向对象的方法,类似于 JDO 查询设计。一旦你开始使用 其中一种方法,您仍然可以混合和匹配以包含来自 不同的方法。所有方法都需要一个符合 JDBC 2.0 的驱动程序,并且一些 高级功能需要 JDBC 3.0 驱动程序。JdbcTemplateSimpleJdbcInsertSimpleJdbcCall

  • JdbcTemplate是经典和最流行的Spring JDBC方法。这 “最低级别”方法和所有其他方法都使用JdbcTemplate。

  • NamedParameterJdbcTemplate包装 A 以提供命名参数 而不是传统的 JDBC 占位符。这种方法提供了更好的 文档和 SQL 语句具有多个参数时的易用性。JdbcTemplate?

  • SimpleJdbcInsert并优化数据库元数据以限制数量 必要的配置。此方法简化了编码,因此您需要 仅提供表或过程的名称,并提供参数匹配的映射 列名。仅当数据库提供足够的元数据时,这才有效。如果 数据库不提供此元数据,您必须提供显式的 参数的配置。SimpleJdbcCall

  • RDBMS 对象 — 包括 、 和 — 要求您在初始化期间创建可重用和线程安全的对象 数据访问层。此方法以 JDO 查询为模型,其中定义 查询字符串,声明参数,然后编译查询。一旦你这样做了,、、和方法可以被调用多个 具有各种参数值的次数。MappingSqlQuerySqlUpdateStoredProcedureexecute(…​)update(…​)findObject(…​)

3.2. 软件包层次结构

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

  • core:包包含类及其 各种回调接口,以及各种相关类。名为 和 的子包包含 和 类。另一个名为 subpackage 的子包包含该类和相关的支持类。请参阅使用 JDBC 核心类控制基本 JDBC 处理和错误处理、JDBC 批处理操作和使用 SimpleJdbc 类简化 JDBC 操作org.springframework.jdbc.coreJdbcTemplateorg.springframework.jdbc.core.simpleSimpleJdbcInsertSimpleJdbcCallorg.springframework.jdbc.core.namedparamNamedParameterJdbcTemplate

  • datasource:该软件包包含一个实用程序类,以便于访问,以及可用于 在 Java EE 容器外部测试和运行未修改的 JDBC 代码。分包 named 提供对创建的支持 使用 Java 数据库引擎(如 HSQL、H2 和 Derby)嵌入数据库。请参见控制数据库连接嵌入式数据库支持org.springframework.jdbc.datasourceDataSourceDataSourceorg.springfamework.jdbc.datasource.embedded

  • object:包中包含表示 RDBMS 的类 查询、更新和存储过程作为线程安全、可重用的对象。请参阅将 JDBC 操作建模为 Java 对象。此方法由 JDO 建模,尽管查询返回的对象 自然与数据库断开连接。这种更高级别的 JDBC 抽象 取决于包中的较低级别的抽象。org.springframework.jdbc.objectorg.springframework.jdbc.core

  • support:软件包提供翻译 功能和一些实用程序类。JDBC 处理期间引发的异常包括 转换为包中定义的异常。这意味着 使用 Spring JDBC 抽象层的代码不需要实现 JDBC 或 特定于 RDBMS 的错误处理。所有翻译的异常都未选中,这为您提供了 捕获异常的选项,您可以从中恢复,同时让其他 异常将传播到调用方。请参阅使用 SQLExceptionTranslatororg.springframework.jdbc.supportSQLExceptionorg.springframework.dao

3.3. 使用 JDBC 核心类来控制基本的 JDBC 处理和错误处理

本节介绍如何使用 JDBC 核心类来控制基本的 JDBC 处理。 包括错误处理。它包括以下主题:

3.3.1. 使用JdbcTemplate

JdbcTemplate是 JDBC 核心包中的中心类。它处理 创建和发布资源,帮助您避免常见错误,例如 忘记关闭连接。它执行核心JDBC的基本任务 工作流(例如语句创建和执行),留下应用程序代码提供 SQL 并提取结果。班级:JdbcTemplate

  • 运行 SQL 查询

  • 更新语句和存储过程调用

  • 对实例执行迭代并提取返回的参数值。ResultSet

  • 捕获 JDBC 异常并将其转换为通用的、信息量更大的异常 包中定义的层次结构。(请参阅一致的异常层次结构org.springframework.dao

当您使用 for 代码时,您只需要实现回调 接口,为它们提供明确定义的契约。给定类提供的 a,回调接口创建一个准备好的 语句,提供 SQL 和任何必要的参数。创建可调用语句的接口也是如此。该接口从 .JdbcTemplateConnectionJdbcTemplatePreparedStatementCreatorCallableStatementCreatorRowCallbackHandlerResultSet

您可以通过直接实例化在 DAO 实现中使用 替换为引用,或者您可以在 Spring IoC 容器中配置它并将其提供给 DAO 作为 Bean 引用。JdbcTemplateDataSource

应始终在 Spring IoC 容器中配置为 bean。在 第一种情况是 Bean 直接提供给服务;在第二种情况下,它被给予 到准备好的模板。DataSource

此类发出的所有 SQL 都记录在类别下的级别 对应于模板实例的完全限定类名(通常为 ,但如果使用类的自定义子类,则可能会有所不同)。DEBUGJdbcTemplateJdbcTemplate

以下各节提供了一些用法示例。这些示例 不是 公开的所有功能的详尽列表。 有关这一点,请参阅附带的 javadocJdbcTemplateJdbcTemplate

查询 (SELECT)

以下查询获取关系中的行数:

爪哇岛
Kotlin
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);

以下查询使用绑定变量:

爪哇岛
Kotlin
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
        "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");

以下查询查找:String

爪哇岛
Kotlin
String lastName = this.jdbcTemplate.queryForObject(
        "select last_name from t_actor where id = ?",
        String.class, 1212L);

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

爪哇岛
Kotlin
Actor actor = jdbcTemplate.queryForObject(
        "select first_name, last_name from t_actor where id = ?",
        (resultSet, rowNum) -> {
            Actor newActor = new Actor();
            newActor.setFirstName(resultSet.getString("first_name"));
            newActor.setLastName(resultSet.getString("last_name"));
            return newActor;
        },
        1212L);

以下查询查找并填充域对象列表:

爪哇岛
Kotlin
List<Actor> actors = this.jdbcTemplate.query(
        "select first_name, last_name from t_actor",
        (resultSet, rowNum) -> {
            Actor actor = new Actor();
            actor.setFirstName(resultSet.getString("first_name"));
            actor.setLastName(resultSet.getString("last_name"));
            return actor;
        });

如果最后两个代码片段确实存在于同一个应用程序中,则 sense 删除两个 lambda 表达式中存在的重复项,以及 将它们提取到单个字段中,然后可以根据需要由 DAO 方法引用。 例如,最好按如下方式编写前面的代码片段:RowMapper

爪哇岛
Kotlin
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
    Actor actor = new Actor();
    actor.setFirstName(resultSet.getString("first_name"));
    actor.setLastName(resultSet.getString("last_name"));
    return actor;
};

public List<Actor> findAllActors() {
    return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}
将 (, , 和 ) 更新为INSERTUPDATEDELETEJdbcTemplate

可以使用该方法执行插入、更新和删除操作。 参数值通常作为变量参数提供,或者作为对象数组提供。update(..)

以下示例插入一个新条目:

爪哇岛
Kotlin
this.jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling");

以下示例更新现有条目:

爪哇岛
Kotlin
this.jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L);

以下示例删除一个条目:

爪哇岛
Kotlin
this.jdbcTemplate.update(
        "delete from t_actor where id = ?",
        Long.valueOf(actorId));
其他操作JdbcTemplate

可以使用该方法运行任意 SQL。因此, 方法通常用于 DDL 语句。它严重超载了需要 回调接口、绑定变量数组等。以下示例创建一个 桌子:execute(..)

爪哇岛
Kotlin
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");

下面的示例调用一个存储过程:

爪哇岛
Kotlin
this.jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        Long.valueOf(unionId));

稍后将介绍更复杂的存储过程支持。

JdbcTemplate最佳实践

类的实例一旦配置,就是线程安全的。这是 很重要,因为这意味着您可以配置单个实例,然后将此共享引用安全地注入到多个 DAO(或存储库)中。 是有状态的,因为它维护了对 的引用,但是 此状态不是会话状态。JdbcTemplateJdbcTemplateJdbcTemplateDataSource

使用该类(以及关联的 NamedParameterJdbcTemplate 类)时的常见做法是 在 Spring 配置文件中配置 ,然后 dependency-inject 将 Bean 共享到您的 DAO 类中。创建于 的 setter 。这导致类似于以下内容的 DAO:JdbcTemplateDataSourceDataSourceJdbcTemplateDataSource

爪哇岛
Kotlin
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...
}

以下示例显示了相应的 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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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>

显式配置的替代方法是使用组件扫描和注释 支持依赖注入。在这种情况下,您可以对类进行注释(这使其成为组件扫描的候选对象)并注释 setter 方法。以下示例演示如何执行此操作:@RepositoryDataSource@Autowired

爪哇岛
Kotlin
@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
2 用 注释 setter 方法。DataSource@Autowired
3 使用 创建一个新的。JdbcTemplateDataSource

以下示例显示了相应的 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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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 的类和各种 JDBC 支持的 DAO 类 从它扩展,您的子类从该类继承一个方法。您可以选择是否从此类继承。该课程仅为方便起见而提供。JdbcDaoSupportsetDataSource(..)JdbcDaoSupportJdbcDaoSupport

无论您选择使用上述哪种模板初始化样式(或 not),很少需要为每个类创建一个新实例 要运行 SQL 的时间。配置后,实例是线程安全的。 如果您的应用程序访问多个 数据库,您可能需要多个实例,这需要多个实例,随后需要多个不同的实例 配置的实例。JdbcTemplateJdbcTemplateJdbcTemplateDataSourcesJdbcTemplate

3.3.2. 使用NamedParameterJdbcTemplate

该类添加了对 JDBC 语句编程的支持 通过使用命名参数,而不是仅使用经典参数对 JDBC 语句进行编程 占位符 ( ) 参数。该类包装 a 并委托给包装后执行其大部分工作。这 部分仅介绍类中那些不同的区域 从本身 — 即,使用 named 对 JDBC 语句进行编程 参数。以下示例演示如何使用:NamedParameterJdbcTemplate'?'NamedParameterJdbcTemplateJdbcTemplateJdbcTemplateNamedParameterJdbcTemplateJdbcTemplateNamedParameterJdbcTemplate

爪哇岛
Kotlin
// 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);
}

请注意,在分配给变量的值中使用了命名参数表示法,以及插入到变量(类型)中的相应值。sqlnamedParametersMapSqlParameterSource

或者,您可以使用基于 - 的样式将命名参数及其相应的值传递到实例。其余的 由 类公开并由类实现的方法遵循类似的模式,此处不予介绍。NamedParameterJdbcTemplateMapNamedParameterJdbcOperationsNamedParameterJdbcTemplate

以下示例演示了基于 -- 的样式的用法:Map

爪哇岛
Kotlin
// 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);
}

一个不错的功能与(并且存在于 Java 包)是接口。您已经看过一个示例 在前面的代码片段之一(类)中此接口的实现。An 是命名参数的源 值设置为 .该类是 简单的实现,即围绕 的适配器,其中键 是参数名称,值是参数值。NamedParameterJdbcTemplateSqlParameterSourceMapSqlParameterSourceSqlParameterSourceNamedParameterJdbcTemplateMapSqlParameterSourcejava.util.Map

另一个实现是类。此类包装任意 JavaBean(即 坚持 JavaBean 约定),并使用包装的 JavaBean 的属性作为源 的命名参数值。SqlParameterSourceBeanPropertySqlParameterSource

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

爪哇岛
Kotlin
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...

}

以下示例使用 a 返回 前面示例中所示的类的成员:NamedParameterJdbcTemplate

爪哇岛
Kotlin
// 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);
}

请记住,该类包装了一个经典模板。如果您需要访问打包的实例才能访问 功能,则可以使用该方法通过接口访问包装。NamedParameterJdbcTemplateJdbcTemplateJdbcTemplateJdbcTemplategetJdbcOperations()JdbcTemplateJdbcOperations

另请参阅 JdbcTemplate 最佳实践,了解有关在应用程序上下文中使用该类的指南。NamedParameterJdbcTemplate

3.3.3. 使用SQLExceptionTranslator

SQLExceptionTranslator是一个接口,由可以翻译的类实现 在 s 和 Spring 之间, 这在数据访问策略方面是不可知的。实现可以是泛型的(对于 例如,对 JDBC 使用 SQLState 代码)或专有代码(例如,使用 Oracle 错误 codes) 以获得更高的精度。SQLExceptionorg.springframework.dao.DataAccessException

SQLErrorCodeSQLExceptionTranslator是默认使用的实现。此实现使用特定的供应商代码。它更多 比实施更精确。错误代码转换基于 代码保存在名为 的 JavaBean 类型类中。创建此类并 由 ,填充 ,顾名思义,它是 的工厂 基于名为 的配置文件的内容进行创建。此文件填充了供应商代码,并基于从 .实际代码 使用您正在使用的数据库。SQLExceptionTranslatorSQLStateSQLErrorCodesSQLErrorCodesFactorySQLErrorCodessql-error-codes.xmlDatabaseProductNameDatabaseMetaData

按以下顺序应用匹配规则:SQLErrorCodeSQLExceptionTranslator

  1. 由子类实现的任何自定义翻译。通常,使用提供的混凝土,因此此规则不适用。它 仅当实际提供了子类实现时才适用。SQLErrorCodeSQLExceptionTranslator

  2. 提供的接口的任何自定义实现 作为类的属性。SQLExceptionTranslatorcustomSqlExceptionTranslatorSQLErrorCodes

  3. 在类的实例列表(为类的属性提供)中搜索匹配项。CustomSQLErrorCodesTranslationcustomTranslationsSQLErrorCodes

  4. 应用错误代码匹配。

  5. 使用回退转换器。 是默认回退 在线翻译。如果此翻译不可用,则下一个后备翻译器是 这。SQLExceptionSubclassTranslatorSQLStateSQLExceptionTranslator

默认情况下用于定义代码和自定义异常 翻译。它们在名为 类路径,并且匹配的实例基于数据库进行定位 名称,从正在使用的数据库的数据库元数据中获取。SQLErrorCodesFactoryErrorsql-error-codes.xmlSQLErrorCodes

您可以扩展 ,如以下示例所示:SQLErrorCodeSQLExceptionTranslator

爪哇岛
Kotlin
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;
    }
}

在前面的示例中,转换了特定的错误代码 (),而其他错误则被转换 留给默认翻译器实现翻译。使用此自定义 translator,您必须将其传递给 through 方法,并且必须将其用于所有数据访问 在需要此转换器的地方进行处理。以下示例演示如何使用此自定义项 在线翻译:-12345JdbcTemplatesetExceptionTranslatorJdbcTemplate

爪哇岛
Kotlin
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);
}

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

3.3.4. 运行语句

运行 SQL 语句只需要很少的代码。您需要 a 和 a ,包括随 提供的便捷方法。以下示例显示了最小但 创建新表的全功能类:DataSourceJdbcTemplateJdbcTemplate

爪哇岛
Kotlin
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))");
    }
}

3.3.5. 运行查询

某些查询方法返回单个值。从中检索计数或特定值 一行,使用 .后者将返回的 JDBC 转换为 作为参数传入的 Java 类。如果类型转换无效,则抛出 。以下示例包含两个 查询方法,一个用于查询:queryForObject(..)TypeInvalidDataAccessApiUsageExceptionintString

爪哇岛
Kotlin
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(..)ListMap

爪哇岛
Kotlin
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");
}

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

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

3.3.6. 更新数据库

以下示例更新特定主键的列:

爪哇岛
Kotlin
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 或对象数组。因此,您应该显式包装基元 在原始包装类中,或者您应该使用自动装箱。

3.3.7. 检索自动生成的密钥

一种便捷的方法支持检索由 数据库。此支持是 JDBC 3.0 标准的一部分。参见第 13.6 章 规格了解详情。该方法将 a 作为其第一个 参数,这是指定所需 INSERT 语句的方式。另一个 参数为 ,它包含从 更新。没有标准的单一方法来创建适当的方法(这解释了为什么方法签名是这样的)。以下示例有效 在 Oracle 上,但可能无法在其他平台上工作:update()PreparedStatementCreatorKeyHolderPreparedStatement

爪哇岛
Kotlin
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
    PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
    ps.setString(1, name);
    return ps;
}, keyHolder);

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

3.4. 控制数据库连接

本节包括:

3.4.1. 使用DataSource

Spring 通过 .A 是 JDBC 规范的一部分,是一个通用的连接工厂。它让一个 容器或框架隐藏连接池和事务管理问题 从应用程序代码。作为开发人员,您不需要了解有关如何 连接到数据库。这是设置的管理员的责任 数据源。在开发和测试代码时,您很可能会同时担任这两个角色,但您 不必知道生产数据源的配置方式。DataSourceDataSource

使用 Spring 的 JDBC 层时,可以从 JNDI 获取数据源,也可以 使用第三方提供的连接池实现配置您自己的连接池实现。 传统的选择是 Apache Commons DBCP 和 C3P0 以及 bean 样式的类; 对于现代 JDBC 连接池,请考虑使用 HikariCP 及其构建器样式的 API。DataSource

您应该使用 和 类 (包含在 Spring 发行版中)仅用于测试目的!这些变体则不然 提供池化,并且在发出多个连接请求时性能不佳。DriverManagerDataSourceSimpleDriverDataSource

以下部分使用 Spring 的实现。 稍后将介绍其他几种变体。DriverManagerDataSourceDataSource

要配置 :DriverManagerDataSource

  1. 获取连接,就像通常获取 JDBC 一样 连接。DriverManagerDataSource

  2. 指定 JDBC 驱动程序的标准类名,以便可以装入驱动程序类。DriverManager

  3. 提供因 JDBC 驱动程序而异的 URL。(请参阅驱动程序的文档 以获得正确的值。

  4. 提供用户名和密码以连接到数据库。

以下示例显示了如何在 Java 中配置 :DriverManagerDataSource

爪哇岛
Kotlin
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");

以下示例显示了相应的 XML 配置:

<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 的基本连接和配置。 若要了解有助于控制池化功能的更多选项,请参阅产品 相应连接池实现的文档。

以下示例显示了 DBCP 配置:

<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"/>

以下示例显示了 C3P0 配置:

<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"/>

3.4.2. 使用DataSourceUtils

该类是一个方便且功能强大的帮助程序类,它提供了从 JNDI 获取连接并在必要时关闭连接的方法。它 例如,支持与 的线程绑定连接。DataSourceUtilsstaticDataSourceTransactionManager

3.4.3. 实现SmartDataSource

该接口应由可以提供 连接到关系数据库。它扩展了接口,让 使用它的类会查询在给定后是否应关闭连接 操作。当您知道需要重用连接时,这种用法非常有效。SmartDataSourceDataSource

3.4.4. 扩展AbstractDataSource

AbstractDataSource是 Spring 实现的基类。它实现所有实现通用的代码。 如果您编写自己的实现,则应扩展该类。abstractDataSourceDataSourceAbstractDataSourceDataSource

3.4.5. 使用SingleConnectionDataSource

该类是接口的实现,该接口包装了每次使用后未关闭的单个接口。 这不支持多线程。SingleConnectionDataSourceSmartDataSourceConnection

如果任何客户端代码在池连接的假设下调用(如使用 持久性工具),应将该属性设置为 。此设置 返回包装物理连接的关闭抑制代理。请注意,您可以 不再将其强制转换为本机 Oracle 或类似对象。closesuppressClosetrueConnection

SingleConnectionDataSource主要是一个测试类。它通常可以轻松测试 应用程序服务器外部的代码,以及简单的 JNDI 环境。 与 相反,它始终重用相同的连接, 避免过度创建物理连接。DriverManagerDataSource

3.4.6. 使用DriverManagerDataSource

该类是标准接口的实现,它通过 Bean 属性配置普通 JDBC 驱动程序,并且每次都返回一个新的。DriverManagerDataSourceDataSourceConnection

此实现对于 Java EE 之外的测试环境和独立环境非常有用 容器,可以作为 Spring IoC 容器中的 bean 也可以结合使用 具有简单的 JNDI 环境。池假设调用 关闭连接,以便任何感知持久性代码都应正常工作。然而 使用 JavaBean 样式的连接池(例如 )非常容易,即使在测试中也是如此 环境中,使用这样的连接池几乎总是更可取的。DataSourceConnection.close()DataSourcecommons-dbcpDriverManagerDataSource

3.4.7. 使用TransactionAwareDataSourceProxy

TransactionAwareDataSourceProxy是目标的代理。代理包装了 target 以增加对 Spring 管理的事务的认知。在这方面,它 类似于Java EE服务器提供的事务性JNDI。DataSourceDataSourceDataSource

很少需要使用此类,除非已经存在的代码必须 调用并传递了标准的 JDBC 接口实现。在本例中, 您仍然可以使用此代码,同时使用此代码 参与 Spring 托管事务。通常最好写你的 通过使用更高级别的资源管理抽象(如 或)拥有新代码。DataSourceJdbcTemplateDataSourceUtils

有关详细信息,请参阅 TransactionAwareDataSourceProxy javadoc。

3.4.8. 使用DataSourceTransactionManager

该类是单个 JDBC 数据源的实现。它绑定来自 指定数据源到当前正在执行的线程,可能允许一个 每个数据源的线程连接。DataSourceTransactionManagerPlatformTransactionManager

需要应用程序代码来检索 JDBC 连接,而不是 Java EE 的标准。它会引发未经检查的异常 而不是检查.所有框架类(例如 )都使用此 隐含的策略。如果未与此事务管理器一起使用,则查找策略 行为与普通的完全相同。因此,它可以在任何情况下使用。DataSourceUtils.getConnection(DataSource)DataSource.getConnectionorg.springframework.daoSQLExceptionsJdbcTemplate

该类支持自定义隔离级别和超时 作为适当的 JDBC 语句查询超时应用。为了支持后者, 应用程序代码必须使用或调用每个创建的语句的方法。DataSourceTransactionManagerJdbcTemplateDataSourceUtils.applyTransactionTimeout(..)

您可以使用此实现,而不是在单个资源中 情况,因为它不需要容器支持 JTA。在 两者都只是配置问题,前提是您坚持所需的连接查找 模式。JTA 不支持自定义隔离级别。JtaTransactionManager

3.5. JDBC批处理操作

如果对同一驱动程序进行批处理多个调用,则大多数 JDBC 驱动程序都会提供改进的性能 准备好的声明。通过将更新分组到批次中,可以限制往返次数 到数据库。

3.5.1. 基本批处理操作JdbcTemplate

您可以通过实现特殊方法的两种方法来完成批处理 接口,并将该实现作为第二个参数传入 在方法调用中。您可以使用该方法提供 当前批次。可以使用该方法设置 准备好的语句。此方法称为 you 的次数 在调用中指定。以下示例更新该表 基于列表中的条目,并将整个列表用作批处理:JdbcTemplateBatchPreparedStatementSetterbatchUpdategetBatchSizesetValuesgetBatchSizet_actor

爪哇岛
Kotlin
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 {
                        Actor actor = actors.get(i);
                        ps.setString(1, actor.getFirstName());
                        ps.setString(2, actor.getLastName());
                        ps.setLong(3, actor.getId().longValue());
                    }
                    public int getBatchSize() {
                        return actors.size();
                    }
                });
    }

    // ... additional methods
}

如果处理更新流或从文件中读取,则可能具有 首选批处理大小,但最后一个批处理可能没有该数量的条目。在这个 情况下,您可以使用该界面,该界面让 一旦输入源用尽,就会中断批处理。方法 允许您发出批处理结束的信号。InterruptibleBatchPreparedStatementSetterisBatchExhausted

3.5.2. 使用对象列表进行批处理操作

和 都提供了另一种方式 提供批量更新。无需实现特殊的批处理接口,而是 以列表形式提供调用中的所有参数值。框架遍历这些 值并使用内部预准备语句 setter。API 因 是否使用命名参数。对于命名参数,请为批处理的每个成员提供一个数组 ,一个条目。您可以使用方便的方法创建此数组,将 在 Bean 样式对象(具有与参数对应的 getter 方法)、键控实例(包含相应的参数作为值)或两者的混合的数组中。JdbcTemplateNamedParameterJdbcTemplateSqlParameterSourceSqlParameterSourceUtils.createBatchStringMap

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

爪哇岛
Kotlin
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 语句,请传入一个列表 包含具有更新值的对象数组。此对象数组必须有一个条目 对于 SQL 语句中的每个占位符,并且它们的顺序必须与它们相同 在 SQL 语句中定义。?

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

爪哇岛
Kotlin
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
}

我们前面介绍的所有批量更新方法都返回一个数组 包含每个批处理条目的受影响行数。此计数由 JDBC 驱动程序。如果计数不可用,那么 JDBC 驱动程序将返回值 。int-2

在这种情况下,通过自动设置底层 , 每个值的相应 JDBC 类型需要派生自给定的 Java 类型。 虽然这通常效果很好,但可能会出现问题(例如,使用映射包含的值)。默认情况下,Spring 会调用这样的 case,这对于您的 JDBC 驱动程序来说可能很昂贵。您应该使用最新的驱动程序 version,如果遇到以下情况,请考虑将该属性设置为(作为 JVM 系统属性或通过 SpringProperties 机制) 性能问题(如 Oracle 12c、JBoss 和 PostgreSQL 上报告的那样)。PreparedStatementnullParameterMetaData.getParameterTypespring.jdbc.getParameterType.ignoretrue

或者,您可以考虑显式指定相应的 JDBC 类型, 通过 a(如前所示),通过显式类型 数组,通过对 自定义实例,或者通过从 Java 声明的属性类型派生 SQL 类型,即使对于 null 值也是如此。BatchPreparedStatementSetterList<Object[]>registerSqlTypeMapSqlParameterSourceBeanPropertySqlParameterSource

3.5.3. 多批次的批处理操作

前面的批量更新示例处理的批处理量太大,您希望 将它们分成几个较小的批次。您可以使用以下方法执行此操作 前面通过对该方法进行多次调用来提到,但现在有一个 更方便的方法。除了 SQL 语句之外,此方法还采用包含参数的对象,以及要为每个对象进行的更新次数 batch,a 设置参数的值 准备好的声明。框架循环提供的值并中断 将调用更新为指定大小的批处理。batchUpdateCollectionParameterizedPreparedStatementSetter

以下示例显示了使用批大小 100 的批处理更新:

爪哇岛
Kotlin
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,
                (PreparedStatement ps, Actor actor) -> {
                    ps.setString(1, actor.getFirstName());
                    ps.setString(2, actor.getLastName());
                    ps.setLong(3, actor.getId().longValue());
                });
        return updateCounts;
    }

    // ... additional methods
}

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

3.6. 使用类简化 JDBC 操作SimpleJdbc

和类提供了简化的配置 通过利用可通过 JDBC 驱动程序检索的数据库元数据。 这意味着,尽管您可以覆盖或关闭,但您需要预先配置的更少 元数据处理(如果您希望在代码中提供所有详细信息)。SimpleJdbcInsertSimpleJdbcCall

3.6.1. 使用SimpleJdbcInsert

我们首先看一下 配置选项。您应该在数据访问中实例化 层的初始化方法。在此示例中,初始化方法是方法。您不需要对类进行子类化。相反 您可以使用该方法创建新实例并设置表名。 此类的配置方法遵循返回实例的样式 ,它允许您链接所有配置方法。以下 示例仅使用一种配置方法(稍后我们将展示多种方法的示例):SimpleJdbcInsertSimpleJdbcInsertsetDataSourceSimpleJdbcInsertwithTableNamefluidSimpleJdbcInsert

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource 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
}

此处使用的方法将普通值作为其唯一参数。这 这里需要注意的重要一点是,用于 must 的键与列匹配 数据库中定义的表的名称。这是因为我们读取了元数据 构造实际的 INSERT 语句。executejava.util.MapMap

3.6.2. 使用SimpleJdbcInsert

下一个示例使用与上一个示例相同的插入,但是,它不是传入 ,而是传入 检索自动生成的密钥并将其设置到新对象上。创建时 除了指定表名外,它还指定了名称 生成的键列。以下 列表显示了它是如何工作的:idActorSimpleJdbcInsertusingGeneratedKeyColumns

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource 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
}

使用第二种方法运行插入时的主要区别在于,您不这样做 将 添加到 中,然后调用该方法。这将返回一个对象,您可以使用该对象创建数值类型的实例,该实例 用于您的域类。不能依赖所有数据库来返回特定的 Java 在这里上课。 是可以依赖的基类。如果您有 多个自动生成的列或生成的值为非数字,您可以 使用从方法返回的 a。idMapexecuteAndReturnKeyjava.lang.Numberjava.lang.NumberKeyHolderexecuteAndReturnKeyHolder

3.6.3. 为SimpleJdbcInsert

可以通过使用该方法指定列名列表来限制插入的列,如以下示例所示:usingColumns

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource 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
}

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

3.6.4. 用于提供参数值SqlParameterSource

使用 a 提供参数值效果很好,但这不是最方便的 要使用的类。Spring 提供了几个接口的实现,你可以改用它们。第一个是, 这是一个非常方便的类,如果您有一个符合 JavaBean 的类,其中包含 你的价值观。它使用相应的 getter 方法来提取参数 值。以下示例演示如何使用:MapSqlParameterSourceBeanPropertySqlParameterSourceBeanPropertySqlParameterSource

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource 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
}

另一种选择是类似于 但提供更多 可以链接的便捷方法。以下示例演示如何使用它:MapSqlParameterSourceMapaddValue

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource 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
}

如您所见,配置是相同的。只有正在执行的代码必须更改为 使用这些替代输入类。

3.6.5. 使用SimpleJdbcCall

该类使用数据库中的元数据来查找名称和参数,这样您就不必显式声明它们。您可以 如果您愿意这样做,或者您的参数(例如 或 )没有自动映射到 Java 类,请声明参数。第一个例子 显示一个简单的过程,该过程仅返回 AND 格式中的标量值 从 MySQL 数据库。该示例过程读取指定的执行组件条目,并以参数的形式返回 、 和 列。 下面的清单显示了第一个示例:SimpleJdbcCallinoutARRAYSTRUCTVARCHARDATEfirst_namelast_namebirth_dateout

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_ididout

您可以采用类似于声明 的方式进行声明。你 应该在数据访问的初始化方法中实例化和配置类 层。与类相比,您不需要创建子类 并且您不需要声明可在数据库元数据中查找的参数。 以下配置示例使用前面存储的 过程(除了 之外,唯一的配置选项是名称 存储过程):SimpleJdbcCallSimpleJdbcInsertStoredProcedureSimpleJdbcCallDataSource

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource 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
}

为执行调用而编写的代码涉及创建一个包含 IN 参数。您必须与为输入值提供的名称匹配 替换为存储过程中声明的参数名称。本案没有 进行匹配,因为您使用元数据来确定应如何引用数据库对象 在存储过程中。在存储过程的源中指定的内容不是 必然是它在数据库中的存储方式。某些数据库将名称转换为所有 大写,而其他人则使用小写或按指定使用大小写。SqlParameterSource

该方法采用 IN 参数,并返回一个,其中包含按存储过程中指定的名称键控的任何参数。在本例中,它们是 、 和 。executeMapoutout_first_nameout_last_nameout_birth_date

该方法的最后一部分创建一个实例,用于返回 检索到的数据。同样,使用参数的名称也很重要,因为它们 在存储过程中声明。此外,结果映射中存储的参数名称的大小写与 数据库,可能因数据库而异。为了使你的代码更具可移植性,你应该 执行不区分大小写的查找或指示 Spring 使用 . 要执行后者,您可以创建自己的属性并将属性设置为 。然后,您可以将此自定义实例传递给 您的 .以下示例显示了此配置:executeActoroutoutoutLinkedCaseInsensitiveMapJdbcTemplatesetResultsMapCaseInsensitivetrueJdbcTemplateSimpleJdbcCall

爪哇岛
Kotlin
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

3.6.6. 显式声明用于SimpleJdbcCall

在本章的前面,我们介绍了如何从元数据中推断参数,但您可以声明它们 如果你愿意,明确。为此,您可以使用以下命令进行创建和配置 该方法,该方法采用可变数量的对象 作为输入。有关如何定义 .SimpleJdbcCalldeclareParametersSqlParameterSqlParameter

如果您使用的数据库不是 Spring 支持的数据库,那么显式声明是必需的 数据库。目前,Spring 支持对存储过程调用的元数据查找 以下数据库:Apache Derby、DB2、MySQL、Microsoft SQL Server、Oracle 和 Sybase。 我们还支持MySQL,Microsoft SQL Server的存储函数的元数据查找, 和 Oracle。

您可以选择显式声明一个、部分或全部参数。参数 在未显式声明参数的情况下,仍会使用元数据。要绕过所有 处理潜在参数的元数据查找,并仅使用声明的 参数,您可以将该方法作为 声明。假设您为 数据库函数。在这种情况下,您可以调用以指定列表 要为给定签名包含的 IN 参数名称。withoutProcedureColumnMetaDataAccessuseInParameterNames

下面的示例演示一个完全声明的过程调用,并使用 前面的示例:

爪哇岛
Kotlin
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
}

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

3.6.7. 如何定义SqlParameters

为类和 RDBMS 操作定义参数 可以使用的类(在将 JDBC 操作建模为 Java 对象中介绍)或其子类之一。 为此,通常在构造函数中指定参数名称和 SQL 类型。SQL 类型 使用常量指定。在本章的前面,我们看到了声明 类似于以下内容:SimpleJdbcSqlParameterjava.sql.Types

爪哇岛
Kotlin
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

带有 的第一行声明了一个 IN 参数。您可以使用 IN 参数 对于存储过程调用和使用 和 其 子类(在了解 SqlQuery 中介绍)。SqlParameterSqlQuery

第二行(带有 )声明要在 存储过程调用。还有一个 for 参数 (为过程提供 IN 值并返回值的参数)。SqlOutParameteroutSqlInOutParameterInOut

只有声明为 和 用于 提供输入值。这与类不同,该类(对于 向后兼容性原因)允许为参数提供输入值 声明为 。SqlParameterSqlInOutParameterStoredProcedureSqlOutParameter

对于 IN 参数,除了名称和 SQL 类型之外,还可以指定 自定义数据库类型的数值数据或类型名称。对于参数,您可以 提供 A 来处理从游标返回的行的映射。另一个 选项是指定一个提供定义机会的机会 自定义处理返回值。outRowMapperREFSqlReturnType

3.6.8. 使用SimpleJdbcCall

调用存储函数的方式与调用存储过程的方式几乎相同,但 您提供的是函数名称,而不是过程名称。使用该方法作为配置的一部分来指示要使 对函数的调用,并生成函数调用的相应字符串。一个 专用调用 () 用于运行函数,它 将函数返回值作为指定类型的对象返回,这意味着您这样做 不必从结果映射中检索返回值。类似的便捷方法 (named ) 也可用于只有一个参数的存储过程。以下示例(适用于 MySQL)基于一个名为的存储函数,该函数返回执行组件的全名:withFunctionNameexecuteFunctionexecuteObjectoutget_actor_name

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;

为了调用这个函数,我们再次在初始化方法中创建一个, 如以下示例所示:SimpleJdbcCall

爪哇岛
Kotlin
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall funcGetActorName;

    public void setDataSource(DataSource 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
}

使用的方法返回一个,其中包含 函数调用。executeFunctionString

3.6.9. 从ResultSetSimpleJdbcCall

调用返回结果集的存储过程或函数有点棘手。一些 数据库在 JDBC 结果处理期间返回结果集,而其他数据库则需要 显式注册特定类型的参数。这两种方法都需要 用于循环访问结果集并处理返回的行的附加处理。跟 可以使用该方法并声明要用于特定参数的实现。如果结果集是 在结果处理过程中返回,没有定义名称,因此返回的 结果必须与声明实现的顺序匹配。指定的名称仍用于存储已处理的结果列表 在从语句返回的结果映射中。outSimpleJdbcCallreturningResultSetRowMapperRowMapperexecute

下一个示例(对于 MySQL)使用一个不带 IN 参数并返回的存储过程 表中的所有行:t_actor

CREATE PROCEDURE read_all_actors()
BEGIN
 SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

若要调用此过程,可以声明 .因为您想要的类 要按照 JavaBean 规则进行映射,则可以使用 在方法中传入要映射到的所需类。 以下示例演示如何执行此操作:RowMapperBeanPropertyRowMappernewInstance

爪哇岛
Kotlin
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
}

调用传入空 ,因为此调用不采用任何参数。 然后,从结果映射中检索参与者列表,并将其返回给调用方。executeMap

3.7. 将 JDBC 操作建模为 Java 对象

该包包含允许您访问 数据库以更面向对象的方式。例如,您可以运行查询 并将结果作为列表返回,该列表包含具有关系的业务对象 映射到业务对象属性的列数据。您也可以运行存储 过程并运行 update、delete 和 insert 语句。org.springframework.jdbc.object

许多 Spring 开发人员认为,下面描述的各种 RDBMS 操作类 (StoredProcedure 类除外)通常可以 替换为直接呼叫。通常,编写 DAO 更简单 方法,直接调用方法(而不是 将查询封装为一个完整的类)。JdbcTemplateJdbcTemplate

但是,如果您从使用 RDBMS 操作类中获得可衡量的价值, 您应该继续使用这些类。

3.7.1. 理解SqlQuery

SqlQuery是一个可重用的线程安全类,用于封装 SQL 查询。子 必须实现该方法以提供一个可以 每行创建一个对象,该对象是通过遍历创建的 在执行查询期间。该类很少直接使用,因为 子类为 将行映射到 Java 类。扩展的其他实现是 和 。newRowMapper(..)RowMapperResultSetSqlQueryMappingSqlQuerySqlQueryMappingSqlQueryWithParametersUpdatableSqlQuery

3.7.2. 使用MappingSqlQuery

MappingSqlQuery是一个可重用的查询,其中具体的子类必须实现 抽象方法将提供的每一行转换为 指定类型的对象。以下示例显示了一个自定义查询,该查询映射了 来自与类实例的关系的数据:mapRow(..)ResultSett_actorActor

爪哇岛
Kotlin
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;
    }
}

该类扩展为使用类型进行参数化。构造函数 对于此客户查询,将 A 作为唯一参数。在这个 构造函数,您可以使用 和 SQL 应运行该行以检索此查询的行。此 SQL 用于 创建一个 ,以便它可以包含任何参数的占位符 在执行期间传入。必须使用传入 .采用名称和 JDBC 类型 如中所定义。定义所有参数后,可以调用该方法,以便可以准备语句并在以后运行。这个班级是 线程安全后编译,所以,只要这些实例是在 DAO 创建时创建的 初始化后,它们可以保留为实例变量并重用。以下 示例演示如何定义这样的类:MappingSqlQueryActorDataSourceDataSourcePreparedStatementdeclareParameterSqlParameterSqlParameterjava.sql.Typescompile()

爪哇岛
Kotlin
private ActorMappingQuery actorMappingQuery;

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

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

前面示例中的方法检索客户,并将 传入的 only 参数。由于我们只想返回一个对象,因此我们将便利性称为 方法,作为参数。如果我们有一个返回 对象列表并采用其他参数,我们将使用其中一种方法,该方法将参数值数组作为 varargs 传入。以下 示例显示了这样的方法:idfindObjectidexecute

爪哇岛
Kotlin
public List<Actor> searchForActors(int age, String namePattern) {
    List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
    return actors;
}

3.7.3. 使用SqlUpdate

该类封装 SQL 更新。与查询一样,更新对象是 可重用,并且与所有类一样,更新可以具有参数和 在 SQL 中定义。此类提供了许多类似于查询对象的方法的方法。该类是具体的。它可以是 subclassed — 例如,添加自定义更新方法。 但是,您不必对类进行子类化,因为可以通过设置 SQL 和声明参数来轻松对其进行参数化。 以下示例创建一个名为 :SqlUpdateRdbmsOperationupdate(..)execute(..)SqlUpdateSqlUpdateexecute

爪哇岛
Kotlin
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);
    }
}

3.7.4. 使用StoredProcedure

该类是 RDBMS 对象抽象的超类 存储过程。StoredProcedureabstract

继承的属性是 RDBMS 中存储过程的名称。sql

若要定义类的参数,可以使用 或 其子类。您必须在构造函数中指定参数名称和 SQL 类型, 如以下代码片段所示:StoredProcedureSqlParameter

爪哇岛
Kotlin
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

SQL 类型是使用常量指定的。java.sql.Types

第一行(带有 )声明一个 IN 参数。您可以使用 IN 参数 对于存储过程调用和使用 和 its 的查询 子类(在了解 SqlQuery 中介绍)。SqlParameterSqlQuery

第二行(带有 )声明要在 存储过程调用。还有一个 for 参数 (为过程提供值并返回值的参数)。SqlOutParameteroutSqlInOutParameterInOutin

对于参数,除了名称和 SQL 类型外,还可以指定 数值数据的 scale 或自定义数据库类型的类型名称。对于参数, 您可以提供 to 处理从游标返回的行的映射。 另一个选项是指定一个允许您定义自定义的 返回值的处理。inoutRowMapperREFSqlReturnType

下一个简单 DAO 示例使用 a 调用函数 (),它随任何 Oracle 数据库一起提供。使用存储过程 功能,您必须创建一个扩展 .在这个 例如,该类是一个内部类。但是,如果需要重用 ,可以将其声明为顶级类。此示例没有输入 参数,但输出参数是使用该类声明为日期类型的。该方法运行该过程并提取 从结果 返回日期。结果对应每个声明的条目 输出参数(在本例中,只有一个),方法是使用参数名称作为键。 下面的清单显示了我们的自定义 StoredProcedure 类:StoredProceduresysdate()StoredProcedureStoredProcedureStoredProcedureSqlOutParameterexecute()MapMap

爪哇岛
Kotlin
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 游标):StoredProcedure

爪哇岛
Kotlin
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>());
    }
}

请注意,该方法的重载变体是如何被 在构造函数中使用的是传递的实现实例。这是一种非常方便和强大的重用现有方式 功能性。接下来的两个示例为这两种实现提供了代码。declareParameter(..)TitlesAndGenresStoredProcedureRowMapperRowMapper

该类将 a 映射到 中每一行的域对象 提供的 ,如下所示:TitleMapperResultSetTitleResultSet

爪哇岛
Kotlin
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;
    }
}

该类将 a 映射到 中每一行的域对象 提供的 ,如下所示:GenreMapperResultSetGenreResultSet

爪哇岛
Kotlin
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"));
    }
}

将参数传递给具有一个或多个输入参数的存储过程 定义,您可以编写一个强类型方法,该方法将 委托给超类中的非类型化方法,如以下示例所示:execute(..)execute(Map)

爪哇岛
Kotlin
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);
    }
}

3.8. 参数和数据值处理的常见问题

不同方法中存在参数和数据值的常见问题 由 Spring Framework 的 JDBC 支持提供。本节介绍如何解决这些问题。

3.8.1. 为参数提供 SQL 类型信息

通常,Spring 会根据参数的类型来确定参数的 SQL 类型 传了进来。可以显式提供在设置 参数值。这有时对于正确设置值是必要的。NULL

您可以通过多种方式提供 SQL 类型信息:

  • 许多更新和查询方法在 数组的形式。此数组用于指示 相应的参数,使用类中的常量值。提供 每个参数一个条目。JdbcTemplateintjava.sql.Types

  • 您可以使用该类来包装需要此值的参数值 其他信息。为此,请为每个值创建一个新实例,并传入 SQL 类型 和构造函数中的参数值。您还可以提供可选的刻度 数值的参数。SqlParameterValue

  • 对于使用命名参数的方法,可以使用类或 .他们都有方法 用于注册任何命名参数值的 SQL 类型。SqlParameterSourceBeanPropertySqlParameterSourceMapSqlParameterSource

3.8.2. 处理 BLOB 和 CLOB 对象

您可以在数据库中存储图像、其他二进制数据和大块文本。这些 对于二进制数据,大型对象称为 BLOB(二进制大型对象)和 CLOB(字符) Large OBject) 用于字符数据。在 Spring 中,你可以使用 直接使用,也可以在使用 RDBMS 提供的更高抽象时使用 对象和类。所有这些方法都使用 用于实际管理 LOB(大型 OBject)数据的接口。 提供对类的访问,通过以下方法: 用于创建要插入的新 LOB 对象。JdbcTemplateSimpleJdbcLobHandlerLobHandlerLobCreatorgetLobCreator

LobCreator并为 LOB 输入和输出提供以下支持:LobHandler

  • 斑点

    • byte[]:和getBlobAsBytessetBlobAsBytes

    • InputStream:和getBlobAsBinaryStreamsetBlobAsBinaryStream

  • 克洛布

    • String:和getClobAsStringsetClobAsString

    • InputStream:和getClobAsAsciiStreamsetClobAsAsciiStream

    • Reader:和getClobAsCharacterStreamsetClobAsCharacterStream

下一个示例演示如何创建和插入 BLOB。稍后我们将展示如何阅读 它从数据库返回。

此示例使用 的 和 实现。它实现了一个方法,即 .此方法提供了一个我们用来设置 SQL 插入语句中的 LOB 列。JdbcTemplateAbstractLobCreatingPreparedStatementCallbacksetValuesLobCreator

对于此示例,我们假设有一个变量 ,它已经 设置为 的实例。通常通过以下方式设置此值 依赖注入。lobHandlerDefaultLobHandler

以下示例演示如何创建和插入 BLOB:

爪哇岛
Kotlin
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 传入 that(在本例中)是普通的 .lobHandlerDefaultLobHandler
2 使用该方法传入 CLOB 的内容。setClobAsCharacterStream
3 使用该方法传入 BLOB 的内容。setBlobAsBinaryStream

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

请参阅用于验证其是否支持流式处理的 JDBC 驱动程序的文档 LOB,而不提供内容长度。

现在是时候从数据库中读取 LOB 数据了。同样,您将 a 与相同的实例变量和对 . 以下示例演示如何执行此操作:JdbcTemplatelobHandlerDefaultLobHandler

爪哇岛
Kotlin
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 使用该方法检索 CLOB 的内容。getClobAsString
2 使用该方法检索 BLOB 的内容。getBlobAsBytes

3.8.3. 传入 IN 子句的值列表

SQL 标准允许根据包含 变量值列表。一个典型的例子是 。预准备语句不直接支持此变量列表 JDBC标准。不能声明可变数量的占位符。你需要一个数字 的变体,并准备了所需数量的占位符,或者您需要生成 一旦知道需要多少个占位符,就会动态地发送 SQL 字符串。命名的 和 中提供的参数支持 后一种方法。您可以将值作为基元对象传入。这 list 用于插入所需的占位符,并在 语句执行。select * from T_ACTOR where id in (1, 2, 3)NamedParameterJdbcTemplateJdbcTemplatejava.util.List

传入多个值时要小心。JDBC标准不保证您 可以对表达式列表使用 100 个以上的值。各种数据库都超过了这个水平 数字,但它们通常对允许的值数有硬性限制。例如,Oracle 的 限制为 1000。in

除了值列表中的基元值之外,还可以创建对象数组。此列表可以支持为子句定义的多个表达式,例如 .当然,这需要您的数据库支持此语法。java.util.Listinselect * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))

3.8.4. 处理存储过程调用的复杂类型

调用存储过程时,有时可以使用特定于 数据库。为了适应这些类型,Spring 提供了一个 当它们从存储过程调用返回时,以及当它们 作为参数传入存储过程。SqlReturnTypeSqlTypeValue

该接口有一个方法(名为 ),该方法必须 实现。此接口用作 声明的一部分。 下面的示例演示如何返回用户的 Oracle 对象的值 声明类型:SqlReturnTypegetTypeValueSqlOutParameterSTRUCTITEM_TYPE

爪哇岛
Kotlin
public class TestItemStoredProcedure extends StoredProcedure {

    public TestItemStoredProcedure(DataSource dataSource) {
        // ...
        declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
            (CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
                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;
            }));
        // ...
    }

您可以使用将 Java 对象(例如 )的值传递给 存储过程。该接口具有必须实现的单个方法(名为 )。活动连接被传入,你 可以使用它来创建特定于数据库的对象,例如实例 或实例。以下示例创建一个实例:SqlTypeValueTestItemSqlTypeValuecreateTypeValueStructDescriptorArrayDescriptorStructDescriptor

爪哇岛
Kotlin
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;
    }
};

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

的另一个用途是将值数组传递给存储的 Oracle 程序。Oracle 有自己的内部类,在这种情况下必须使用该类,并且 您可以使用 创建 Oracle 的实例并填充 它替换为 Java 中的值,如以下示例所示:SqlTypeValueARRAYSqlTypeValueARRAYARRAY

爪哇岛
Kotlin
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;
    }
};

3.9. 嵌入式数据库支持

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

3.9.1. 为什么要使用嵌入式数据库?

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

3.9.2. 使用 Spring XML 创建嵌入式数据库

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

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

上述配置将创建一个嵌入式 HSQL 数据库,该数据库填充了来自 类路径根目录中的 和 资源。此外,作为 最佳做法是为嵌入式数据库分配一个唯一生成的名称。这 嵌入式数据库作为 Bean 类型提供给 Spring 容器,然后可以根据需要将其注入到数据访问对象中。schema.sqltest-data.sqljavax.sql.DataSource

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

该类提供了一个流畅的 API,用于构造嵌入式 以编程方式建立数据库。当您需要在 独立环境或独立集成测试,如以下示例所示:EmbeddedDatabaseBuilder

爪哇岛
Kotlin
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 创建嵌入式数据库 配置,如以下示例所示:EmbeddedDatabaseBuilder

爪哇岛
Kotlin
@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();
    }
}

3.9.4. 选择嵌入式数据库类型

本节介绍如何从 Spring 的三个嵌入式数据库中选择一个 支持。它包括以下主题:

使用 HSQL

Spring 支持 HSQL 1.8.0 及更高版本。如果没有类型,则 HSQL 是默认的嵌入式数据库 明确指定。要显式指定 HSQL,请将标记的属性设置为 。如果使用构建器 API,请使用 调用该方法。typeembedded-databaseHSQLsetType(EmbeddedDatabaseType)EmbeddedDatabaseType.HSQL

使用 H2

Spring 支持 H2 数据库。要启用 H2,请将标记的属性设置为 。如果使用构建器 API,请使用 调用该方法。typeembedded-databaseH2setType(EmbeddedDatabaseType)EmbeddedDatabaseType.H2

使用 Derby

Spring 支持 Apache Derby 10.5 及更高版本。要启用 Derby,请将标记的属性设置为 。如果您使用构建器 API, 使用 调用该方法。typeembedded-databaseDERBYsetType(EmbeddedDatabaseType)EmbeddedDatabaseType.DERBY

3.9.5. 使用嵌入式数据库测试数据访问逻辑

嵌入式数据库提供了一种轻量级方法来测试数据访问代码。下一个示例是 使用嵌入式数据库的数据访问集成测试模板。使用这样的模板 当嵌入式数据库不需要在测试中重用时,对于一次性数据库很有用 类。但是,如果您希望创建在测试套件中共享的嵌入式数据库, 考虑使用 Spring TestContext 框架和 在 Spring 中将嵌入式数据库配置为 Bean,如上所述 使用 Spring XML 创建嵌入式数据库和以编程方式创建嵌入式数据库。以下列表 显示测试模板:ApplicationContext

爪哇岛
Kotlin
public class DataAccessIntegrationTestTemplate {

    private EmbeddedDatabase db;

    @BeforeEach
    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( /* ... */ );
    }

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

}

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

开发团队经常会遇到嵌入式数据库错误,如果他们的测试套件 无意中尝试重新创建同一数据库的其他实例。这可以 如果 XML 配置文件或类负责,则很容易发生 用于创建嵌入式数据库,然后重用相应的配置 在同一测试套件(即同一 JVM 中)中的多个测试场景中 process) — 例如,针对嵌入式数据库的集成测试,其配置仅在于哪个 Bean 定义不同 配置文件处于活动状态。@ConfigurationApplicationContext

造成此类错误的根本原因是 Spring 的(使用 在内部,通过 XML 命名空间元素和 for Java 配置)将嵌入式数据库的名称设置为 如果未另行指定。对于 的情况,, 嵌入式数据库通常被分配一个与 Bean 相等的名称(通常, 像这样的东西)。因此,随后尝试创建嵌入式数据库 不生成新数据库。相反,会重用相同的 JDBC 连接 URL, 并且尝试创建新的嵌入式数据库实际上指向现有的 从相同配置创建的嵌入式数据库。EmbeddedDatabaseFactory<jdbc:embedded-database>EmbeddedDatabaseBuildertestdb<jdbc:embedded-database>iddataSource

为了解决这个常见问题,Spring Framework 4.2 支持生成 嵌入式数据库的唯一名称。若要启用生成的名称,请使用 以下选项。

  • EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()

  • EmbeddedDatabaseBuilder.generateUniqueName()

  • <jdbc:embedded-database generate-name="true" …​ >

3.9.7. 扩展嵌入式数据库支持

您可以通过两种方式扩展 Spring JDBC 嵌入式数据库支持:

  • 实现以支持新的嵌入式数据库类型。EmbeddedDatabaseConfigurer

  • 实现以支持新的实现,例如 连接池,用于管理嵌入式数据库连接。DataSourceFactoryDataSource

我们鼓励你在 GitHub Issues 上为 Spring 社区贡献扩展。

3.10. 初始化一个DataSource

该软件包提供对初始化的支持 现有的 .嵌入式数据库支持提供了一个用于创建 并为应用程序初始化 a。但是,有时可能需要初始化 在某处服务器上运行的实例。org.springframework.jdbc.datasource.initDataSourceDataSource

3.10.1. 使用 Spring XML 初始化数据库

如果要初始化数据库,并且可以提供对 Bean 的引用,则可以在命名空间中使用标记:DataSourceinitialize-databasespring-jdbc

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

前面的示例对数据库运行两个指定的脚本。第一个 脚本创建一个架构,第二个脚本使用测试数据集填充表。脚本 locations 也可以是带有通配符的模式,采用通常用于资源的 Ant 样式 在 Spring 中(例如,)。如果您使用 模式,脚本按其 URL 或文件名的词法顺序运行。classpath*:/com/foo/**/sql/*-data.sql

数据库初始值设定项的默认行为是无条件运行提供的 脚本。这可能并不总是你想要的——例如,如果你运行 针对已包含测试数据的数据库的脚本。可能性 通过遵循常见模式(如前所示)可以减少意外删除数据的情况 首先创建表,然后插入数据。如果出现以下情况,第一步将失败 这些表已存在。

但是,为了更好地控制现有数据的创建和删除,XML 命名空间提供了一些附加选项。第一个是用于切换 初始化打开和关闭。您可以根据环境进行设置(例如拉取 来自系统属性或环境 Bean 的布尔值)。下面的示例从系统属性中获取一个值:

<jdbc:initialize-database data-source="dataSource"
    enabled="#{systemProperties.INITIALIZE_DATABASE}"> (1)
    <jdbc:script location="..."/>
</jdbc:initialize-database>
1 从名为 的系统属性中获取 的值。enabledINITIALIZE_DATABASE

控制现有数据发生情况的第二种选择是更宽容 失败。为此,您可以控制初始值设定项忽略某些 它从脚本运行的 SQL 中的错误,如以下示例所示:

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

在前面的示例中,我们说我们期望有时运行脚本 针对空数据库,并且脚本中有一些语句 因此,会失败。所以失败的SQL语句将被忽略,但其他失败的 将导致异常。如果您的 SQL 方言不支持(或类似),但您希望无条件删除之前的所有测试数据,这将非常有用 重新创建它。在这种情况下,第一个脚本通常是一组语句, 后跟一组语句。DROPDROPDROP …​ IF EXISTSDROPCREATE

该选项可以设置为(默认值)、(忽略失败) drops),或(忽略所有失败)。ignore-failuresNONEDROPSALL

每个语句都应用换行符分隔,如果字符不是 完全存在于脚本中。您可以全局控制它或逐个脚本控制它,因为 以下示例显示:;;

<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;

在此示例中,这两个脚本用作语句分隔符,并且仅 用途 .此配置指定默认分隔符 并覆盖脚本的默认值。test-data@@db-schema.sql;@@db-schema

如果需要比从 XML 命名空间获得的更多控制,则可以直接使用 并将其定义为应用程序中的组件。DataSourceInitializer

初始化依赖于数据库的其他组件

一大类应用程序(那些直到 Spring 上下文具有 started) 可以使用数据库初始值设定项,无需进一步操作 并发症。如果您的应用程序不是其中之一,您可能需要阅读其余部分 本节的内容。

数据库初始值设定项依赖于实例并运行脚本 在其初始化回调中提供(类似于 XML Bean 中的 定义,组件中的方法,或组件中实现的方法)。如果其他 Bean 依赖于 相同的数据源,并在初始化回调中使用该数据源,其中 可能是一个问题,因为数据尚未初始化。一个常见的例子 这是一个缓存,它急切地初始化并从应用程序上的数据库加载数据 启动。DataSourceinit-method@PostConstructafterPropertiesSet()InitializingBean

若要解决此问题,您有两种选择: 更改缓存初始化策略 到稍后的阶段,或确保首先初始化数据库初始值设定项。

如果应用程序在您的控制范围内,则更改缓存初始化策略可能很容易,否则则不然。 有关如何实现此目的的一些建议包括:

  • 使缓存在首次使用时延迟初始化,从而改进应用程序启动 时间。

  • 使用缓存或用于初始化缓存的单独组件实现或 。当应用程序上下文启动时,您可以 通过设置其标志自动启动 A,您可以 通过调用封闭上下文手动启动 A。LifecycleSmartLifecycleSmartLifecycleautoStartupLifecycleConfigurableApplicationContext.start()

  • 使用 Spring 或类似的自定义观察器机制来触发 缓存初始化。 总是由上下文发布 它已准备好使用(在所有 bean 初始化之后),因此这通常很有用 钩子(这是默认的工作方式)。ApplicationEventContextRefreshedEventSmartLifecycle

确保首先初始化数据库初始值设定项也很容易。关于如何实现这一点的一些建议包括:

  • 依赖于 Spring 的默认行为,即 bean 是 按注册顺序初始化。您可以通过采用通用的 在 XML 配置中练习一组元素,这些元素对 应用程序模块,并确保数据库和数据库初始化是 首先列出。BeanFactory<import/>

  • 分离使用它的业务组件并控制其 通过将它们放在单独的实例中来启动顺序(例如, 父上下文包含 ,子上下文包含业务 组件)。这种结构在 Spring Web 应用程序中很常见,但可以更多 普遍适用。DataSourceApplicationContextDataSource

4. 使用 R2DBC 访问数据

R2DBC(“反应式关系数据库连接”)是社区驱动的 规范使用反应式模式标准化对 SQL 数据库的访问。

4.1. 软件包层次结构

Spring Framework 的 R2DBC 抽象框架由两个不同的包组成:

  • core:包中包含类以及各种相关类。请参阅使用 R2DBC 核心类控制基本的 R2DBC 处理和错误处理org.springframework.r2dbc.coreDatabaseClient

  • connection:包中包含一个实用程序类 便于访问和各种简单的实现 可用于测试和运行未修改的 R2DBC。请参见控制数据库连接org.springframework.r2dbc.connectionConnectionFactoryConnectionFactory

4.2. 使用 R2DBC 核心类来控制基本的 R2DBC 处理和错误处理

本节介绍如何使用 R2DBC 核心类来控制基本的 R2DBC 处理, 包括错误处理。它包括以下主题:

4.2.1. 使用DatabaseClient

DatabaseClient是 R2DBC 核心包中的中心类。它处理 创建和释放资源,这有助于避免常见错误,例如 忘记关闭连接。它执行核心 R2DBC 的基本任务 工作流(例如语句创建和执行),留下应用程序代码提供 SQL 并提取结果。班级:DatabaseClient

  • 运行 SQL 查询

  • 更新语句和存储过程调用

  • 对实例执行迭代Result

  • 捕获 R2DBC 异常,并将其转换为信息量更大的通用异常 包中定义的层次结构。(请参阅一致的异常层次结构org.springframework.dao

客户端有一个功能流畅的 API,使用响应式类型进行声明性组合。

当你使用 for 你的代码时,你只需要实现接口,给它们一个明确定义的约定。 给定类提供的 ,回调会创建一个 .映射函数也是如此 提取结果。DatabaseClientjava.util.functionConnectionDatabaseClientFunctionPublisherRow

您可以通过直接实例化在 DAO 实现中使用 替换为引用,或者您可以在 Spring IoC 容器中对其进行配置 并将其作为 Bean 参考提供给 DAO。DatabaseClientConnectionFactory

创建对象的最简单方法是通过静态工厂方法,如下所示:DatabaseClient

爪哇岛
Kotlin
DatabaseClient client = DatabaseClient.create(connectionFactory);
应始终在 Spring IoC 中配置为 bean 容器。ConnectionFactory

上述方法使用默认设置创建。DatabaseClient

您也可以从 获取实例。 您可以通过调用以下方法自定义客户端:BuilderDatabaseClient.builder()

  • ….bindMarkers(…):提供特定的配置名称 参数到数据库绑定标记转换。BindMarkersFactory

  • ….executeFunction(…):设置对象获取方式 跑。ExecuteFunctionStatement

  • ….namedParameters(false):禁用命名参数扩展。默认启用。

方言由 BindMarkersFactoryResolver 从 进行解析,通常通过检查 .
你可以让 Spring 通过注册一个 通过 实现的类。 发现绑定标记提供程序实现 使用 Spring 的 .
ConnectionFactoryConnectionFactoryMetadataBindMarkersFactoryorg.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProviderMETA-INF/spring.factoriesBindMarkersFactoryResolverSpringFactoriesLoader

目前支持的数据库有:

  • H2型

  • MariaDB数据库

  • Microsoft SQL Server

  • MySQL数据库

  • Postgres的

此类发出的所有 SQL 都记录在类别下的级别 对应于客户端实例的完全限定类名(通常为 )。此外,每次执行都会在 帮助调试的响应式序列。DEBUGDefaultDatabaseClient

以下各节提供了一些用法示例。这些示例 不是 公开的所有功能的详尽列表。 有关这一点,请参阅附带的 javadocDatabaseClientDatabaseClient

执行语句

DatabaseClient提供运行语句的基本功能。 以下示例显示了需要包含的内容,以实现最小但功能齐全的内容 创建新表的代码:

爪哇岛
Kotlin
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
        .then();

DatabaseClient专为方便、流畅的使用而设计。 它公开了 执行规范。上面的示例用于返回一个完成,该完成在查询(或查询,如果 SQL 查询包含 多个语句)完成。then()Publisher

execute(…)接受 SQL 查询字符串或查询,以将实际查询创建推迟到执行。Supplier<String>
查询 (SELECT)

SQL 查询可以通过对象或受影响的行数返回值。 可以返回更新的行数或行本身, 取决于发出的查询。RowDatabaseClient

以下查询从表中获取 and 列:idname

爪哇岛
Kotlin
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
        .fetch().first();

以下查询使用绑定变量:

爪哇岛
Kotlin
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
        .bind("fn", "Joe")
        .fetch().first();

您可能已经注意到上面示例中的用法。 是一个 延续运算符,用于指定要使用的数据量。fetch()fetch()

调用返回结果中的第一行,并丢弃剩余的行。 您可以使用以下运算符使用数据:first()

  • first()返回整个结果的第一行。其 Kotlin 协程变体 为不可为 null 的返回值命名,如果该值是可选的。awaitSingle()awaitSingleOrNull()

  • one()只返回一个结果,如果结果包含更多行,则返回失败。 使用 Kotlin 协程,只针对一个值,或者如果该值可能是 .awaitOne()awaitOneOrNull()null

  • all()返回结果的所有行。使用 Kotlin 协程时,请使用 .flow()

  • rowsUpdated()返回受影响的行数 (// count)。它的 Kotlin 协程变体被命名为 。INSERTUPDATEDELETEawaitRowsUpdated()

在不指定更多映射详细信息的情况下,查询将返回表格结果 因为其键是映射到其列值的不区分大小写的列名。Map

您可以通过提供 获取 调用,以便它可以返回任意值(奇异值、 集合和地图,以及对象)。Function<Row, T>Row

以下示例提取该列并发出其值:name

爪哇岛
Kotlin
Flux<String> names = client.sql("SELECT name FROM person")
        .map(row -> row.get("name", String.class))
        .all();
怎么样?null

关系数据库结果可以包含值。 Reactive Streams 规范禁止发出值。 该要求要求在提取器功能中正确处理。 虽然可以从 获取值,但不得发出值。您必须将任何值包装在对象中(例如,对于奇异值),以确保永远不会直接返回值 通过您的提取器功能。nullnullnullnullRownullnullOptionalnull

将 (, , 和 ) 更新为INSERTUPDATEDELETEDatabaseClient

修改语句的唯一区别是,这些语句通常 不要返回表格数据,以便用于使用结果。rowsUpdated()

下面的示例演示一个返回数字的语句 更新的行数:UPDATE

爪哇岛
Kotlin
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
        .bind("fn", "Joe")
        .fetch().rowsUpdated();
将值绑定到查询

典型的应用程序需要参数化 SQL 语句来选择或 根据某些输入更新行。这些是典型的语句 受子句或接受的语句的约束 输入参数。参数化语句在以下情况下存在 SQL 注入风险 参数未正确转义。 利用 R2DBC 的 API 来消除查询参数的 SQL 注入风险。 您可以使用运算符提供参数化 SQL 语句 并将参数绑定到实际的 .然后,R2DBC 驱动程序将运行 使用预准备语句和参数替换的语句。SELECTWHEREINSERTUPDATEDatabaseClientbindexecute(…)Statement

参数绑定支持两种绑定策略:

  • 按索引,使用从零开始的参数索引。

  • 按名称,使用占位符名称。

以下示例显示了查询的参数绑定:

db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
    .bind("id", "joe")
    .bind("name", "Joe")
    .bind("age", 34);
R2DBC 本机绑定标记

R2DBC 使用数据库本机绑定标记,这些标记取决于实际的数据库供应商。 例如,Postgres 使用索引标记,例如 、 、 。 另一个示例是 SQL Server,它使用以 为前缀的命名绑定标记。$1$2$n@

这与 JDBC 不同,JDBC 需要作为绑定标记。 在 JDBC 中,实际驱动程序将绑定标记转换为数据库原生 标记作为其语句执行的一部分。??

Spring Framework 的 R2DBC 支持允许您使用本机绑定标记或命名绑定 带有语法的标记。:name

命名参数支持利用实例来扩展命名 参数在查询执行时添加到本机绑定标记,这为您提供了 跨各种数据库供应商的一定程度的查询可移植性。BindMarkersFactory

查询预处理器将命名参数展开到一系列绑定中 标记,以消除基于参数数创建动态查询的需要。 嵌套对象数组被扩展为允许使用(例如)选择列表。Collection

请考虑以下查询:

SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))

上述查询可以按如下方式参数化和运行:

爪哇岛
Kotlin
List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann",  50});

client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
    .bind("tuples", tuples);
选择列表的使用取决于供应商。

以下示例显示了使用谓词的更简单的变体:IN

爪哇岛
Kotlin
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
    .bind("ages", Arrays.asList(35, 50));
R2DBC 本身不支持类似 Collection 的值。不过 在上面的示例中扩展给定参数适用于命名参数 在 Spring 的 R2DBC 支持中,例如用于如上所示的子句。 但是,插入或更新数组类型的列(例如在 Postgres 中) 需要基础 R2DBC 驱动程序支持的数组类型: 通常是一个 Java 数组,例如 以更新列。 不要将 Sort 等作为数组参数传递。ListINString[]text[]Collection<String>
语句筛选器

有时,您需要在运行之前对实际选项进行微调。注册筛选器 () 通过拦截和 modify 语句的执行,如以下示例所示:StatementStatementStatementFilterFunctionDatabaseClient

爪哇岛
Kotlin
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
    .bind("name", …)
    .bind("state", …);

DatabaseClient还暴露了简化的重载接受:filter(…)Function<Statement, Statement>

爪哇岛
Kotlin
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter(statement -> s.returnGeneratedValues("id"));

client.sql("SELECT id, name, state FROM table")
    .filter(statement -> s.fetchSize(25));

StatementFilterFunction实现允许对对象进行过滤和过滤。StatementResult

DatabaseClient最佳实践

类的实例一旦配置,就是线程安全的。这是 很重要,因为这意味着您可以配置单个实例,然后将此共享引用安全地注入到多个 DAO(或存储库)中。 是有状态的,因为它维护了对 , 但这种状态不是对话状态。DatabaseClientDatabaseClientDatabaseClientConnectionFactory

使用该类时的常见做法是在 Spring 配置文件中配置 dependency-inject 将 Bean 共享到您的 DAO 类中。创建于 的 setter 。这导致类似于以下内容的 DAO:DatabaseClientConnectionFactoryConnectionFactoryDatabaseClientConnectionFactory

爪哇岛
Kotlin
public class R2dbcCorporateEventDao implements CorporateEventDao {

    private DatabaseClient databaseClient;

    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.databaseClient = DatabaseClient.create(connectionFactory);
    }

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

显式配置的替代方法是使用组件扫描和注释 支持依赖注入。在这种情况下,您可以对类进行注释(这使其成为组件扫描的候选对象)并注释 setter 方法。以下示例演示如何执行此操作:@ComponentConnectionFactory@Autowired

爪哇岛
Kotlin
@Component (1)
public class R2dbcCorporateEventDao implements CorporateEventDao {

    private DatabaseClient databaseClient;

    @Autowired (2)
    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.databaseClient = DatabaseClient.create(connectionFactory); (3)
    }

    // R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 用 批注类。@Component
2 用 注释 setter 方法。ConnectionFactory@Autowired
3 使用 创建一个新的。DatabaseClientConnectionFactory

无论您选择使用上述哪种模板初始化样式(或 not),很少需要为每个类创建一个新实例 要运行 SQL 的时间。配置后,实例是线程安全的。 如果您的应用程序访问多个 数据库中,您可能需要多个实例,这需要多个实例,随后需要多个不同配置的实例。DatabaseClientDatabaseClientDatabaseClientConnectionFactoryDatabaseClient

4.3. 检索自动生成的密钥

INSERT语句可能会在向表中插入行时生成键 定义自动递增或标识列。要完全控制 要生成的列名,只需注册一个 请求为所需列生成的键。StatementFilterFunction

爪哇岛
Kotlin
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter(statement -> s.returnGeneratedValues("id"))
        .map(row -> row.get("id", Integer.class))
        .first();

// generatedId emits the generated key once the INSERT statement has finished

4.4. 控制数据库连接

本节包括:

4.4.1. 使用ConnectionFactory

Spring 通过 . A 是 R2DBC 规范的一部分,是一个常见的入口点 对于司机。它允许容器或框架隐藏连接池 以及应用程序代码中的事务管理问题。作为开发人员, 您无需知道有关如何连接到数据库的详细信息。那就是 设置 .你 在开发和测试代码时,很可能会同时担任这两个角色,但您不会 必须知道生产数据源是如何配置的。ConnectionFactoryConnectionFactoryConnectionFactory

当您使用 Spring 的 R2DBC 层时,您可以使用 由第三方提供的连接池实现。一个流行的 实现为 R2DBC 池 ()。Spring 中的实现 分发仅用于测试目的,不提供池化。r2dbc-pool

要配置 :ConnectionFactory

  1. 获取连接,因为您通常获取 R2DBC 。ConnectionFactoryConnectionFactory

  2. 提供 R2DBC URL (有关正确的值,请参阅驱动程序的文档)。

以下示例演示如何配置:ConnectionFactory

爪哇岛
Kotlin
ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");

4.4.2. 使用ConnectionFactoryUtils

该类是一个方便而强大的帮助程序类 它提供了从中获取连接和关闭连接(如有必要)的方法。ConnectionFactoryUtilsstaticConnectionFactory

例如,它支持与订阅者绑定的连接。ContextR2dbcTransactionManager

4.4.3. 使用SingleConnectionFactory

该类是接口的实现,该接口包装了每次使用后未关闭的单个。SingleConnectionFactoryDelegatingConnectionFactoryConnection

如果任何客户端代码在池连接的假设下调用(如使用 持久性工具),应将该属性设置为 。此设置 返回包装物理连接的关闭抑制代理。请注意,您可以 不再将其强制转换为本机或类似对象。closesuppressClosetrueConnection

SingleConnectionFactory主要是一个测试类,可用于特定要求 例如,如果您的 R2DBC 驱动程序允许此类使用,则流水线。 与 pooled 相比,它始终重用相同的连接,从而避免 过度创建物理连接。ConnectionFactory

4.4.4. 使用TransactionAwareConnectionFactoryProxy

TransactionAwareConnectionFactoryProxy是目标的代理。 代理包装该目标,以增加对 Spring 管理的事务的感知。ConnectionFactoryConnectionFactory

如果使用未以其他方式集成的 R2DBC 客户端,则需要使用此类 支持 Spring 的 R2DBC。在这种情况下,您仍然可以使用此客户端,并且 同时,让这个客户端参与 Spring 管理的事务。一般是 最好集成具有适当访问权限的 R2DBC 客户端以进行资源管理。ConnectionFactoryUtils

有关详细信息,请参阅 TransactionAwareConnectionFactoryProxy javadoc。

4.4.5. 使用R2dbcTransactionManager

该类是 单个 R2DBC 数据源。它绑定来自指定连接工厂的 R2DBC 连接 到订阅者,可能允许每个订阅者连接一个订阅者连接 连接工厂。R2dbcTransactionManagerReactiveTransactionManagerContext

需要应用程序代码才能通过 检索 R2DBC 连接,而不是 R2DBC 的标准。ConnectionFactoryUtils.getConnection(ConnectionFactory)ConnectionFactory.create()

所有框架类(例如 )都隐式使用此策略。 如果不与此事务管理器一起使用,则查找策略的行为与普通策略完全相同。 因此,它可以在任何情况下使用。DatabaseClient

该类支持应用于连接的自定义隔离级别。R2dbcTransactionManager

5. 对象关系映射 (ORM) 数据访问

本节介绍使用对象关系映射 (ORM) 时的数据访问。

5.1. 使用 Spring 进行 ORM 介绍

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

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

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

  • 测试更简单。Spring 的 IoC 方法使交换实现变得容易 以及 Hibernate 实例、JDBC 实例、事务管理器和映射对象实现(如果需要)的配置位置。这 反过来,测试每一段与持久性相关的代码变得更加容易 隔离。SessionFactoryDataSource

  • 常见的数据访问异常。Spring 可以包装 ORM 工具中的异常, 将它们从专有(可能已检查)异常转换为通用运行时层次结构。此功能允许您处理大多数持久性 异常,这些异常是不可恢复的,仅在适当的层中,没有 烦人的样板捕获、抛出和异常声明。你仍然可以陷阱 并根据需要处理异常。请记住,JDBC 异常(包括 特定于数据库的方言)也会转换为相同的层次结构,这意味着您可以 在一致的编程模型中使用 JDBC 执行一些操作。DataAccessException

  • 常规资源管理。Spring 应用程序上下文可以处理该位置 以及 Hibernate 实例、JPA 实例、JDBC 实例和其他相关资源的配置。这使得这些 值易于管理和更改。Spring 提供高效、简单和安全的处理 持久性资源。例如,使用 Hibernate 的相关代码通常需要 使用相同的 Hibernate 来确保效率和正确的事务处理。 Spring 可以很容易地透明地创建和绑定 a 到当前线程, 通过通过 Hibernate 暴露电流。因此,春天 解决了典型 Hibernate 使用的许多长期问题,适用于任何本地或 JTA 交易环境。SessionFactoryEntityManagerFactoryDataSourceSessionSessionSessionSessionFactory

  • 集成事务管理。你可以用声明式来包装你的 ORM 代码, 面向方面的编程 (AOP) 样式方法拦截器,通过注解或显式配置事务 AOP 建议 XML 配置文件。在这两种情况下,事务语义和异常处理 (回滚等)为您处理。如资源和事务管理中所述, 您还可以交换各种事务管理器,而不会影响与 ORM 相关的代码。 例如,您可以使用相同的完整服务在本地事务和 JTA 之间切换 (例如声明性事务)在这两种方案中都可用。此外 JDBC相关代码可以与用于执行ORM的代码完全集成。 这对于不适合 ORM 的数据访问(例如批处理和 BLOB 流式处理),但这仍然需要与 ORM 操作共享公共事务。@Transactional

提供更全面的 ORM 支持,包括对替代数据库的支持 MongoDB 等技术,您可能想查看 Spring Data 项目套件。如果你是 JPA 用户,即 Getting Started Access 来自 https://spring.io 的 JPA 指南提供了很好的介绍。

5.2. 一般 ORM 集成注意事项

本节重点介绍适用于所有 ORM 技术的注意事项。 Hibernate 部分提供了更多详细信息,并展示了这些功能和 具体上下文中的配置。

Spring 的 ORM 集成的主要目标是清晰的应用程序分层(包含任何数据) 访问和事务技术)和应用程序对象的松散耦合 — 否 不再有更多业务服务对数据访问或事务策略的依赖 硬编码资源查找,不再有难以替换的单例,不再有自定义服务 登记处。目标是采用一种简单且一致的方法来连接应用程序对象,同时保持 它们尽可能可重用且不受容器依赖性的影响。所有个人 数据访问功能可以单独使用,但可以很好地与 Spring 的 应用程序上下文概念,提供基于 XML 的配置和交叉引用 不需要 Spring 感知的普通 JavaBean 实例。在典型的 Spring 应用程序中, 许多重要的对象都是 JavaBeans:数据访问模板、数据访问对象、 事务管理器,使用数据访问对象和事务的业务服务 管理器、Web 视图解析程序、使用业务服务的 Web 控制器等。

5.2.1. 资源和事务管理

典型的业务应用程序充斥着重复的资源管理代码。 许多项目试图发明自己的解决方案,有时会牺牲适当的处理 为方便编程而发生的故障。Spring 提倡简单的解决方案,以适当的方式 资源处理,即在JDBC的情况下通过模板化进行IoC并应用AOP ORM 技术的拦截器。

基础结构提供适当的资源处理和适当的转换 未经检查的基础结构异常层次结构的特定 API 例外。春天 引入了适用于任何数据访问策略的 DAO 异常层次结构。对于直接 JDBC,上一节中提到的类提供连接处理和到层次结构的正确转换,包括特定于数据库的 SQL 错误的转换 代码转换为有意义的异常类。对于 ORM 技术,请参阅下一节,了解如何获取相同的异常 翻译优势。JdbcTemplateSQLExceptionDataAccessException

当涉及到事务管理时,该类与 Spring 挂钩 事务支持,同时支持 JTA 和 JDBC 事务,通过各自的 Spring 事务管理器。对于受支持的 ORM 技术,Spring 提供了 Hibernate 通过 Hibernate 和 JPA 事务管理器以及 JTA 支持提供 JPA 支持。 有关事务支持的详细信息,请参阅事务管理一章。JdbcTemplate

5.2.2. 异常转换

当您在 DAO 中使用 Hibernate 或 JPA 时,您必须决定如何处理持久性 技术的本机异常类。DAO 抛出一个 or 的子类,具体取决于技术。这些异常都是运行时的 例外,不必声明或捕获。您可能还需要处理 和 .这意味着调用方只能 将异常视为通常致命的异常,除非它们想要依赖于持久性 技术自身的异常结构。捕捉特定原因(如乐观的 锁定失败)如果不将调用方与实现策略绑定,则是不可能的。 对于强基于 ORM 或 不需要任何特殊的例外处理(或两者兼而有之)。但是,Spring 允许例外 翻译通过注释透明地应用。以下 示例(一个用于 Java 配置,一个用于 XML 配置)显示了如何执行此操作:HibernateExceptionPersistenceExceptionIllegalArgumentExceptionIllegalStateException@Repository

爪哇岛
Kotlin
@Repository
public class ProductDaoImpl implements ProductDao {

    // class body here...

}
<beans>

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

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

</beans>

后处理器会自动查找所有异常转换器(实现 接口),并建议所有标有注解的 bean,以便发现的翻译器可以拦截和应用 对抛出的异常进行适当的翻译。PersistenceExceptionTranslator@Repository

总之,您可以基于普通持久化技术的 API 和 注解,同时仍然受益于 Spring 管理的事务、依赖关系 注入,并透明异常转换(如果需要)到 Spring 的自定义 异常层次结构。

5.3. 休眠

我们从 Spring 环境中的 Hibernate 5 开始, 用它来演示 Spring 集成 OR 映射器的方法。 本节详细介绍了许多问题,并展示了 DAO 的不同变体 实现和事务划分。这些模式中的大多数都可以直接 转换为所有其他受支持的 ORM 工具。本章后面的部分则 介绍其他 ORM 技术并展示简要示例。

从 Spring Framework 5.3 开始,Spring 需要 Hibernate ORM 5.2+ 用于 Spring 以及原生 Hibernate 设置。 强烈建议使用 Hibernate ORM 5.4 作为新启动的应用程序。 要与 一起使用,Hibernate Search 需要升级到 5.11.6。HibernateJpaVendorAdapterSessionFactoryHibernateJpaVendorAdapter

5.3.1. 在 Spring 容器中设置SessionFactory

为了避免将应用程序对象绑定到硬编码的资源查找,您可以定义 资源(例如 JDBC 或 Hibernate )作为 弹簧容器。需要访问资源的应用程序对象接收引用 通过 Bean 引用到此类预定义实例,如 DAO 中所示 定义在下一节中。DataSourceSessionFactory

以下摘自 XML 应用程序上下文定义,演示如何设置 JDBC 和它之上的 Hibernate:DataSourceSessionFactory

<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 切换到位于 JNDI(通常由应用服务器管理)只是一个问题 配置,如以下示例所示:BasicDataSourceDataSource

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

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

Spring 还提供了一个变体,可以无缝集成 具有样式配置和编程设置(不涉及)。LocalSessionFactoryBuilder@BeanFactoryBean

两者和支持背景 引导,Hibernate 初始化与应用程序并行运行 给定引导执行程序(例如 )。 On ,可通过酒店获得。在编程上,有一个重载的方法,它接受引导执行器参数。LocalSessionFactoryBeanLocalSessionFactoryBuilderSimpleAsyncTaskExecutorLocalSessionFactoryBeanbootstrapExecutorLocalSessionFactoryBuilderbuildSessionFactory

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

5.3.2. 基于 Plain Hibernate API 实现 DAO

Hibernate 有一个叫做上下文会话的功能,其中 Hibernate 本身管理 每笔交易一个电流。这大致相当于 Spring 的 每个事务同步一个 Hibernate。对应的 DAO 实现类似于以下示例,基于普通的 Hibernate API:SessionSession

爪哇岛
Kotlin
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

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

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

这种风格类似于 Hibernate 参考文档和示例的风格, 除了保存 in an instance 变量。我们强烈建议 这种基于实例的设置,而不是老式的类 Hibernate 的 CaveatEmptor 示例应用程序。(通常,除非绝对必要,否则不要在变量中保留任何资源。SessionFactorystaticHibernateUtilstatic

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

<beans>

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

</beans>

这种 DAO 风格的主要优点是它只依赖于 Hibernate API。无导入 任何 Spring 类都是必需的。这是从非侵入性上吸引人的 透视,并且对 Hibernate 开发人员来说可能感觉更自然。

但是,DAO 抛出普通(这是未选中的,因此它没有 to be declared or caught),这意味着调用方只能将异常视为 通常是致命的——除非他们想依赖 Hibernate 自己的异常层次结构。 如果没有,就不可能捕获特定原因(例如乐观锁定失败) 将调用方与实施策略联系起来。这种权衡可能是可以接受的 强基于 Hibernate 的应用程序不需要任何特殊例外 治疗,或两者兼而有之。HibernateException

幸运的是,Spring 支持任何 Spring 事务策略的 Hibernate 方法, 返回当前 Spring 管理的事务,即使有 .该方法的标准行为保持不变 返回与正在进行的 JTA 事务(如果有)关联的当前值。 无论您使用的是 Spring、EJB 容器管理事务 (CMT) 还是 JTA,此行为都适用。LocalSessionFactoryBeanSessionFactory.getCurrentSession()SessionHibernateTransactionManagerSessionJtaTransactionManager

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

5.3.3. 声明式事务划分

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

在继续之前,我们强烈建议您阅读声明式事务管理(如果您尚未这样做)。

您可以使用注释对服务层进行注释,并指示 Spring 容器,用于查找这些注解并为 这些带注释的方法。以下示例演示如何执行此操作:@Transactional

爪哇岛
Kotlin
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();
    }
}

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

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

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

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

    <tx:annotation-driven/>

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

</beans>

5.3.4. 程序化交易划分

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

<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>
爪哇岛
Kotlin
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 允许抛出任何已检查的应用程序异常 替换为回调代码,而 被限制为未选中 回调中的异常。 在以下情况下触发回滚 未经检查的应用程序异常,或者事务被标记为仅回滚 应用程序(通过设置 )。默认情况下,行为方式相同,但允许每个方法使用可配置的回滚策略。TransactionInterceptorTransactionTemplateTransactionTemplateTransactionStatusTransactionInterceptor

5.3.5. 事务管理策略

两者都委托实际事务 处理到实例(可以是 (对于单个 Hibernate ),通过使用 a 底层)或 (委托给 容器的 JTA 子系统)用于 Hibernate 应用程序。您甚至可以使用自定义实现。从本机 Hibernate 事务切换 对 JTA 的管理(例如,当面对某些分布式事务需求时 应用程序的部署)只是一个配置问题。您可以替换 带有 Spring 的 JTA 事务实现的 Hibernate 事务管理器。双 事务划分和数据访问代码无需更改即可工作,因为它们 使用通用事务管理 API。TransactionTemplateTransactionInterceptorPlatformTransactionManagerHibernateTransactionManagerSessionFactoryThreadLocalSessionJtaTransactionManagerPlatformTransactionManager

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

两者都允许适当的 使用 Hibernate 进行 JVM 级缓存处理,无需特定于容器的事务管理器 查找或 JCA 连接器(如果不使用 EJB 启动事务)。HibernateTransactionManagerJtaTransactionManager

HibernateTransactionManager可以将 Hibernate JDBC 导出为普通 JDBC 特定 .此功能允许高级别 使用混合 Hibernate 和 JDBC 数据访问进行事务划分,完全无需 JTA,前提是您只能访问一个数据库。 自然而然 如果已使用类的 through 属性设置了传入的 Hibernate 事务,则将 Hibernate 事务公开为 JDBC 事务。或者,可以通过类的属性显式指定应公开事务的内容。ConnectionDataSourceHibernateTransactionManagerSessionFactoryDataSourcedataSourceLocalSessionFactoryBeanDataSourcedataSourceHibernateTransactionManager

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

您可以在容器管理的 JNDI 和本地定义的 JNDI 之间切换 无需更改任何一行应用程序代码。是否保留 容器中或本地应用程序中的资源定义主要是 您使用的交易策略的问题。与 Spring 定义的本地相比,手动注册的 JNDI 不提供任何 好处。通过 Hibernate 的 JCA 连接器部署 参与 Java EE 服务器管理基础架构的附加值,但确实如此 除此之外,不要增加实际价值。SessionFactorySessionFactorySessionFactorySessionFactory

Spring 的事务支持不绑定到容器。当配置任何策略时 除了 JTA 之外,事务支持还可以在独立或测试环境中工作。 特别是在单数据库事务的典型情况下,Spring 的 single-resource 本地事务支持是 JTA 的轻量级且功能强大的替代方案。当您使用 本地 EJB 无状态会话 Bean 来驱动事务,两者都依赖于 EJB 容器和 JTA,即使您只访问单个数据库并且仅使用无状态 会话 Bean,用于通过容器管理提供声明性事务 交易。以编程方式直接使用 JTA 还需要 Java EE 环境。 JTA 不仅涉及 JTA 本身和 JNDI 实例。对于非 Spring 的、JTA 驱动的 Hibernate 事务,您有 使用 Hibernate JCA 连接器或额外的 Hibernate 事务代码,并配置正确的 JVM 级缓存。DataSourceTransactionManagerLookup

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

考虑到所有因素,如果您不使用 EJB,请坚持使用本地设置 和 Spring 的 or .你得到所有的 优点,包括适当的事务性 JVM 级缓存和分布式缓存 事务,没有容器部署的不便。JNDI注册 通过 JCA 连接器休眠仅在以下情况下增加价值 与 EJB 结合使用。SessionFactoryHibernateTransactionManagerJtaTransactionManagerSessionFactory

5.3.7. Hibernate 的虚假应用服务器警告

在一些具有非常严格实现的 JTA 环境中(目前 某些 WebLogic Server 和 WebSphere 版本),当 Hibernate 配置时没有 关于该环境的 JTA 事务管理器、虚假警告或 异常可能会显示在应用程序服务器日志中。这些警告或异常 指示正在访问的连接不再有效或 JDBC 访问无效 有效期更长,可能是因为交易不再处于活动状态。举个例子, 这是 WebLogic 的一个实际异常:XADataSource

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

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

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

  • 将 Spring Bean 传递给 Hibernate 设置。最简单的 way 是对 Bean 属性的 Bean 引用(参见 Hibernate Transaction Setup)。 然后,Spring 将相应的 JTA 策略提供给 Hibernate。JtaTransactionManagerjtaTransactionManagerLocalSessionFactoryBean

  • 你也可以显式地配置 Hibernate 的 JTA 相关属性,特别是 “hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode” 并可能在您的 “hibernateProperties” 中显示 “hibernate.transaction.jta.platform” on (有关这些属性的详细信息,请参见 Hibernate 的手册)。LocalSessionFactoryBean

本节的其余部分将介绍 和 一起发生的事件序列 没有 Hibernate 对 JTA 的意识。PlatformTransactionManager

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

  • JTA 事务提交。

  • Spring 与 JTA 事务同步,因此它是 通过 JTA 事务管理器的回调回调。JtaTransactionManagerafterCompletion

  • 除其他活动外,此同步可以触发 Spring 对 Hibernate,通过 Hibernate 的回调(用于清除 Hibernate 缓存),然后对 Hibernate 会话进行显式调用, 这会导致 Hibernate 尝试 JDBC 连接。afterTransactionCompletionclose()close()

  • 在某些环境中,此调用会触发警告或 错误,因为应用程序服务器不再认为 是可用的, 因为事务已经提交。Connection.close()Connection

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

  • JTA 事务已准备好提交。

  • Spring 与 JTA 事务同步,因此 事务由 JTA 通过回调回调 事务管理器。JtaTransactionManagerbeforeCompletion

  • Spring 知道 Hibernate 本身与 JTA 事务同步,并且 行为与上一个方案不同。特别是,它与 Hibernate 的事务性资源管理。

  • JTA 事务提交。

  • Hibernate 与 JTA 事务同步,因此事务被回调 通过 JTA 事务管理器的回调,可以 正确清除其缓存。afterCompletion

5.4. JPA协议

Spring JPA 在软件包中提供,提供 对 Java 持久性的全面支持 API 以类似于与 Hibernate 集成的方式,同时意识到 底层实现,以便提供附加功能。org.springframework.orm.jpa

5.4.1. Spring 环境中 JPA 设置的三个选项

Spring JPA 支持提供了三种方法来设置 JPA,应用程序使用这些方法获取实体管理器。EntityManagerFactory

LocalEntityManagerFactoryBean

此选项只能在简单的部署环境(如单机)中使用 应用程序和集成测试。

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

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

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

从 JNDI 获取 EntityManagerFactory

在部署到 Java EE 服务器时,可以使用此选项。查看服务器的文档 关于如何将自定义 JPA 提供程序部署到您的服务器中,从而允许不同的 provider 设置为服务器的默认值。

从 JNDI 获取 (例如,在 Java EE 环境中), 是更改 XML 配置的问题,如以下示例所示:EntityManagerFactory

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

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

在这种情况下,整个持久性单元部署,包括编织 持久类的(字节码转换)取决于 Java EE 服务器。JDBC 是通过文件中的 JNDI 位置定义的。 事务与服务器的 JTA 子系统集成。纯春 使用获得的 ,通过 依赖关系注入和管理持久性单元的事务(通常 通过 )。DataSourceMETA-INF/persistence.xmlEntityManagerEntityManagerFactoryJtaTransactionManager

如果在同一应用程序中使用多个持久性单元,则此类 Bean 名称 JNDI 检索到的持久性单元应与 应用程序用来引用它们(例如,in 和 annotations)。@PersistenceUnit@PersistenceContext

LocalContainerEntityManagerFactoryBean

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

如果你想专门配置 Hibernate 设置,一个直接的替代方案 是设置一个原生的 Hibernate 而不是一个普通的 JPA,让它与 JPA 访问代码进行交互 以及原生 Hibernate 访问代码。 有关详细信息,请参阅 JPA 交互的本机 Hibernate 设置LocalSessionFactoryBeanLocalContainerEntityManagerFactoryBean

提供对配置的完全控制,适用于以下环境 需要细粒度的定制。基于文件创建一个实例, 提供的策略和指定的 .因此, 可以在 JNDI 之外使用自定义数据源并控制编织 过程。以下示例显示了 :LocalContainerEntityManagerFactoryBeanEntityManagerFactoryLocalContainerEntityManagerFactoryBeanPersistenceUnitInfopersistence.xmldataSourceLookuploadTimeWeaverLocalContainerEntityManagerFactoryBean

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

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

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
        <mapping-file>META-INF/orm.xml</mapping-file>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>
快捷方式表示没有扫描 带注释的实体类应该出现。显式“true”值 () 也表示不扫描。 会触发扫描。 但是,我们建议省略该元素 如果要进行实体类扫描。<exclude-unlisted-classes/><exclude-unlisted-classes>true</exclude-unlisted-classes/><exclude-unlisted-classes>false</exclude-unlisted-classes/>exclude-unlisted-classes

使用 是最强大的 JPA 设置 选项,允许在应用程序中进行灵活的本地配置。它支持 链接到现有 JDBC ,支持本地和全局事务,以及 等等。但是,它也对运行时环境提出了要求,例如 如果持久性提供程序需要,则提供支持 WEAVING 的类装入器 字节码转换。LocalContainerEntityManagerFactoryBeanDataSource

此选项可能与 Java EE 服务器的内置 JPA 功能冲突。在一个 完整的 Java EE 环境,请考虑从 JNDI 获取。 或者,在定义上指定自定义项(例如, META-INF/my-persistence.xml),并在 应用程序 JAR 文件。因为 Java EE 服务器只查找缺省文件,所以它会忽略此类定制持久性单元,因此 预先避免与 Spring 驱动的 JPA 设置发生冲突。(这适用于 Resin 3.1,用于 示例。EntityManagerFactorypersistenceXmlLocationLocalContainerEntityManagerFactoryBeanMETA-INF/persistence.xml

什么时候需要装载时编织?

并非所有 JPA 提供程序都需要 JVM 代理程序。Hibernate 就是一个例子。 如果您的提供者不需要代理,或者您有其他选择,例如 在构建时通过自定义编译器或 Ant 任务应用增强功能时,不应使用 加载时 Weaver。

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

Spring 为各种环境提供了许多实现, 让实例只应用于每个类装入器,而不应用 对于每个 VM。LoadTimeWeaverClassTransformer

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

如 Spring 配置中所述,您可以配置 使用批注或 XML 元素的上下文范围。这样的全球织布机被自动捡起 由所有 JPA 实例提供。以下示例 显示了设置加载时间编织器的首选方法,提供自动检测 平台(例如,Tomcat 的可编织类加载器或 Spring 的 JVM 代理) 以及 weaver 自动传播到所有 weaver 感知的 bean:LoadTimeWeaver@EnableLoadTimeWeavingcontext:load-time-weaverLocalContainerEntityManagerFactoryBean

<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

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

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

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

处理多个持久性单元

对于依赖于多个持久性单元位置(存储在不同位置的应用程序 例如,类路径中的 JARS),Spring 提供了 充当 一个中央存储库,并避免持久性单元的发现过程,这可以是 贵。默认实现允许指定多个位置。这些位置是 解析,稍后通过持久性单元名称进行检索。(默认情况下,类路径 正在搜索文件。以下示例配置 多个地点:PersistenceUnitManagerMETA-INF/persistence.xml

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

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

默认实现允许自定义实例 (在它们被馈送到 JPA 提供程序之前)以声明方式(通过其属性,即 影响所有托管单元)或以编程方式(通过 ,允许选择持久性单元)。如果指定 no,则由 在内部创建一个。PersistenceUnitInfoPersistenceUnitPostProcessorPersistenceUnitManagerLocalContainerEntityManagerFactoryBean

后台引导

LocalContainerEntityManagerFactoryBean支持后台引导 该属性,如以下示例所示:bootstrapExecutor

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

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

5.4.2. 基于 JPA 实现 DAO: 和EntityManagerFactoryEntityManager

尽管实例是线程安全的,但实例是 不。注入的 JPA 的行为类似于从 应用程序服务器的 JNDI 环境,由 JPA 规范定义。它委托 对当前事务的所有调用(如果有)。否则,它会回退 添加到新创建的 per 操作,实际上使其使用线程安全。EntityManagerFactoryEntityManagerEntityManagerEntityManagerEntityManagerEntityManager

可以在没有任何 Spring 依赖项的情况下针对普通 JPA 编写代码,方法是 使用注入的 或 .Spring 可以在字段和方法级别理解 和 注解 如果启用了 a。以下示例显示了一个普通的 JPA DAO 实现 使用注解:EntityManagerFactoryEntityManager@PersistenceUnit@PersistenceContextPersistenceAnnotationBeanPostProcessor@PersistenceUnit

爪哇岛
Kotlin
public class ProductDaoImpl implements ProductDao {

    private EntityManagerFactory emf;

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

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

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

<beans>

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

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

</beans>

作为显式定义 的替代方法, 请考虑在应用程序中使用 Spring XML 元素 上下文配置。这样做会自动注册所有 Spring 标准 用于基于注释的配置的后处理器,包括等。PersistenceAnnotationBeanPostProcessorcontext:annotation-configCommonAnnotationBeanPostProcessor

请看以下示例:

<beans>

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

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

</beans>

这种 DAO 的主要问题是它总是创建一个新的通过 工厂。您可以通过请求事务性(也 称为“共享 EntityManager”,因为它是实际 transactional EntityManager) 而不是工厂。以下示例演示如何执行此操作:EntityManagerEntityManager

爪哇岛
Kotlin
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();
    }
}

注释具有一个名为 的可选属性,该属性默认为 。您可以使用此默认值来接收共享代理。替代方案 , 是完全的 不同的事情。这导致了所谓的扩展,它不是 线程安全,因此不得在并发访问的组件中使用,例如 Spring 管理的单例 Bean。扩展实例只应用于 例如,驻留在会话中的有状态组件,其生命周期不与当前事务相关联,而是完全取决于 应用。@PersistenceContexttypePersistenceContextType.TRANSACTIONEntityManagerPersistenceContextType.EXTENDEDEntityManagerEntityManagerEntityManager

方法级和现场级进样

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

类级注释呢?

在 Java EE 平台上,它们用于依赖关系声明,而不是资源 注射。

注入的是由 Spring 管理的(知道正在进行的事务)。 尽管新的 DAO 实现使用方法级 注入 an 而不是 an ,没有变化是 由于使用注释,在应用程序上下文 XML 中是必需的。EntityManagerEntityManagerEntityManagerFactory

这种 DAO 风格的主要优点是它只依赖于 Java 持久性 API。 不需要导入任何 Spring 类。此外,正如对 JPA 注解的理解, 注射由 Spring 容器自动应用。这很吸引人 非侵入性视角,对 JPA 开发人员来说更自然。

5.4.3. Spring 驱动的 JPA 事务

我们强烈建议您阅读声明式事务管理(如果您尚未阅读) 已经这样做了,以便更详细地了解 Spring 的声明式事务支持。

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

Spring JPA 还允许配置公开 JPA 事务 到访问相同的JDBC访问代码,前提是注册的JDBC支持检索底层JDBC。 Spring 为 EclipseLink 和 Hibernate JPA 实现提供了方言。 有关该机制的详细信息,请参阅下一节JpaTransactionManagerDataSourceJpaDialectConnectionJpaDialect

作为直接的替代方案,Spring 的本地人有能力 与 JPA 访问代码交互,适应多种 Hibernate 细节并提供 JDBC 交互。这与设置结合使用特别有意义。有关详细信息,请参阅 JPA 交互的本机 Hibernate 设置HibernateTransactionManagerLocalSessionFactoryBean

5.4.4. 理解和JpaDialectJpaVendorAdapter

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

  • 应用特定的事务语义(例如自定义隔离级别或事务) 超时)

  • 检索事务性 JDBC(用于暴露于基于 JDBC 的 DAO)Connection

  • Spring 的高级翻译PersistenceExceptionsDataAccessExceptions

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

作为一个更广泛的供应商适配设施,主要用于 Spring 的全功能设置,结合了 具有其他特定于提供程序的默认值的功能。指定 or 是最方便的 自动配置 Hibernate 或 EclipseLink 设置的方式, 分别。请注意,这些提供程序适配器主要设计用于 Spring 驱动的事务管理(即,用于 )。LocalContainerEntityManagerFactoryBeanJpaVendorAdapterJpaDialectHibernateJpaVendorAdapterEclipseLinkJpaVendorAdapterEntityManagerFactoryJpaTransactionManager

请参阅 JpaDialect 和 JpaVendorAdapter javadoc 有关其操作的更多详细信息,以及如何在 Spring 的 JPA 支持中使用它们。

5.4.5. 使用 JTA Transaction Management 设置 JPA

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

  • 底层 JDBC 连接池需要支持 XA,并与 您的交易协调员。这在 Java EE 环境中通常很简单, 通过JNDI暴露另一种。查看应用程序服务器 文档了解详细信息。类似地,独立的事务协调器通常 带有特殊的 XA 集成变体。再次检查其文档。DataSourceDataSource

  • 需要为 JTA 配置 JPA 设置。这是 特定于提供程序,通常通过特殊属性指定为 on 。在 Hibernate 的情况下,这些属性 甚至是特定于版本的。有关详细信息,请参阅 Hibernate 文档。EntityManagerFactoryjpaPropertiesLocalContainerEntityManagerFactoryBean

  • Spring 强制执行某些面向 Spring 的默认值,例如 作为连接释放模式,这与 Hibernate 自己的默认值相匹配 Hibernate 5.0 但 Hibernate 5.1+ 中没有更多。对于 JTA 设置,请确保声明 您的持久性单元事务类型为“JTA”。或者,将 Hibernate 5.2 的属性设置为恢复 Hibernate 自己的默认值。 有关相关说明,请参阅 Hibernate 的虚假应用程序服务器警告HibernateJpaVendorAdapteron-closehibernate.connection.handling_modeDELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT

  • 或者,考虑从您的应用程序中获取 服务器本身(即,通过 JNDI 查找而不是本地声明)。服务器提供的服务器可能需要在服务器配置中进行特殊定义(进行部署 不太便携),但针对服务器的 JTA 环境进行了设置。EntityManagerFactoryLocalContainerEntityManagerFactoryBeanEntityManagerFactory

5.4.6. 用于 JPA 交互的 Native Hibernate 设置和 Native Hibernate 事务

本机设置与允许与其他 JPA 访问代码进行交互。Hibernate 现在原生实现了 JPA 的接口 而 Hibernate 句柄本身就是一个 JPA 。 Spring 的 JPA 支持工具会自动检测原生 Hibernate 会话。LocalSessionFactoryBeanHibernateTransactionManager@PersistenceContextSessionFactoryEntityManagerFactorySessionEntityManager

因此,这种原生的 Hibernate 设置可以替代标准 JPA 和组合 在许多情况下,允许与 (以及 ) 在 内 相同的本地事务。这样的设置也提供了更强大的 Hibernate 集成 以及更大的配置灵活性,因为它不受 JPA 引导契约的约束。LocalContainerEntityManagerFactoryBeanJpaTransactionManagerSessionFactory.getCurrentSession()HibernateTemplate@PersistenceContext EntityManager

在这种情况下,您不需要配置, 因为 Spring 的原生 Hibernate 设置提供了更多功能 (例如,自定义 Hibernate Integrator 设置、Hibernate 5.3 Bean 容器集成、 以及针对只读事务的更强优化)。最后但并非最不重要的一点是,您还可以 通过 表示原生 Hibernate 设置, 与样式配置无缝集成(不涉及)。HibernateJpaVendorAdapterLocalSessionFactoryBuilder@BeanFactoryBean

LocalSessionFactoryBean和支持背景 引导,就像 JPA 一样。 有关介绍,请参阅后台引导LocalSessionFactoryBuilderLocalContainerEntityManagerFactoryBean

On ,可通过酒店获得。在编程方法上,重载方法接受引导执行器参数。LocalSessionFactoryBeanbootstrapExecutorLocalSessionFactoryBuilderbuildSessionFactory

6. 使用 Object-XML 映射器封送 XML

6.1. 简介

本章介绍了 Spring 的 Object-XML Mapping 支持。对象 XML 映射(简称 O-X 映射)是将 XML 文档与 XML 文档相互转换的行为 一个对象。此转换过程也称为 XML 封送处理或 XML 序列化。本章可互换使用这些术语。

在 O-X 映射领域,编组器负责序列化 对象(图形)转换为 XML。以类似的方式,解组程序将 XML 反序列化为 对象图。此 XML 可以采用 DOM 文档、输入或输出的形式 stream 或 SAX 处理程序。

使用 Spring 来满足 O/X 映射需求的一些好处是:

6.1.1. 易于配置

Spring 的 Bean 工厂使配置编组器变得容易,而无需 构造 JAXB 上下文、JiBX 绑定工厂等。您可以配置封送处理程序 就像在应用程序上下文中处理任何其他 Bean 一样。此外,基于 XML 命名空间 配置可用于多个编组器,使配置均匀 简单。

6.1.2. 一致的接口

Spring 的 O-X 映射通过两个全局接口运行:MarshallerUnmarshaller。通过这些抽象,可以切换 O-X 映射框架 相对容易,几乎不需要对执行 编组。这种方法还有一个额外的好处,那就是可以执行 XML 使用混合匹配方法进行编组(例如,使用 JAXB 执行的一些编组 以及 XStream 的一些),以一种非侵入性的方式,让您利用每个的优势 科技。

6.1.3. 一致的异常层次结构

Spring 提供了从底层 O-X 映射工具到其 自己的异常层次结构,其中 作为根异常。 这些运行时异常包装原始异常,因此不会丢失任何信息。XmlMappingException

6.2. 和MarshallerUnmarshaller

引言中所述,编组器序列化对象 到 XML,解组程序将 XML 流反序列化为对象。本节介绍 用于此目的的两个 Spring 接口。

6.2.1. 理解Marshaller

Spring 抽象了接口后面的所有编组操作,其主要方法如下:org.springframework.oxm.Marshaller

public interface Marshaller {

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

该接口有一个 main 方法,它将给定的对象封送至 鉴于。结果是一个标记接口,基本上 表示 XML 输出抽象。具体实现包装各种 XML 表示形式,如下表所示:Marshallerjavax.xml.transform.Result

结果实现 包装 XML 表示形式

DOMResult

org.w3c.dom.Node

SAXResult

org.xml.sax.ContentHandler

StreamResult

java.io.Filejava.io.OutputStreamjava.io.Writer

尽管该方法接受普通对象作为其第一个参数,但大多数实现无法处理任意对象。相反,对象类 必须在映射文件中映射,用注释标记,向 marshaller,或者有一个共同的基类。请参阅本章后面的部分 以确定您的 O-X 技术如何管理这一点。marshal()Marshaller

6.2.2. 理解Unmarshaller

与 类似,我们有一个界面,如下面的清单所示:Marshallerorg.springframework.oxm.Unmarshaller

public interface Unmarshaller {

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

此接口还有一个方法,该方法从给定的(XML 输入抽象)中读取并返回读取的对象。如 with 是一个标记接口,它有三个具体的实现。每 包装不同的 XML 表示形式,如下表所示:javax.xml.transform.SourceResultSource

源代码实现 包装 XML 表示形式

DOMSource

org.w3c.dom.Node

SAXSource

org.xml.sax.InputSourceorg.xml.sax.XMLReader

StreamSource

java.io.Filejava.io.InputStreamjava.io.Reader

尽管有两个单独的编组接口( 和 ),但 Spring-WS 中的所有实现都在一个类中实现。 这意味着您可以连接一个封送拆处理器类,并将其同时称为 编组员和作为 .MarshallerUnmarshallerapplicationContext.xml

6.2.3. 理解XmlMappingException

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

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

O-X 映射异常层次结构如下图所示:

OXM 异常

6.3. 使用 和MarshallerUnmarshaller

您可以将 Spring 的 OXM 用于各种情况。在以下示例中,我们 使用它来将 Spring 管理的应用程序的设置封送为 XML 文件。在以下示例中,我们 使用一个简单的 JavaBean 来表示设置:

爪哇岛
Kotlin
public class Settings {

    private boolean fooEnabled;

    public boolean isFooEnabled() {
        return fooEnabled;
    }

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

应用程序类使用此 Bean 来存储其设置。除了 main 方法之外, 类有两种方法:将设置 Bean 保存到名为 的文件中,然后再次加载这些设置。以下方法 构造一个 Spring 应用程序上下文并调用以下两个方法:saveSettings()settings.xmlloadSettings()main()

爪哇岛
Kotlin
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 {
        try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
            this.marshaller.marshal(settings, new StreamResult(os));
        }
    }

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

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

需要同时设置 a 和 an 属性。我们 可以使用以下方法执行此操作:ApplicationmarshallerunmarshallerapplicationContext.xml

<beans>
    <bean id="application" class="Application">
        <property name="marshaller" ref="xstreamMarshaller" />
        <property name="unmarshaller" ref="xstreamMarshaller" />
    </bean>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</beans>

此应用程序上下文使用 XStream,但我们可以使用任何其他封送处理程序 本章稍后将介绍实例。请注意,默认情况下,XStream 不需要 任何进一步的配置,因此 Bean 定义相当简单。还要注意的是,实现了 和 ,因此我们可以在 和 属性中引用 bean 应用。XStreamMarshallerMarshallerUnmarshallerxstreamMarshallermarshallerunmarshaller

此示例应用程序生成以下文件:settings.xml

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

6.4.XML 配置命名空间

通过使用 OXM 命名空间中的标记,可以更简洁地配置封送处理程序。 要使这些标签可用,您必须首先在 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:oxm="http://www.springframework.org/schema/oxm" (1)
xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> (2)
1 引用架构。oxm
2 指定架构位置。oxm

该架构使以下元素可用:

每个标签都在其各自的封送部分中进行了说明。不过,举个例子, JAXB2 封送拆接管程序的配置可能类似于以下内容:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

6.5. JAXB的

JAXB 绑定编译器将 W3C XML 模式转换为一个或多个 Java 类、一个文件,可能还有一些资源文件。JAXB 还提供了一种 从带注释的 Java 类生成模式。jaxb.properties

Spring 支持 JAXB 2.0 API 作为 XML 编组策略,遵循 MarshallerUnmarshaller 中描述的 和 接口。 相应的集成类驻留在包中。MarshallerUnmarshallerorg.springframework.oxm.jaxb

6.5.1. 使用Jaxb2Marshaller

该类实现了 Spring 和接口。它需要上下文路径才能运行。可以通过设置属性来设置上下文路径。上下文路径是以冒号分隔的 Java 包的列表 包含架构派生类的名称。它还提供房产, 它允许您设置编组器支持的类数组。图式 验证是通过为 Bean 指定一个或多个模式资源来执行的,如以下示例所示:Jaxb2MarshallerMarshallerUnmarshallercontextPathclassesToBeBound

<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-marshallerorg.springframework.oxm.jaxb.Jaxb2Marshaller

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

或者,可以使用子元素提供要绑定到封送处理程序的类列表:class-to-be-bound

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

下表描述了可用属性:

属性 描述 必填

id

封送程序的 ID

contextPath

JAXB 上下文路径

6.6. JiBX的

JiBX 框架提供了一个类似于 Hibernate 为 ORM 提供的解决方案:A 绑定定义定义了 Java 对象如何与 Java 对象相互转换的规则 在准备绑定并编译类之后,JiBX 绑定编译器 增强了类文件并添加了代码以处理类的转换实例 从 XML 或到 XML。

有关 JiBX 的更多信息,请参阅 JiBX Web 网站。Spring 集成类驻留在包中。org.springframework.oxm.jibx

6.6.1. 使用JibxMarshaller

该类同时实现 和 接口。要操作,它需要封送入的类的名称,您可以这样做 使用属性设置。(可选)可以通过设置属性来设置绑定名称。在以下示例中,我们绑定了类:JibxMarshallerMarshallerUnmarshallertargetClassbindingNameFlights

<beans>
    <bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
        <property name="targetClass">org.springframework.oxm.jibx.Flights</property>
    </bean>
    ...
</beans>

为单个类配置 A。如果要封送多个 类,您必须使用不同的属性值配置多个实例。JibxMarshallerJibxMarshallertargetClass

XML 配置命名空间

该标记配置了一个 , 如以下示例所示:jibx-marshallerorg.springframework.oxm.jibx.JibxMarshaller

<oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/>

下表描述了可用属性:

属性 描述 必填

id

封送程序的 ID

target-class

此封送处理器的目标类

是的

bindingName

此封送处理程序使用的绑定名称

6.7. XStream的

XStream 是一个简单的库,用于将对象序列化为 XML 并返回。它没有 需要任何映射并生成干净的 XML。

有关 XStream 的详细信息,请参阅 XStream 网站。Spring 集成类驻留在包中。org.springframework.oxm.xstream

6.7.1. 使用XStreamMarshaller

不需要任何配置,可以在 应用程序上下文。要进一步自定义 XML,您可以设置别名映射, 它由映射到类的字符串别名组成,如以下示例所示:XStreamMarshaller

<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 允许对任意类进行解组,这可能导致 不安全的 Java 序列化效果。因此,我们不建议使用 从外部源(即 Web)取消封送 XML,因为这样可以 导致安全漏洞。XStreamMarshaller

如果选择使用 从外部源取消封送 XML, 设置 上的属性,如以下示例所示:XStreamMarshallersupportedClassesXStreamMarshaller

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

这样做可确保只有已注册的类才有资格进行解组。

此外,您还可以注册自定义 转换器,以确保只有您支持的类可以解组。你可能会 想要添加 A 作为列表中的最后一个转换器,此外 显式支持应支持的域类的转换器。作为 结果,默认的 XStream 转换器具有较低的优先级和可能的安全性 漏洞不会被调用。CatchAllConverter

请注意,XStream 是一个 XML 序列化库,而不是一个数据绑定库。 因此,它对命名空间的支持有限。因此,它相当不适合使用 在 Web 服务中。

7. 附录

7.1.XML 模式

附录的这一部分列出了用于数据访问的 XML 架构,包括以下内容:

7.1.1. 模式tx

这些标签涉及在 Spring 的全面支持中配置所有这些 bean 用于交易。这些标记在标题为“事务管理”的章节中介绍。tx

我们强烈建议您查看 春季分布。此文件包含 Spring 事务的 XML 模式 配置并涵盖命名空间中的所有各种元素,包括 属性默认值和类似信息。此文件以内联方式记录,因此, 为了遵守 DRY(不要 重复自己)的原则。'spring-tx.xsd'tx

为了完整起见,要使用架构中的元素,您需要 Spring XML 配置文件顶部的以下前导码。中的文本 以下代码段引用了正确的架构,以便命名空间中的标签 可供您使用:txtx

<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd (2)
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>
1 声明命名空间的用法。tx
2 指定位置(使用其他架构位置)。
通常,当您使用命名空间中的元素时,您还会使用 元素(因为 Spring 中的声明式事务支持是 通过使用 AOP 实现)。前面的 XML 代码段包含所需的相关行 引用架构,以便命名空间中的元素可用 给你。txaopaopaop

7.1.2. 模式jdbc

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

要使用架构中的元素,您需要在 Spring XML 配置文件的顶部。以下代码片段中的文本引用 正确的架构,以便命名空间中的元素可供您使用:jdbcjdbc

<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> (2)

    <!-- bean definitions here -->

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