Stored Procedures

在某些情况下,普通的 JDBC 支持是不够的。也许你处理遗留关系数据库模式,或者你必须进行复杂的数据处理,但最终你必须使用 stored procedures 或存储过程。自 Spring 集成 2.1 以来,我们提供了三个组件来执行存储过程或存储函数:

  • 存储过程入站通道适配器

  • 存储过程出站通道适配器

  • Stored Procedures Outbound Gateway

Supported Databases

为了启用对存储过程和存储函数的调用,存储过程组件使用了 org.springframework.jdbc.core.simple.SimpleJdbcCall 类。因此,完全支持以下数据库执行存储过程:

  • Apache Derby

  • DB2

  • MySQL

  • Microsoft SQL Server

  • Oracle

  • PostgreSQL

  • Sybase

如果你想要执行存储函数,则以下数据库得到完全支持:

  • MySQL

  • Microsoft SQL Server

  • Oracle

  • PostgreSQL

即使你的特定数据库可能未得到完全支持,但你仍很有可能能够成功使用存储过程 Spring Integration 组件,只要你的 RDBMS 支持存储过程或存储函数即可。 事实上,一些提供的集成测试使用 H2 database。但是,全面地测试那些使用场景非常重要。

Configuration

存储过程组件提供完全的 XML 名称空间支持,并且配置组件与前面讨论的通用 JDBC 组件类似。

Common Configuration Attributes

所有存储过程组件共享特定的配置参数:

  • auto-startup: 生命周期属性,指示此组件是否应在应用程序上下文启动期间启动。它默认为 true。可选项。

  • data-source: 对 javax.sql.DataSource 的引用,用于访问数据库。必填。

  • id: 标识基础 Spring Bean 定义,该定义是 EventDrivenConsumerPollingConsumer`的实例,具体取决于出站通道适配器的 `channel 属性是引用 SubscribableChannel 还是 PollableChannel。可选项。

  • ignore-column-meta-data: 对于受完全支持的数据库,基础 SimpleJdbcCall 类可从 JDBC 元数据自动检索存储过程或存储函数的参数信息。但是,如果数据库不支持元数据查找或如果你需要提供自定义参数定义,则可以将此标志设置成 true。它默认为 false。可选的。

  • is-function: 如果为 true,则调用 SQL 函数。在这种情况下, stored-procedure-namestored-procedure-name-expression 属性定义所调用函数的名称。它默认为 false。可选项。

  • stored-procedure-name: 此属性指定存储过程的名称。如果 is-function 属性设置为 true,则此属性改为指定函数名称。此属性或 stored-procedure-name-expression 必须指定。

  • stored-procedure-name-expression: 此属性使用 SpEL 表达式指定存储过程的名称。使用 SpEL,你可以访问完整消息(如果可用),包括它的头和有效负载。你可以在运行时使用此属性调用不同的存储过程。例如,你可以提供你要作为消息头执行的存储过程名称。该表达式必须解决为 String。如果将 is-function 特性设置为 true,则此特性指定一个存储函数。此属性或 stored-procedure-name 必须指定。

  • jdbc-call-operations-cache-size: 定义缓存的 SimpleJdbcCallOperations 实例的最大数量。基本上,对于每个存储过程名称,都会创建一个新的 SimpleJdbcCallOperations 实例,该实例会反过来被缓存。

Spring Integration 2.2 增加了 stored-procedure-name-expression 特性和 jdbc-call-operations-cache-size 特性。

默认缓存大小为 10。值为 0 将禁用缓存。不允许使用负值。 如果您启用 JMX,则 {jdbc-call-operations-cache} 的统计信息将作为 MBean 暴露。有关更多信息,请参见 {MBean Exporter}。 * sql-parameter-source-factory:(不适用于存储过程入站通道适配器)对 SqlParameterSourceFactory 的引用。默认情况下,传入的 Message 有效负载的 Bean 属性被用作存储过程输入参数的来源,方法是使用 BeanPropertySqlParameterSourceFactory。这可能足以满足基本用例。对于更精细的选择,请考虑传入一个或多个 {ProcedureParameter} 值。有关详细信息,请参见 {Defining Parameter Sources}。可选。 * use-payload-as-parameter-source:(不适用于存储过程入站通道适配器)如果设置为 true,则 Message 的有效负载将用作提供参数的来源。然而,如果设置为 false,则整个 Message 可用作参数的来源。如果没有传入存储过程参数,则此属性默认为 true。这意味着,通过使用默认的 BeanPropertySqlParameterSourceFactory,将使用有效负载的 bean 属性作为存储过程或存储函数的参数值的源。 但是,如果传入存储过程参数,此属性(默认情况下)将变为 falseProcedureParameter 让 SpEL 表达式得以提供。因此,非常有益于访问整个 Message。在底层的 StoredProcExecutor 上设置的属性。可选的。

