儲存過程

在某些情況下,純粹的 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

即使你的特定資料庫可能不是完全支援的,如果你的 RDBMS 支援儲存過程或儲存函式,你很可能仍然可以成功地使用 Spring Integration 的儲存過程元件。

事實上,一些提供的整合測試使用了 H2 database。儘管如此,徹底測試這些使用場景非常重要。

配置

儲存過程元件提供完整的 XML Namespace 支援,並且配置這些元件與之前討論的通用 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 屬性將用作儲存過程或儲存函式的引數值的來源。

    但是,如果傳入了過程引數,此屬性(預設情況下)評估為 falseProcedureParameter 允許提供 SpEL 表示式。因此,訪問整個 Message 非常有益。此屬性設定在底層的 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 除了 value 屬性,你還可以指定一個 SpEL 表示式來傳遞引數的值。如果你指定了 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()),檢索到的儲存過程引數宣告會被轉換為小寫。因此,如果你有駝峰式命名法(例如 lastName)的 bean 屬性,查詢會失敗。在這種情況下,請提供顯式的 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=""                                    (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>