INT-1201: add support for nested queries
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
@@ -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
113
src/docbkx/jdbc.xml
Normal 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>
|
||||
Reference in New Issue
Block a user