Common Configuration Sub-Elements

存储过程组件共享一组通用的子元素,你可以使用它们来定义参数并将其传递给存储过程或存储函数。有以下元素可用:

  • parameter

  • returning-resultset

  • sql-parameter-definition

  • poller

  • parameter: 提供了一种机制来提供存储过程参数。参数可以是静态的,也可以使用 SpEL 表达式提供。[source, xml]

<int-jdbc:parameter name=""         1
                    type=""         2
                    value=""/>      3

<int-jdbc:parameter name=""
                    expression=""/> 4
1 传递到存储过程或存储函数的参数的名称。必填。
2 此属性指定值的类型。如果未提供任何内容,此属性默认为 java.lang.String。仅当使用 value 属性时才使用此属性。可选项。
3 参数的值。你必须提供此属性或 expression 属性。可选项。
4 代替 value 属性,你可以指定一个用于传递参数值的 SpEL 表达式。如果你指定 expression,则不允许使用 value 属性。可选项。可选项。
  • returning-resultset: 存储过程可能返回多个结果集。通过设置一个或多个 returning-resultset 元素,你可以指定 RowMappers 将每个返回的 ResultSet 转换为有意义的对象。可选项。[source, xml]

<int-jdbc:returning-resultset name="" row-mapper="" />
  • sql-parameter-definition:如果使用全面支持的数据库,您通常无需指定存储过程参数定义。相反,您可以从 JDBC 元数据中自动导出这些参数。但是,如果您使用未完全支持的数据库,必须使用 sql-parameter-definition 元素手动设置这些参数。你还可以使用 ignore-column-meta-data 特性来选择关闭通过 JDBC 获得的任何参数元数据信息的处理。

<int-jdbc:sql-parameter-definition
                                   name=""                           1
                                   direction="IN"                    2
                                   type="STRING"                     3
                                   scale="5"                         4
                                   type-name="FOO_STRUCT"            5
                                   return-type="fooSqlReturnType"/>  6
1 指定 SQL 参数的名称。必需。
2 指定 SQL 参数定义的方向。默认为 IN。有效值包括: INOUT`和 `INOUT。如果存储过程正在返回结果集,请使用 returning-resultset 元素。可选。
3 对此 SQL 参数定义使用的 SQL 类型。转换为整数,由 java.sql.Types 定义。或者,您也可以提供此整数。如果没有明确设置此属性,则默认为“VARCHAR”。可选。
4 SQL 参数的精度。仅用于数字和十进制参数。可选。
5 对于类型为用户命名的类型,如:STRUCTDISTINCTJAVA_OBJECT`和命名的数组类型,使用 `typeName。此属性与 scale 属性互斥。可选。
6 对于 OUT 和 INOUT 参数,对复杂类型的自定义值处理程序的引用。 SqlReturnType 的实现。此属性与 scale 属性互斥,并且仅适用于 OUT 和 INOUT 参数。可选。
  • poller:允许您配置消息轮询器(如果此端点是 PollingConsumer)。可选。

Defining Parameter Sources

参数源控制获取 Spring Integration 消息属性并将其映射到相关存储过程输入参数的技术。

存储过程组件遵循特定规则。默认情况下,Message 有效负载的 bean 属性被用作存储过程输入参数的源。在这种情况下,将使用 BeanPropertySqlParameterSourceFactory。这对于基本用例可能足够了。下一个示例说明了该默认行为。

对于使用 BeanPropertySqlParameterSourceFactory 进行 “automatic” 查找 bean 特性,你的 bean 特性必须定义为小写。这是因为在 org.springframework.jdbc.core.metadata.CallMetaDataContext(Java 方法是 matchInParameterValuesWithCallParameters())中,检索的存储过程参数声明被转换为小写。因此,如果你有驼峰式 bean 特性(如 lastName),则查找将会失败。在这种情况下,提供一个明确的 ProcedureParameter

