INT-3333: Return empty list as is from DB gateway (#3907)

* INT-3333: Return empty list as is from DB gateway

Fixes https://jira.spring.io/browse/INT-3333

In `JdbcOutboundGateway` and `JpaOutboundGateway` the empty result list
is treated as "no reply" and therefore `null` is returned cause
the flow to stop at this point.
It is better to return such a result as is and the target application to
decided what to do with it, e.g. a `discardChannel` on a downstream splitter
configuration.

NOTE: the `MongoDbOutboundGateway` doesn't treat an empty result as a `null`

* * Fix language in docs

Co-authored-by: Gary Russell <grussell@vmware.com>

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2022-10-11 14:39:29 -04:00
committed by GitHub
parent 6641b1f545
commit f7dd876be2
8 changed files with 125 additions and 131 deletions

View File

@@ -365,6 +365,10 @@ It can also have a `SqlParameterSourceFactory` injected to control the binding o
Starting with the version 4.2, the `request-prepared-statement-setter` attribute is available on the `<int-jdbc:outbound-gateway>` as an alternative to `request-sql-parameter-source-factory`.
It lets you specify a `MessagePreparedStatementSetter` bean reference, which implements more sophisticated `PreparedStatement` preparation before its execution.
Starting with the version 6.0, the `JdbcOutboundGateway` returns an empty list result as is instead of converting it to `null` as it was before with the meaning "no reply".
This caused an extra configuration in applications where handling of empty lists is a part of downstream logic.
See <<./splitter.adoc#split-stream-and-flux,Splitter Discard Channel>> for possible empty list handling option.
See <<jdbc-outbound-channel-adapter>> for more information about `MessagePreparedStatementSetter`.
[[jdbc-message-store]]

View File

@@ -1011,10 +1011,89 @@ public class JpaJavaApplication {
[[jpa-retrieving-outbound-gateway]]
==== Retrieving Outbound Gateway
The following example shows all the attributes that you can set on a retrieving outbound gateway and describes the key attributes:
The following example demonstrates how to configure a retrieving outbound gateway:
====
[source,xml]
[source, java, role="primary"]
.Java DSL
----
@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaJavaApplication.class)
.web(false)
.run(args);
}
@Autowired
private EntityManagerFactory entityManagerFactory;
@Bean
public IntegrationFlow retrievingGatewayFlow() {
return f -> f
.handle(Jpa.retrievingGateway(this.entityManagerFactory)
.jpaQuery("from Student s where s.id = :id")
.expectSingleResult(true)
.parameterExpression("id", "payload"))
.channel(c -> c.queue("retrieveResults"));
}
}
----
[source, kotlin, role="secondary"]
.Kotlin DSL
----
@Bean
fun retrievingGatewayFlow() =
integrationFlow {
handle(Jpa.retrievingGateway(this.entityManagerFactory)
.jpaQuery("from Student s where s.id = :id")
.expectSingleResult(true)
.parameterExpression("id", "payload"))
channel { queue("retrieveResults") }
}
----
[source, java, role="secondary"]
.Java
----
@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaJavaApplication.class)
.web(false)
.run(args);
}
@Autowired
private EntityManagerFactory entityManagerFactory;
@Bean
public JpaExecutor jpaExecutor() {
JpaExecutor executor = new JpaExecutor(this.entityManagerFactory);
jpaExecutor.setJpaQuery("from Student s where s.id = :id");
executor.setJpaParameters(Collections.singletonList(new JpaParameter("id", null, "payload")));
jpaExecutor.setExpectSingleResult(true);
return executor;
}
@Bean
@ServiceActivator(channel = "jpaRetrievingChannel")
public MessageHandler jpaOutbound() {
JpaOutboundGateway adapter = new JpaOutboundGateway(jpaExecutor());
adapter.setOutputChannelName("retrieveResults");
adapter.setGatewayType(OutboundGatewayType.RETRIEVING);
return adapter;
}
}
----
[source, xml, role="secondary"]
.XML
----
<int-jpa:retrieving-outbound-gateway request-channel=""
auto-startup="true"
@@ -1075,86 +1154,6 @@ Version 3.0 introduced this attribute.
Optional.
====
The remaining attributes are described earlier in this chapter.
See <<jpaInboundChannelAdapterParameters>> and <<jpaOutboundChannelAdapterParameters>>.
==== Configuring with Java Configuration
The following Spring Boot application shows an example of how configure the outbound adapter with Java:
====
[source, java]
----
@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaJavaApplication.class)
.web(false)
.run(args);
}
@Autowired
private EntityManagerFactory entityManagerFactory;
@Bean
public JpaExecutor jpaExecutor() {
JpaExecutor executor = new JpaExecutor(this.entityManagerFactory);
jpaExecutor.setJpaQuery("from Student s where s.id = :id");
executor.setJpaParameters(Collections.singletonList(new JpaParameter("id", null, "payload")));
jpaExecutor.setExpectSingleResult(true);
return executor;
}
@Bean
@ServiceActivator(channel = "jpaRetrievingChannel")
public MessageHandler jpaOutbound() {
JpaOutboundGateway adapter = new JpaOutboundGateway(jpaExecutor());
adapter.setOutputChannelName("retrieveResults");
adapter.setGatewayType(OutboundGatewayType.RETRIEVING);
return adapter;
}
}
----
====
==== Configuring with the Java DSL
The following Spring Boot application shows an example of how to configure the outbound adapter with the Java DSL:
====
[source, java]
----
@SpringBootApplication
@EntityScan(basePackageClasses = StudentDomain.class)
public class JpaJavaApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaJavaApplication.class)
.web(false)
.run(args);
}
@Autowired
private EntityManagerFactory entityManagerFactory;
@Bean
public IntegrationFlow retrievingGatewayFlow() {
return f -> f
.handle(Jpa.retrievingGateway(this.entityManagerFactory)
.jpaQuery("from Student s where s.id = :id")
.expectSingleResult(true)
.parameterExpression("id", "payload"))
.channel(c -> c.queue("retrieveResults"));
}
}
----
====
[IMPORTANT]
====
When you choose to delete entities upon retrieval, and you have retrieved a collection of entities, by default, entities are deleted on a per-entity basis.
@@ -1171,6 +1170,12 @@ It does not cascade to related entities.`"
For more information, see https://jcp.org/en/jsr/detail?id=317[JSR 317: Java™ Persistence 2.0]
====
NOTE: Starting with version 6.0, the `Jpa.retrievingGateway()` returns an empty list result when there are no entities returned by the query.
Previously `null` was returned ending the flow, or throwing an exception, depending on `requiresReply`.
Or, to revert to the previous behavior, add a `filter` after the gateway to filter out empty lists.
It requires extra configuration in applications where empty list handling is a part of the downstream logic.
See <<./splitter.adoc#split-stream-and-flux,Splitter Discard Channel>> for possible empty list handling options.
[[outboundGatewaySamples]]
==== JPA Outbound Gateway Samples

View File

@@ -100,6 +100,9 @@ See <<./gateway.adoc#async-gateway, Asynchronous Gateway>> for more information.
The `integrationGlobalProperties` bean is now declared by the framework as an instance of `org.springframework.integration.context.IntegrationProperties` instead of the previously deprecated `java.util.Properties`.
Message handlers which produce a collection as a reply (e.g. `JpaOutboundGateway`, `JdbcOutboundGateway` and other DB-based gateways) now return an empty result list if no records are returned by the query.
Previously, `null` was returned ending the flow, or throwing an exception, depending on `requiresReply`.
[[x6.0-rmi]]
=== RMI Removal