INT-1201: add support for nested queries

This commit is contained in:
David Syer
2010-06-24 07:54:47 +00:00
parent 69d19fdf8c
commit 4ed5ae919b
9 changed files with 445 additions and 119 deletions

View File

@@ -46,22 +46,25 @@ public abstract class IntegrationNamespaceUtils {
static final String REF_ATTRIBUTE = "ref";
static final String METHOD_ATTRIBUTE = "method";
static final String ORDER = "order";
/**
* Configures the provided bean definition builder with a property
* value corresponding to the attribute whose name is provided if
* that attribute is defined in the given element.
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be
* used to populate the property
* @param propertyName the name of the property to be populated
* @param builder
* the bean definition builder to be configured
* @param element
* the XML element where the attribute should be defined
* @param attributeName
* the name of the attribute whose value will be used to populate
* the property
* @param propertyName
* the name of the property to be populated
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder,
Element element, String attributeName, String propertyName) {
public static void setValueIfAttributeDefined(
BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName) {
String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue)) {
builder.addPropertyValue(propertyName, attributeValue);
@@ -69,41 +72,50 @@ public abstract class IntegrationNamespaceUtils {
}
/**
* Configures the provided bean definition builder with a property
* value corresponding to the attribute whose name is provided if
* that attribute is defined in the given element.
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
*
* <p>The property name will be the camel-case equivalent of the lower
* case hyphen separated attribute (e.g. the "foo-bar" attribute would
* match the "fooBar" property).
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element - the XML element where the attribute should be defined
* @param attributeName - the name of the attribute whose value will be set
* on the property
* @param builder
* the bean definition builder to be configured
* @param element
* - the XML element where the attribute should be defined
* @param attributeName
* - the name of the attribute whose value will be set on the
* property
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder,
Element element, String attributeName) {
setValueIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName));
public static void setValueIfAttributeDefined(
BeanDefinitionBuilder builder, Element element, String attributeName) {
setValueIfAttributeDefined(builder, element, attributeName, Conventions
.attributeNameToPropertyName(attributeName));
}
/**
* Configures the provided bean definition builder with a property
* reference to a bean. The bean reference is identified by the value
* from the attribute whose name is provided if that attribute is
* defined in the given element.
* Configures the provided bean definition builder with a property reference
* to a bean. The bean reference is identified by the value from the
* attribute whose name is provided if that attribute is defined in the
* given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be
* used as a bean reference to populate the property
* @param propertyName the name of the property to be populated
* @param builder
* the bean definition builder to be configured
* @param element
* the XML element where the attribute should be defined
* @param attributeName
* the name of the attribute whose value will be used as a bean
* reference to populate the property
* @param propertyName
* the name of the property to be populated
*/
public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder,
Element element, String attributeName, String propertyName) {
public static void setReferenceIfAttributeDefined(
BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName) {
String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue)) {
builder.addPropertyReference(propertyName, attributeValue);
@@ -111,34 +123,38 @@ public abstract class IntegrationNamespaceUtils {
}
/**
* Configures the provided bean definition builder with a property
* reference to a bean. The bean reference is identified by the value
* from the attribute whose name is provided if that attribute is
* defined in the given element.
* Configures the provided bean definition builder with a property reference
* to a bean. The bean reference is identified by the value from the
* attribute whose name is provided if that attribute is defined in the
* given element.
*
* <p>The property name will be the camel-case equivalent of the lower
* case hyphen separated attribute (e.g. the "foo-bar" attribute would
* match the "fooBar" property).
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element - the XML element where the attribute should be defined
* @param attributeName - the name of the attribute whose value will be
* used as a bean reference to populate the property
* @param builder
* the bean definition builder to be configured
* @param element
* - the XML element where the attribute should be defined
* @param attributeName
* - the name of the attribute whose value will be used as a bean
* reference to populate the property
*
* @see Conventions#attributeNameToPropertyName(String)
*/
public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder,
Element element, String attributeName) {
public static void setReferenceIfAttributeDefined(
BeanDefinitionBuilder builder, Element element, String attributeName) {
setReferenceIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName));
}
/**
* Provides a user friendly description of an element based on its node
* name and, if available, its "id" attribute value. This is useful for
* creating error messages from within bean definition parsers.
* Provides a user friendly description of an element based on its node name
* and, if available, its "id" attribute value. This is useful for creating
* error messages from within bean definition parsers.
*/
public static String createElementDescription(Element element) {
String elementId = "'" + element.getNodeName() + "'";
@@ -155,53 +171,106 @@ public abstract class IntegrationNamespaceUtils {
* attribute, this will create and register a PollerMetadata instance and
* then add it as a property reference of the target builder.
*
* @param pollerElement the "poller" element to parse
* @param targetBuilder the builder that expects the "trigger" property
* @param parserContext the parserContext for the target builder
* @param pollerElement
* the "poller" element to parse
* @param targetBuilder
* the builder that expects the "trigger" property
* @param parserContext
* the parserContext for the target builder
*/
public static void configurePollerMetadata(Element pollerElement, BeanDefinitionBuilder targetBuilder, ParserContext parserContext) {
public static void configurePollerMetadata(Element pollerElement,
BeanDefinitionBuilder targetBuilder, ParserContext parserContext) {
if (pollerElement.hasAttribute("ref")) {
if (pollerElement.getAttributes().getLength() != 1) {
parserContext.getReaderContext().error(
"A 'poller' element that provides a 'ref' must have no other attributes.", pollerElement);
parserContext
.getReaderContext()
.error(
"A 'poller' element that provides a 'ref' must have no other attributes.",
pollerElement);
}
if (pollerElement.getChildNodes().getLength() != 0) {
parserContext.getReaderContext().error(
"A 'poller' element that provides a 'ref' must have no child elements.", pollerElement);
parserContext
.getReaderContext()
.error(
"A 'poller' element that provides a 'ref' must have no child elements.",
pollerElement);
}
targetBuilder.addPropertyReference("pollerMetadata", pollerElement.getAttribute("ref"));
}
else {
BeanDefinition beanDefinition = parserContext.getDelegate().parseCustomElement(
pollerElement, targetBuilder.getBeanDefinition());
targetBuilder.addPropertyReference("pollerMetadata", pollerElement
.getAttribute("ref"));
} else {
BeanDefinition beanDefinition = parserContext.getDelegate()
.parseCustomElement(pollerElement,
targetBuilder.getBeanDefinition());
if (beanDefinition == null) {
parserContext.getReaderContext().error("BeanDefinition must not be null", pollerElement);
parserContext.getReaderContext().error(
"BeanDefinition must not be null", pollerElement);
}
targetBuilder.addPropertyValue("pollerMetadata", beanDefinition);
}
}
public static BeanComponentDefinition parseInnerHandlerDefinition(Element element, ParserContext parserContext){
// parses out inner bean definition for concrete implementation if defined
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "bean");
/**
* Get a text value from a named attribute if it exists, otherwise check for
* a nested element of the same name. If both are specified it is an error,
* but if neither is specified, just returns null.
*
* @param element
* a DOM node
* @param name
* the name of the property (attribute or child element)
* @param parserContext
* the current context
* @return the text from the attribite or element or null
*/
public static String getTextFromAttributeOrNestedElement(Element element,
String name, ParserContext parserContext) {
String attr = element.getAttribute(name);
Element childElement = DomUtils.getChildElementByTagName(element, name);
if (StringUtils.hasText(attr) && childElement != null) {
parserContext.getReaderContext().error(
"Either an attribute or a child element can be specified for "
+ name + " but not both", element);
return null;
}
if (!StringUtils.hasText(attr) && childElement == null) {
return null;
}
return StringUtils.hasText(attr) ? attr : childElement.getTextContent();
}
public static BeanComponentDefinition parseInnerHandlerDefinition(
Element element, ParserContext parserContext) {
// parses out inner bean definition for concrete implementation if
// defined
List<Element> childElements = DomUtils.getChildElementsByTagName(
element, "bean");
BeanComponentDefinition innerComponentDefinition = null;
if (childElements != null && childElements.size() == 1){
if (childElements != null && childElements.size() == 1) {
Element beanElement = childElements.get(0);
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(beanElement);
bdHolder = delegate.decorateBeanDefinitionIfRequired(beanElement, bdHolder);
BeanDefinitionHolder bdHolder = delegate
.parseBeanDefinitionElement(beanElement);
bdHolder = delegate.decorateBeanDefinitionIfRequired(beanElement,
bdHolder);
BeanDefinition inDef = bdHolder.getBeanDefinition();
String beanName = BeanDefinitionReaderUtils.generateBeanName(inDef, parserContext.getRegistry());
innerComponentDefinition = new BeanComponentDefinition(inDef, beanName);
String beanName = BeanDefinitionReaderUtils.generateBeanName(inDef,
parserContext.getRegistry());
innerComponentDefinition = new BeanComponentDefinition(inDef,
beanName);
parserContext.registerBeanComponent(innerComponentDefinition);
}
String ref = element.getAttribute(REF_ATTRIBUTE);
Assert.isTrue(!(StringUtils.hasText(ref) && innerComponentDefinition != null), "Ambiguous definition. Inner bean " +
(innerComponentDefinition == null
? innerComponentDefinition
: innerComponentDefinition.getBeanDefinition().getBeanClassName()) + " declaration and \"ref\" " + ref +
" are not allowed together.");
Assert
.isTrue(
!(StringUtils.hasText(ref) && innerComponentDefinition != null),
"Ambiguous definition. Inner bean "
+ (innerComponentDefinition == null ? innerComponentDefinition
: innerComponentDefinition
.getBeanDefinition()
.getBeanClassName())
+ " declaration and \"ref\" " + ref
+ " are not allowed together.");
return innerComponentDefinition;
}

View File

@@ -52,7 +52,10 @@ public class JdbcMessageHandlerParser extends AbstractOutboundChannelAdapterPars
"Exactly one of the attributes data-source or "
+ "simple-jdbc-operations should be set for the JDBC outbound-channel-adapter", source);
}
String query = element.getAttribute("query");
String query = IntegrationNamespaceUtils.getTextFromAttributeOrNestedElement(element, "query", parserContext);
if (!StringUtils.hasText(query)) {
throw new BeanCreationException("The query attrbitue is required");
}
if (!StringUtils.hasText(query)) {
throw new BeanCreationException("The query attrbitue is required");
}

View File

@@ -55,10 +55,11 @@ public class JdbcPollingChannelAdapterParser extends AbstractPollingInboundChann
parserContext.getReaderContext().error("Exactly one of the attributes data-source or " +
"simple-jdbc-operations should be set for the JDBC inbound-channel-adapter", source);
}
String query = element.getAttribute("query");
String query = IntegrationNamespaceUtils.getTextFromAttributeOrNestedElement(element, "query", parserContext);
if (!StringUtils.hasText(query)) {
throw new BeanCreationException("The query attrbitue is required");
}
String update = IntegrationNamespaceUtils.getTextFromAttributeOrNestedElement(element, "update", parserContext);
if (refToDataSourceSet) {
builder.addConstructorArgReference(dataSourceRef);
}
@@ -68,7 +69,9 @@ public class JdbcPollingChannelAdapterParser extends AbstractPollingInboundChann
builder.addConstructorArgValue(query);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "row-mapper");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "sql-parameter-source-factory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "update", "updateSql");
if (update!=null) {
builder.addPropertyValue("updateSql", update);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "update-per-row");
return BeanDefinitionReaderUtils.registerWithGeneratedName(
builder.getBeanDefinition(), parserContext.getRegistry());