假设我们有一个有效负载,它包含一个具有以下三个属性的简单 bean:idnamedescription。此外,我们有一个称为 INSERT_COFFEE 的简单存储过程,它接受三个输入参数:idnamedescription。我们还使用一个完全受支持的数据库。在这种情况下,对存储过程出站适配器进行以下配置就足够了:

<int-jdbc:stored-proc-outbound-channel-adapter data-source="dataSource"
    channel="insertCoffeeProcedureRequestChannel"
    stored-procedure-name="INSERT_COFFEE"/>

对于更高级的选项,请考虑传入一个或多个 ProcedureParameter 值。

如果您确实显式提供了 ProcedureParameter 值,默认情况下会使用 ExpressionEvaluatingSqlParameterSourceFactory 用于参数处理,以支持 SpEL 表达式的全部功能。

如果您需要进一步控制如何检索参数,请考虑使用 sql-parameter-source-factory 属性传入 SqlParameterSourceFactory 的自定义实现。

Stored Procedure Inbound Channel Adapter

以下列表调用对存储过程入站通道适配器重要的属性:

<int-jdbc:stored-proc-inbound-channel-adapter
                                   channel=""                                    1
                                   stored-procedure-name=""
                                   data-source=""
                                   auto-startup="true"
                                   id=""
                                   ignore-column-meta-data="false"
                                   is-function="false"
                                   skip-undeclared-results=""                    2
                                   return-value-required="false"                 3
    <int:poller/>
    <int-jdbc:sql-parameter-definition name="" direction="IN"
                                               type="STRING"
                                               scale=""/>
    <int-jdbc:parameter name="" type="" value=""/>
    <int-jdbc:parameter name="" expression=""/>
    <int-jdbc:returning-resultset name="" row-mapper="" />
</int-jdbc:stored-proc-inbound-channel-adapter>
1 发送轮询消息的通道。如果存储过程或函数未返回任何数据,则 Message 的有效负载为 null。必需。
2 如果此属性设置为 true,则将跳过存储过程调用中所有没有对应 SqlOutParameter 声明的结果。例如,即使存储过程仅声明了一个结果参数,存储过程也可以返回更新计数值。确切的行为取决于数据库实现。此值在底层 JdbcTemplate 中设置。值默认为 true。可选。
3 指示是否应包含此存储过程的返回值。自 Spring Integration 3.0 起。可选。

Stored Procedure Outbound Channel Adapter

以下列表调用对存储过程出站通道适配器重要的属性:

<int-jdbc:stored-proc-outbound-channel-adapter channel=""                        1
                                               stored-procedure-name=""
                                               data-source=""
                                               auto-startup="true"
                                               id=""
                                               ignore-column-meta-data="false"
                                               order=""                          2
                                               sql-parameter-source-factory=""
                                               use-payload-as-parameter-source="">
    <int:poller fixed-rate=""/>
    <int-jdbc:sql-parameter-definition name=""/>
    <int-jdbc:parameter name=""/>

</int-jdbc:stored-proc-outbound-channel-adapter>
1 此端点的接收消息通道。必需。
2 指定此端点连接为通道的订阅器时的调用顺序。当该通道使用 failover 调度策略时,这一点尤为重要。当此端点自身为带队列的通道的轮询使用者时,它不会生效。可选。

Stored Procedure Outbound Gateway

以下列表调用对存储过程出站通道适配器重要的属性:

<int-jdbc:stored-proc-outbound-gateway request-channel=""                        1
                                       stored-procedure-name=""
                                       data-source=""
                                   auto-startup="true"
                                   id=""
                                   ignore-column-meta-data="false"
                                   is-function="false"
                                   order=""
                                   reply-channel=""                              2
                                   reply-timeout=""                              3
                                   return-value-required="false"                 4
                                   skip-undeclared-results=""                    5
                                   sql-parameter-source-factory=""
                                   use-payload-as-parameter-source="">
<int-jdbc:sql-parameter-definition name="" direction="IN"
                                   type=""
                                   scale="10"/>
