存储过程
在某些情况下,纯 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 集成组件。
事实上,一些提供的集成测试使用了 H2 数据库。然而,彻底测试这些使用场景非常重要。
配置
存储过程组件提供完整的 XML 命名空间支持,配置这些组件的方式与前面讨论的通用 JDBC 组件类似。
常见配置属性
所有存储过程组件共享某些配置参数:
-
auto-startup: 生命周期属性,指示是否应在应用程序上下文启动期间启动此组件。默认值为true。可选。 -
data-source: 引用一个javax.sql.DataSource,用于访问数据库。必需。 -
id: 识别底层的 Spring Bean 定义,该定义是EventDrivenConsumer或PollingConsumer的实例之一,具体取决于外发通道适配器的channel属性引用的是SubscribableChannel还是PollableChannel。可选。 -
ignore-column-meta-data: 对于完全支持的数据库,底层 SimpleJdbcCall 类可以从 JDBC 元数据中自动检索存储过程或存储函数的参数信息。但是,如果数据库不支持元数据查找,或者您需要提供自定义参数定义,则可以将此标志设置为
true。默认值为false。可选。 -
is-function: 如果为true,则调用 SQL 函数。在这种情况下,stored-procedure-name或stored-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 属性用作存储过程或存储函数的参数值来源。但是,如果传递了存储过程参数,默认情况下此属性评估为
false。ProcedureParameter允许提供 SpEL 表达式。因此,访问整个Message非常有利。此属性设置在底层的StoredProcExecutor上。可选。
常见配置子元素
存储过程组件共享一组通用的子元素,您可以使用这些子元素来定义和传递参数给存储过程或存储函数。以下元素可用:
parameterreturning-resultsetsql-parameter-definitionpollerparameter: 提供了一种机制来提供存储过程参数。参数可以是静态的,也可以通过使用 SpEL 表达式提供。<int-jdbc:parameter name="" // <1>
type="" // <2>
value=""/> // <3>
<int-jdbc:parameter name=""
expression=""/> // <4>要传递给存储过程或存储函数的参数名称。必需。
此属性指定值的类型。如果没有提供任何内容,默认为
java.lang.String。仅在使用value属性时使用此属性。可选。参数的值。您必须提供此属性或
expression属性之一。可选。除了
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>指定 SQL 参数的名称。必需。
指定 SQL 参数定义的方向。默认为
IN。有效值为:IN、OUT和INOUT。如果您的过程返回结果集,请使用returning-resultset元素。可选。用于此 SQL 参数定义的 SQL 类型。转换为由
java.sql.Types定义的整数值。或者,您也可以提供整数值。如果未明确设置此属性,默认为 'VARCHAR'。可选。SQL 参数的比例。仅用于数字和小数参数。可选。
用户命名类型的
typeName,例如:STRUCT、DISTINCT、JAVA_OBJECT和命名数组类型。此属性与scale属性互斥。可选。复杂类型的自定义值处理器引用。实现了 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 组成:id、name 和 description。此外,我们还有一个名为 INSERT_COFFEE 的简单存储过程,它接受三个输入参数:id、name 和 description。我们还使用了一个完全支持的数据库。在这种情况下,以下存储过程输出适配器的配置就足够了:
<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>
轮询的消息发送到的通道。如果存储过程或函数不返回任何数据,
Message的有效负载为 null。必填。如果此属性设置为
true,则所有来自存储过程调用且没有相应SqlOutParameter声明的结果都将被忽略。例如,存储过程可以返回一个更新计数值,即使你的存储过程只声明了一个结果参数。确切的行为取决于数据库实现。该值设置在底层的JdbcTemplate上。默认值为true。可选。指示是否应包含此过程的返回值。自 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>
此端点的接收消息通道。必需。
指定当此端点作为订阅者连接到通道时的调用顺序。当该通道使用
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="" />
该端点的接收消息通道。必需。
收到数据库响应后应发送回复的消息通道。可选。
允许您指定此网关在抛出异常之前等待回复消息成功发送的时间长度。请记住,当发送到
DirectChannel时,调用发生在发送者的线程中。因此,发送操作的失败可能是由下游的其他组件引起的。该值以毫秒为单位指定。可选。指示是否应包含此过程的返回值。可选。
如果将
skip-undeclared-results属性设置为true,则所有来自存储过程调用的结果(没有对应的SqlOutParameter声明)都将被跳过。例如,存储过程可能会返回一个更新计数值,即使您的存储过程只声明了一个结果参数。确切的行为取决于数据库。该值设置在底层JdbcTemplate上,默认值为true。可选。
示例
本节包含两个调用 Apache Derby 存储过程的示例。第一个过程调用一个返回 ResultSet 的存储过程。通过使用 RowMapper,数据被转换成一个领域对象,该领域对象随后成为 Spring Integration 消息的有效载荷。
在第二个示例中,我们调用一个使用输出参数来返回数据的存储过程。
看看 Spring Integration Samples 项目。
该项目包含这里引用的 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>