View File

@@ -1,12 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/jdbc" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration" targetNamespace="http://www.springframework.org/schema/integration/jdbc"
<xsd:schema xmlns="http://www.springframework.org/schema/integration/jdbc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/jdbc"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/integration" schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.0.xsd" />
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -49,7 +52,8 @@
specified (but not both).
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jdbc.core.JdbcOperations" />
<tool:expected-type
type="org.springframework.jdbc.core.JdbcOperations" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@@ -85,7 +89,8 @@
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jdbc.support.lob.LobHandler" />
<tool:expected-type
type="org.springframework.jdbc.support.lob.LobHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@@ -106,14 +111,45 @@
<xsd:complexContent>
<xsd:extension base="jdbcType">
<xsd:sequence>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
<xsd:element name="query" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
A select query to execute when a message is
polled. In general
the query can return multiple
rows, because
the result will be a List (of type determined by the
row
mapper).
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="update" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
An update query to execute when a message is
polled. If the poll is in a transaction then the
update will
roll back if the transaction does.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element ref="integration:poller" minOccurs="0"
maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="query" type="xsd:string" use="required">
<xsd:attribute name="query" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
A select query to execute when a message is polled. In general the query can return multiple
rows, because the result will be a List (of type determined by the row mapper).
A select query to execute when a message is
polled. In general the query can return multiple
rows, because
the result will be a List (of type determined by the row
mapper).
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
@@ -122,13 +158,17 @@
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Reference to a row mapper to use to convert JDBC result set rows to message payloads.
Reference to a row mapper to use to convert
JDBC result set rows to message payloads.
Optional
with default that maps
result set row to a map (column name to column value). Other simple
with default
that maps
result set row to a map (column name to column value).
Other simple
use cases can
be handled
with out-of-the box implementations from Spring JDBC. Others require a custom row
with out-of-the box
implementations from Spring JDBC. Others require a custom row
mapper.
</xsd:documentation>
<tool:annotation kind="ref">
@@ -141,18 +181,23 @@
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
An update query to execute when a message is polled. If the poll is in a transaction then the
update will roll back if the transaction does.
An update query to execute when a message is
polled. If the poll is in a transaction then the
update will
roll back if the transaction does.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="update-per-row" type="xsd:boolean" default="false">
<xsd:attribute name="update-per-row" type="xsd:boolean"
default="false">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Flag to indicate whether the update query should be executed per message, or per row (in the
case that a message contains multiple rows).
Flag to indicate whether the update query
should be executed per message, or per row (in the
case that a
message contains multiple rows).
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
@@ -161,10 +206,12 @@
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Channel to which polled messages will be sent.
Channel to which polled messages will be
sent.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
<tool:expected-type
type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@@ -184,15 +231,36 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="jdbcType">
<xsd:attribute name="query" type="xsd:string" use="required">
<xsd:sequence>
<xsd:element name="query" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
An SQL update query to execute (INSERT,
UPDATE
or DELETE). Bean properties of the outgoing
message can be
referenced in named parameters, e.g. "INSERT into FOOS (ID,
NAME) values (:headers[business.key],
:payload)". More complex
requirements can be implemented by
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="query" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
An SQL update query to execute (INSERT, UPDATE
An SQL update query to execute (INSERT,
UPDATE
or DELETE). Bean properties of the outgoing
message can be
referenced in named parameters, e.g. "INSERT into FOOS (ID, NAME) values (:headers[business.key],
:payload)". More complex requirements can be implemented by
referenced in named parameters, e.g. "INSERT into FOOS (ID,
NAME) values (:headers[business.key],
:payload)". More complex
requirements can be implemented by
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
@@ -201,11 +269,14 @@
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Channel from which messages will be output. When a message is sent to this channel it will
cause the query to be executed.
Channel from which messages will be output.
When a message is sent to this channel it will
cause the query
to be executed.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.core.MessageChannel" />
<tool:expected-type
type="org.springframework.integration.core.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@@ -224,7 +295,8 @@
database. Either this or the
simple-jdbc-operations
must be
specified (but not both).
specified
(but not both).
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
@@ -239,7 +311,8 @@
<xsd:documentation>
Reference to a JdbcOperations. Either
this or
the data-source must be
the
data-source must be
specified (but not both).
</xsd:documentation>
<tool:annotation kind="ref">
@@ -252,14 +325,21 @@
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Reference to a SqlParameterSourceFactory. For an inbound adapter the input is the result of the
query, and for an outbound adapter the input is the whole outgoing message. The default factory creates a bean
property parameter source for a generic input (like a Message), and treats a List in a special way: the List is
assumed to contain entities with a field called "id" and these are collected and copied to a field in the
Reference to a SqlParameterSourceFactory. For an
inbound adapter the input is the result of the
query, and for an
outbound adapter the input is the whole outgoing message. The
default factory creates a bean
property parameter source for a
generic input (like a Message), and treats a List in a special
way: the List is
assumed to contain entities with a field called
"id" and these are collected and copied to a field in the
parameter source called "idList".
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.jdbc.SqlParameterSourceFactory" />
<tool:expected-type
type="org.springframework.integration.jdbc.SqlParameterSourceFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>

