存储过程

在某些情况下,简单的 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 定义,根据出站通道适配器的 channel 属性引用的是 SubscribableChannel 还是 PollableChannel,它是一个 EventDrivenConsumerPollingConsumer 的实例。可选。

  • 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 导出器

  • sql-parameter-source-factory:(存储过程入站通道适配器不可用。)指向 SqlParameterSourceFactory 的引用。默认情况下,传递的消息有效负载的 bean 属性将作为存储过程输入参数的来源,使用 BeanPropertySqlParameterSourceFactory

    这可能足以满足基本用例。对于更复杂的选项,请考虑传入一个或多个 ProcedureParameter 值。请参阅定义参数源。可选。

  • use-payload-as-parameter-source:(存储过程入站通道适配器不可用。)如果设置为 true,则消息的有效负载将用作提供参数的来源。但是,如果设置为 false,则整个消息可用作参数的来源。

    如果没有传入过程参数,则此属性默认为 true。这意味着,通过使用默认的 BeanPropertySqlParameterSourceFactory,有效负载的 bean 属性将用作存储过程或存储函数的参数值的来源。

    但是,如果传入过程参数,则此属性(默认情况下)将评估为 falseProcedureParameter 允许提供 SpEL 表达式。因此,访问整个消息非常有益。此属性设置在底层的 StoredProcExecutor 上。可选。

常见配置子元素

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

  • parameter

  • returning-resultset

  • sql-parameter-definition

  • poller

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

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

    <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。有效值为:INOUTINOUT。如果您的过程返回结果集,请使用 returning-resultset 元素。可选。
3 用于此 SQL 参数定义的 SQL 类型。转换为整数,由 java.sql.Types 定义。或者,您也可以提供整数值。如果未显式设置此属性,则默认为“VARCHAR”。可选。
4 SQL 参数的刻度。仅用于数字和十进制参数。可选。
5 用户命名的类型的 typeName,例如:STRUCTDISTINCTJAVA_OBJECT 和命名数组类型。此属性与 scale 属性互斥。可选。
6 指向复杂类型的自定义值处理程序的引用。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 组成,该 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=""                                    (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 起。可选。

存储过程出站通道适配器

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

<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调度策略时,这一点尤其重要。当此端点本身是具有队列的通道的轮询使用者时,它没有任何作用。可选。

存储过程出站网关

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

<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。可选。

示例

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

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

该项目包含此处引用的 Apache Derby 示例,以及有关如何运行它的说明。Spring Integration Samples 项目还提供了一个使用 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>