存储过程

在某些情况下,仅凭纯 JDBC 支持是不够的。 也许您处理的是遗留的关系型数据库模式,或者您有复杂的数据处理需求,但最终,您必须使用 存储过程或存储函数。 自 Spring Integration 2.1 起,我们提供了三个组件来执行存储过程或存储函数:

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

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

  • 存储过程出站网关

支持的数据库

为了启用对存储过程和存储函数的调用,存储过程组件使用 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 数据库。 然而,彻底测试这些使用场景非常重要。

配置

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

常用配置属性

所有存储过程组件共享某些配置参数:

  • 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 值。 请参阅 定义参数来源。 可选。 * use-payload-as-parameter-source:(不适用于存储过程入站通道适配器。) 如果设置为 true,则 Message 的消息体用作提供参数的来源。 如果设置为 false,则整个 Message 可用作参数的来源。如果没有传入任何过程参数,则此属性默认为 true。 这意味着,通过使用默认的 BeanPropertySqlParameterSourceFactory,消息体的 bean 属性用作存储过程或存储函数的参数值的来源。 或者,从版本 6.5 开始,如果消息体是 Map,则用作键。 但是,如果传入了过程参数,则此属性(默认情况下)评估为 falseProcedureParameter 允许提供 SpEL 表达式。 因此,访问整个 Message 非常有益。 该属性设置在底层的 StoredProcExecutor 上。 可选。

常用配置子元素

存储过程组件共享一组常用的子元素,您可以使用它们来定义和传递参数给存储过程或存储函数。 以下元素可用:

  • parameter

  • returning-resultset

  • sql-parameter-definition

  • poller

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

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

<int-jdbc:parameter name=""
                    expression=""/> [id="CO1-4"]4
 <1> 要传递给存储过程或存储函数的参数名称。
必需。
 <1> 此属性指定值的类型。
如果未提供任何内容,则此属性默认为 `java.lang.String`。
此属性仅在使用 `value` 属性时使用。
可选。
 <1> 参数的值。
您必须提供此属性或 `expression` 属性。
可选。
 <1> 除了 `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=""                           [id="CO2-1"]1
                                   direction="IN"                    [id="CO2-2"]2
                                   type="STRING"                     [id="CO2-3"]3
                                   scale="5"                         [id="CO2-4"]4
                                   type-name="FOO_STRUCT"            [id="CO2-5"]5
                                   return-type="fooSqlReturnType"/>  [id="CO2-6"]6
 <1> 指定 SQL 参数的名称。
必需。
 <1> 指定 SQL 参数定义的方向。
默认为 `IN`。
有效值为:`IN`、`OUT` 和 `INOUT`。
如果您的过程返回结果集,请使用 `returning-resultset` 元素。
可选。
 <1> 此 SQL 参数定义使用的 SQL 类型。
转换为整数值,如 `java.sql.Types` 所定义。
或者,您也可以提供整数值。
如果未明确设置此属性,则默认为“VARCHAR”。
可选。
 <1> SQL 参数的比例。
仅用于数字和小数参数。
可选。
 <1> 用户命名类型的 `typeName`,例如:`STRUCT`、`DISTINCT`、`JAVA_OBJECT` 和命名数组类型。
此属性与 `scale` 属性互斥。
可选。
 <1> 对复杂类型的自定义值处理程序的引用。
link:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/core/SqlReturnType.html[`SqlReturnType`] 的实现。
此属性与 `scale` 属性互斥,并且仅适用于 OUT 和 INOUT 参数。
可选。
* `poller`:如果此端点是 `PollingConsumer`,则允许您配置消息轮询器。
可选。

定义参数来源

参数来源管理检索 Spring Integration 消息属性并将其映射到相关存储过程输入参数的技术。

存储过程组件遵循某些规则。 默认情况下,Message 消息体的 bean 属性用作存储过程输入参数的来源。 在这种情况下,使用 BeanPropertySqlParameterSourceFactory。 这对于基本用例可能足够了。 下一个示例说明了该默认行为。

对于使用 BeanPropertySqlParameterSourceFactory 的 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 的自定义实现。

存储过程入站通道适配器

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

<int-jdbc:stored-proc-inbound-channel-adapter
                                   channel=""                                    [id="CO3-1"]1
                                   stored-procedure-name=""
                                   data-source=""
                                   auto-startup="true"
                                   id=""
                                   ignore-column-meta-data="false"
                                   is-function="false"
                                   skip-undeclared-results=""                    [id="CO3-2"]2
                                   return-value-required="false"                 [id="CO3-3"]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。
必需。
 <1> 如果此属性设置为 `true`,则跳过所有没有相应 `SqlOutParameter` 声明的存储过程调用结果。
例如,存储过程可以返回更新计数,即使您的存储过程只声明了一个结果参数。
确切的行为取决于数据库实现。
该值设置在底层的 `JdbcTemplate` 上。
该值默认为 `true`。
可选。
 <1> 指示是否应包含此过程的返回值。
自 Spring Integration 3.0 起。
可选。

存储过程出站通道适配器

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

<int-jdbc:stored-proc-outbound-channel-adapter channel=""                        [id="CO4-1"]1
                                               stored-procedure-name=""
                                               data-source=""
                                               auto-startup="true"
                                               id=""
                                               ignore-column-meta-data="false"
                                               order=""                          [id="CO4-2"]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> 此端点的接收消息通道。
必需。
 <1> 当此端点作为订阅者连接到通道时,指定调用的顺序。
当该通道使用 `failover` 调度策略时,这尤其相关。
当此端点本身是带有队列的通道的轮询消费者时,它不起作用。
可选。

存储过程出站网关

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

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

示例

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

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

请查看 Spring Integration 示例项目。 该项目包含此处引用的 Apache Derby 示例,以及如何运行它的说明。 Spring Integration 示例项目还提供了使用 Oracle 存储过程的 示例

在第一个示例中,我们调用名为 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>