View File

@@ -54,6 +54,16 @@ public class JdbcMessageHandlerParserTests {
assertEquals("Wrong name", "bar", map.get("name"));
}
@Test
public void testMapPayloadNestedQueryOutboundChannelAdapter(){
setUp("handlingMapPayloadNestedQueryJdbcOutboundChannelAdapterTest.xml", getClass());
Message<?> message = MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")).build();
channel.send(message);
Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * from FOOS");
assertEquals("Wrong id", message.getHeaders().getId().toString(), map.get("ID"));
assertEquals("Wrong name", "bar", map.get("name"));
}
@Test
public void testParameterSourceOutboundChannelAdapter(){
setUp("handlingParameterSourceJdbcOutboundChannelAdapterTest.xml", getClass());

View File

@@ -52,6 +52,16 @@ public class JdbcPollingChannelAdapterParserTests {
assertNull(channelTemplate.receive());
}
@Test
public void testSimpleInboundChannelAdapterWithNestedUpdate(){
setUp("pollingForMapJdbcInboundChannelAdapterWithNestedUpdateTest.xml", getClass());
this.jdbcTemplate.update("insert into item values(1,'',2)");
Message<?> message = channelTemplate.receive();
assertNotNull(message);
message = channelTemplate.receive();
assertNull(channelTemplate.receive());
}
@Test
public void testExtendedInboundChannelAdapter(){
setUp("pollingWithJdbcOperationsJdbcInboundChannelAdapterTest.xml", getClass());

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration/jdbc"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:si="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jdbc
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<outbound-channel-adapter channel="target" data-source="dataSource">
<query>insert into foos (id, status, name) values (:headers[$id], 0, :payload[foo])</query>
</outbound-channel-adapter>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />
</beans:beans>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration/jdbc"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jdbc
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<inbound-channel-adapter channel="target" data-source="dataSource">
<query>select * from item where status=2</query>
<update>update item set status=10 where id in (:idList)</update>
</inbound-channel-adapter>
<beans:import resource="jdbcInboundChannelAdapterCommonConfig.xml" />
</beans:beans>

113
src/docbkx/jdbc.xml Normal file
View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="jdbc">
<title>JDBC Support</title>
<para>Spring Integration provides Channel Adapters for receiving and sending
messages via database queries.</para>
<section id="jdbc-inbound-channel-adapter">
<title>Inbound Channel Adapter</title>
<para>The main function of an inbound Channel Adapter is to execute a SQL
<code>SELECT</code> query and turn the result set into a message. The
message payload is the whole result set, expressed as a
<classname>List</classname>, and the types of the items in the list
depends on the row-mapping strategy that is used. The default strategy is
a generic mapper that just returns a <classname>Map</classname> for each
row i nthe query. Optionally this can be changed by adding a reference to
requires a reference to a <classname>RowMapper</classname> instance (see
the <ulink
url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jdbc.html">Spring
JDBC</ulink> documentation for more detailed information about row
mapping).<note>
<para>If you want to convert rows in the SELECT query result to
individual messages you can use a downstream splitter.</para>
</note></para>
<para>The inbound adapter also requires a reference to either
<classname>JdbcTemplate</classname> instance or
<interfacename>DataSource</interfacename>. The following example defines
an inbound Channel Adapter with a <classname>DataSource</classname>
reference. <programlisting language="xml"><![CDATA[<jdbc:inbound-channel-adapter query="select * from item where status=2"
channel="target" data-source="dataSource"
update="update item set status=10 where id in (:idList)" />]]></programlisting>
<note>
The parameters in the update query are specified with a colon (:) prefix to the name of a map key. This is a standard feature of the named parameter JDBC support in Spring JDBC.
</note></para>
<para>As well as the <code>SELECT</code> statement to generate the
messages, the adapter above also has an <code>UPDATE</code> statement that
is being used to mark the records as processed, so they don't show up in
the next poll. The update is parameterised by the list of ids from the
original select. This is done through a naming convention by default (a
column in the input result set called "id" is translated into a list in
the parameter map for the update called "idList"). To change the parameter
generation strategy you can inject a
<classname>SqlParameterSourceFactory</classname> into the adapter to
override the default behaviour (the adapter has a
<code>sql-parameter-source-factory</code> attribute).</para>
<section>
<title>Polling and Transactions</title>
<para>The inbound adapter accepts a regular Spring Integration poller as
a sub element, so for instance the frequency of the polling can be
controlled. A very important feature of the poller for JDBC usage is the
option to wrap the poll operation in a transaction, for example:</para>
<programlisting><![CDATA[<jdbc:inbound-channel-adapter query="..."
channel="target" data-source="dataSource"
update="...">
<poller>
<interval-trigger interval="1000"/>
<transactional/>
</poller>
</jdbc:inbound-channel-adapter>]]></programlisting>
<para>In this example the database is polled every 1000 milliseconds,
and the update and select queries are both executed in the same
transaction. The transaction manager configuration is not shown, but as
long as it is aware of the data source then the poll is transactional. A
common use case is for the downstream channels to be direct channels
(the default), so that the endpoints are invoked in the same thread, and
hence the same transaction. then if any of them fails, the transaction
rolls back and the input data are reverted to their original
state.</para>
</section>
</section>
<section id="jdbc-outbound-channel-adapter">
<title>Outbound Channel Adapter</title>
<para>The outbound Channel Adapter is the inverse of the inbound: its role
is to handle a message and use it to execute a SQL query. The message
payload and headers are available by default as input parameters to the
query, for instance: <programlisting language="xml"><![CDATA[<jdbc:outbound-channel-adapter
query="insert into foos (id, status, name) values (:headers[$id], 0, :payload[foo])"
channel="input" data-source="dataSource"/>]]></programlisting> In the
example above, messages arriving on the channel "input" have a payload of
a map with key "foo", so the <code>[]</code> operator dereferences that
value from the map. The headers are also accessed as a map. <note>
The parameters in the query above are bean paths in the incoming message (they are not Spring EL expressions). This behaviour is part of the
<classname>MapSqlParameterSource</classname>
in Spring JDBC, which is the default source created by the outbound adapter. Other behaviour is possible in the adapter, and only requires the user to inject a different
<classname>SqlParameterSourceFactory</classname>
.
</note></para>
<para>The outbound adapter requires a reference to either a DataSource or
a JdbcTemplate. It can also have a
<classname>SqlParameterSourceFactory</classname> injected to control the
binding of incoming message to the query. </para>
<para>If the input channel is a direct channel then the outbound adapter
runs its query in the same thread, and therefor ethe same transaction (if
there is one) as the sender of the message.</para>
</section>
</chapter>