INT-3322 JDBC i-c-a SpEL Select ParameterSource

JIRA: https://jira.spring.io/browse/INT-3322

Support the use of an ExpressionEvaluatingSqlParameterSource
from the ExpressionEvaluatingSqlParameterSourceFactory as the
`select-sql-parameter-source` for an inbound channel adapter.

Add documentation showing how to construct the bean.

Add a mechanism to disable caching so that the expression is
re-evaluated each time. However, the value should still be
cached between the `hasValue()` and `getValue()` calls.

INT-3322 Polishing; PR Comments
This commit is contained in:
Gary russell
2014-03-13 16:09:19 +02:00
committed by Artem Bilan
parent 98b46c61f3
commit c73cc40e23
5 changed files with 155 additions and 12 deletions

View File

@@ -125,7 +125,18 @@ public class ExpressionEvaluatingSqlParameterSourceFactory extends AbstractExpre
@Override
public SqlParameterSource createParameterSource(final Object input) {
return new ExpressionEvaluatingSqlParameterSource(input, this.staticParameters, this.parameterExpressions);
return new ExpressionEvaluatingSqlParameterSource(input, this.staticParameters, this.parameterExpressions, true);
}
/**
* Create an expression evaluating {@link SqlParameterSource} that does not cache it's results. Useful for cases
* where the source is used multiple times, for example in a {@code <int-jdbc:inbound-channel-adapter/>} for the
* {@code select-sql-parameter-source} attribute.
* @param input The root object for the evaluation.
* @return The parameter source.
*/
public SqlParameterSource createParameterSourceNoCache(final Object input) {
return new ExpressionEvaluatingSqlParameterSource(input, this.staticParameters, this.parameterExpressions, false);
}
@Override
@@ -138,21 +149,32 @@ public class ExpressionEvaluatingSqlParameterSourceFactory extends AbstractExpre
private final Object input;
private volatile Map<String, Object> values = new HashMap<String, Object>();
private final Map<String, Object> values = new HashMap<String, Object>();
private final Map<String, Expression[]> parameterExpressions;
private final boolean cache;
private ExpressionEvaluatingSqlParameterSource(Object input, Map<String, ?> staticParameters,
Map<String, Expression[]> parameterExpressions) {
Map<String, Expression[]> parameterExpressions, boolean cache) {
this.input = input;
this.parameterExpressions = parameterExpressions;
this.values.putAll(staticParameters);
this.cache = cache;
}
@Override
public Object getValue(String paramName) throws IllegalArgumentException {
return this.doGetValue(paramName, false);
}
public Object doGetValue(String paramName, boolean calledFromHasValue) throws IllegalArgumentException {
if (values.containsKey(paramName)) {
return values.get(paramName);
Object cachedByHasValue = values.get(paramName);
if (!this.cache) {
values.remove(paramName);
}
return cachedByHasValue;
}
if (!parameterExpressions.containsKey(paramName)) {
@@ -174,7 +196,9 @@ public class ExpressionEvaluatingSqlParameterSourceFactory extends AbstractExpre
}
Object value = evaluateExpression(expression, input);
values.put(paramName, value);
if (this.cache || calledFromHasValue) {
values.put(paramName, value);
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved expression " + expression + " to " + value);
}
@@ -184,7 +208,7 @@ public class ExpressionEvaluatingSqlParameterSourceFactory extends AbstractExpre
@Override
public boolean hasValue(String paramName) {
try {
Object value = getValue(paramName);
Object value = doGetValue(paramName, true);
if (value == ERROR) {
return false;
}
@@ -193,7 +217,9 @@ public class ExpressionEvaluatingSqlParameterSourceFactory extends AbstractExpre
if (logger.isDebugEnabled()) {
logger.debug("Could not evaluate expression", e);
}
values.put(paramName, ERROR);
if (this.cache) {
values.put(paramName, ERROR);
}
return false;
}
return true;

View File

@@ -202,6 +202,10 @@
that query has
placeholders (e.g. "SELECT * from FOO where KEY=:key") they
will be bound from this source by name.
Note: if you use the framework's 'ExpressionEvaluatingSqlParameterSourceFactory'
to create a SpEL-based parameter source, be sure to use the 'createParameterSourceNoCache'
method so that the expression will be re-evaluated on each poll. See the reference
documentation for more information.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jdbc.core.namedparam.SqlParameterSource" />

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -129,6 +129,20 @@ public class JdbcPollingChannelAdapterParserTests {
assertEquals("BAR", list.get(0).get("NAME"));
}
@Test
public void testSelectParameterSourceFactoryInboundChannelAdapter() {
setUp("pollingWithSelectParameterSourceJdbcInboundChannelAdapterTest.xml", getClass());
this.jdbcTemplate.update("insert into item values(1,'',42)");
Message<?> message = messagingTemplate.receive();
assertNotNull(message);
assertEquals(42, ((Map<?,?>) ((List<?> )message.getPayload()).get(0)).get("STATUS"));
this.jdbcTemplate.update("insert into item values(2,'',84)");
this.appCtx.getBean(Status.class).which = 84;
message = messagingTemplate.receive();
assertNotNull(message);
assertEquals(84, ((Map<?,?>) ((List<?> )message.getPayload()).get(0)).get("STATUS"));
}
@Test
public void testParameterSourceInboundChannelAdapter() {
setUp("pollingWithParametersForMapJdbcInboundChannelAdapterTest.xml", getClass());
@@ -141,6 +155,7 @@ public class JdbcPollingChannelAdapterParserTests {
public void testMaxRowsInboundChannelAdapter() {
setUp("pollingWithMaxRowsJdbcInboundChannelAdapterTest.xml", getClass());
new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
jdbcTemplate.update("insert into item values(1,'',2)");
jdbcTemplate.update("insert into item values(2,'',2)");
@@ -200,14 +215,26 @@ public class JdbcPollingChannelAdapterParserTests {
public static class TestSqlParameterSource extends AbstractSqlParameterSource {
@Override
public Object getValue(String paramName) throws IllegalArgumentException {
return 2;
}
@Override
public boolean hasValue(String paramName) {
return true;
}
}
public static class Status {
private int which = 42;
public int which() {
return this.which;
}
}
}

View File

@@ -0,0 +1,33 @@
<?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 query="select * from item where status=:status" channel="target"
data-source="dataSource" select-sql-parameter-source="parameterSource"
update="delete from item" />
<beans:import resource="jdbcInboundChannelAdapterCommonConfig.xml" />
<beans:bean id="parameterSource" factory-bean="parameterSourceFactory" factory-method="createParameterSourceNoCache">
<beans:constructor-arg value="" />
</beans:bean>
<beans:bean id="parameterSourceFactory" class="org.springframework.integration.jdbc.ExpressionEvaluatingSqlParameterSourceFactory">
<beans:property name="parameterExpressions">
<beans:map>
<beans:entry key="status" value="@statusBean.which()" />
</beans:map>
</beans:property>
</beans:bean>
<beans:bean id="statusBean" class="org.springframework.integration.jdbc.config.JdbcPollingChannelAdapterParserTests$Status" />
</beans:beans>

View File

@@ -74,12 +74,65 @@
channel="target" data-source="dataSource"
update="update item set status=10 where id in (:id)" />]]></programlisting>
<note>
The parameters in the update query are specified with a colon (:) prefix to the name of a parameter (which in this case is an expression to be applied to each of the rows in the polled result set). This is a standard feature of the named parameter JDBC support in Spring JDBC combined with a convention (projection onto the polled result list) adopted in Spring Integration. The underlying Spring JDBC features limit the available expressions (e.g. most special characters other than period are disallowed), but since the target is usually a list of or an individual object addressable by simple bean paths this isn't unduly restrictive.
</note> To change the parameter generation strategy you can inject a
The parameters in the update query are specified with a colon (:)
prefix to the name of a parameter (which in this case is an expression
to be applied to each of the rows in the polled result set).
This is a standard feature of the named parameter JDBC support in Spring JDBC
combined with a convention (projection onto the polled result list)
adopted in Spring Integration.
The underlying Spring JDBC features limit the available expressions
(e.g. most special characters other than period are disallowed),
but since the target is usually a list of or an individual object addressable by simple bean paths this isn't unduly restrictive.
</note>
To change the parameter generation strategy you can inject a
<classname>SqlParameterSourceFactory</classname> into the adapter to
override the default behavior (the adapter has a
<code>sql-parameter-source-factory</code> attribute).</para>
<code>sql-parameter-source-factory</code> attribute). Spring Integration
provides a <classname>ExpressionEvaluatingSqlParameterSourceFactory</classname> which
will create a SpEL-based parameter source, with the results of the query as the
<code>#root</code> object. (If <code>update-per-row</code> is true, the root object
is the row). If the same parameter name appears multiple times in the update query, it
is evaluated only one time, and its result is cached.
</para>
<para>
You can also use a parameter source for the select query. In this case, since there is no "result"
object to evaluate against, a single parameter source is used each time (rather than using a
parameter source factory). Starting with <emphasis>version 4.0</emphasis>, you can use Spring
to create a SpEL based parameter source as follows:
</para>
<programlisting language="xml"><![CDATA[<int-jdbc:inbound-channel-adapter query="select * from item where status=:status"
channel="target" data-source="dataSource"
select-sql-parameter-source="parameterSource" />
<bean id="parameterSource" factory-bean="parameterSourceFactory"
factory-method="createParameterSourceNoCache">
<constructor-arg value="" />
</bean>
<bean id="parameterSourceFactory"
class="o.s.integration.jdbc.ExpressionEvaluatingSqlParameterSourceFactory">
<property name="parameterExpressions">
<map>
<entry key="status" value="@statusBean.which()" />
</map>
</property>
</bean>
<bean id="statusBean" class="foo.StatusDetermination" />]]></programlisting>
<para>
The <code>value</code> in each parameter expression can be any valid SpEL expression.
The <code>#root</code> object for the expression evaluation is the
constructor argument defined on the <code>parameterSource</code> bean. It is static
for all evaluations (in this case, an empty String).
</para>
<important>
Use the <code>createParameterSourceNoCache</code> factory method; otherwise the parameter source will
cache the result of the evaluation. Also note that, because caching is disabled, if the same
parameter name appears in the select query multiple times, it will be re-evaluated for each
occurrence.
</important>
<section>
<title>Polling and Transactions</title>
@@ -355,7 +408,7 @@
For more information, please see:
</para>
<para>
<ulink url="http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html"></ulink>
<ulink url="http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html"/>
</para>
<para>
Also important, please ensure that you use an up-to-date version