<int-jdbc:sql-parameter-definition name=""/>
<int-jdbc:parameter name="" type="" value=""/>
<int-jdbc:parameter name="" expression=""/>
<int-jdbc:returning-resultset name="" row-mapper="" />
1 此端点的接收消息通道。必需。
2 接收数据库响应后应将回复发送到的消息通道。可选。
3 允许您指定此网关在抛出异常之前等待成功发送回复消息的时间。请记住,发送到 DirectChannel 时,调用发生在发送者的线程中。因此,发送操作的失败可能是由下游的其他组件导致的。该值以毫秒为单位。可选。
4 指示是否应包含此存储过程的返回值。可选。
5 如果 skip-undeclared-results 属性设置为 true,则将跳过存储过程调用中所有没有对应 SqlOutParameter 声明的结果。例如,即使存储过程仅声明了一个结果参数,存储过程也可以返回更新计数值。确切的行为取决于数据库。此值在底层 JdbcTemplate 中设置。值默认为 true。可选。

Examples

本节包含两个调用 { Apache Derby} 存储过程的示例。第一个过程调用了一个返回 {ResultSet} 的存储过程。通过使用 {RowMapper},数据被转化成一个域对象,然后成为 Spring Integration 消息有效负载。

在第二个示例中,我们调用一个存储过程,该存储过程使用输出参数来返回数据。

看看 { Spring Integration Samples project}。 该项目包含本文所引用的 Apache Derby 示例以及如何运行它的说明。Spring Integration Samples 项目还提供了一个如何使用 Oracle 存储过程的 { example}。

在第一个示例中,我们调用名为 FIND_ALL_COFFEE_BEVERAGES 的存储过程,该存储过程未定义任何输入参数,但返回 ResultSet

在 Apache Derby 中,存储过程在 Java 中实现。以下列表显示方法签名:

public static void findAllCoffeeBeverages(ResultSet[] coffeeBeverages)
            throws SQLException {
    ...
}

以下列表显示相应的 SQL:

CREATE PROCEDURE FIND_ALL_COFFEE_BEVERAGES() \
PARAMETER STYLE JAVA LANGUAGE JAVA MODIFIES SQL DATA DYNAMIC RESULT SETS 1 \
EXTERNAL NAME 'o.s.i.jdbc.storedproc.derby.DerbyStoredProcedures.findAllCoffeeBeverages';

在 Spring Integration 中,您现在可以通过使用,例如,stored-proc-outbound-gateway 调用此存储过程,如下例所示:

<int-jdbc:stored-proc-outbound-gateway id="outbound-gateway-storedproc-find-all"
                                       data-source="dataSource"
                                       request-channel="findAllProcedureRequestChannel"
                                       expect-single-result="true"
                                       stored-procedure-name="FIND_ALL_COFFEE_BEVERAGES">
<int-jdbc:returning-resultset name="coffeeBeverages"
    row-mapper="org.springframework.integration.support.CoffeBeverageMapper"/>
</int-jdbc:stored-proc-outbound-gateway>

在第二个示例中,我们调用一个名为 FIND_COFFEE 的存储过程,该存储过程有一个输入参数。它不返回 ResultSet,而使用输出参数。以下示例显示方法签名:

public static void findCoffee(int coffeeId, String[] coffeeDescription)
            throws SQLException {
    ...
}

以下列表显示相应的 SQL:

CREATE PROCEDURE FIND_COFFEE(IN ID INTEGER, OUT COFFEE_DESCRIPTION VARCHAR(200)) \
PARAMETER STYLE JAVA LANGUAGE JAVA EXTERNAL NAME \
'org.springframework.integration.jdbc.storedproc.derby.DerbyStoredProcedures.findCoffee';

在 Spring Integration 中,您现在可以通过使用,例如,stored-proc-outbound-gateway 调用此存储过程,如下例所示:

<int-jdbc:stored-proc-outbound-gateway id="outbound-gateway-storedproc-find-coffee"
                                       data-source="dataSource"
                                       request-channel="findCoffeeProcedureRequestChannel"
                                       skip-undeclared-results="true"
                                       stored-procedure-name="FIND_COFFEE"
                                       expect-single-result="true">
    <int-jdbc:parameter name="ID" expression="payload" />
</int-jdbc:stored-proc-outbound-gateway>