GH-8760 Postgres: using DELETE ... RETURNING

Fixes https://github.com/spring-projects/spring-integration/issues/8760

* Make `PostgresChannelMessageStoreQueryProvider` to use single `DELETE ... RETURNING` for polling statements
* Add `isUsingSingleStatementForPoll` and use it from `JdbcChannelMessageStore`
* Execute Postgres init scripts to `PostgresContainerTest`
* Code clean up
* Document the new feature
This commit is contained in:
Johannes Edmeier
2023-10-11 17:05:00 -04:00
committed by Artem Bilan
parent 29186e2b46
commit 87a2ac5b5c
9 changed files with 156 additions and 43 deletions

View File

@@ -65,7 +65,7 @@ import org.springframework.util.StringUtils;
* Channel-specific implementation of
* {@link org.springframework.integration.store.BasicMessageGroupStore} using a relational
* database via JDBC.
*
* <p>
* This message store shall be used for message channels only.
* <p>
* As such, the {@link JdbcChannelMessageStore} uses database specific SQL queries.
@@ -86,6 +86,7 @@ import org.springframework.util.StringUtils;
* @author Gary Russell
* @author Meherzad Lahewala
* @author Trung Pham
* @author Johannes Edmeier
*
* @since 2.2
*/
@@ -554,12 +555,16 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
public Message<?> pollMessageFromGroup(Object groupId) {
String key = getKey(groupId);
Message<?> polledMessage = doPollForMessage(key);
if (polledMessage != null && !doRemoveMessageFromGroup(groupId, polledMessage)) {
if (polledMessage != null && !isSingleStatementForPoll() && !doRemoveMessageFromGroup(groupId, polledMessage)) {
return null;
}
return polledMessage;
}
private boolean isSingleStatementForPoll() {
return this.channelMessageStoreQueryProvider.isSingleStatementForPoll();
}
/**
* This method executes a call to the DB to get the oldest Message in the
* MessageGroup which in the context of the {@link JdbcChannelMessageStore}

View File

@@ -25,6 +25,7 @@ package org.springframework.integration.jdbc.store.channel;
* @author Artem Bilan
* @author Gary Russell
* @author Adama Sorho
* @author Johannes Edmeier
*
* @since 2.2
*/
@@ -33,7 +34,7 @@ public interface ChannelMessageStoreQueryProvider {
String SELECT_COMMON = """
SELECT %PREFIX%CHANNEL_MESSAGE.MESSAGE_ID, %PREFIX%CHANNEL_MESSAGE.MESSAGE_BYTES
from %PREFIX%CHANNEL_MESSAGE
where %PREFIX%CHANNEL_MESSAGE.GROUP_KEY = :group_key and %PREFIX%CHANNEL_MESSAGE.REGION = :region\s
where %PREFIX%CHANNEL_MESSAGE.GROUP_KEY = :group_key and %PREFIX%CHANNEL_MESSAGE.REGION = :region
""";
/**
@@ -125,4 +126,14 @@ public interface ChannelMessageStoreQueryProvider {
*/
String getPriorityPollFromGroupQuery();
/**
* Indicate if the queries for polling are using a single statement (e.g. DELETE ... RETURNING) to
* retrieve and delete the message from the channel store.
* @return true if a single statement is used, false if a select and delete is required.
* @since 6.2
*/
default boolean isSingleStatementForPoll() {
return false;
}
}

View File

@@ -20,6 +20,7 @@ package org.springframework.integration.jdbc.store.channel;
* @author Gunnar Hillert
* @author Artem Bilan
* @author Adama Sorho
* @author Johannes Edmeier
*
* @since 2.2
*/
@@ -27,30 +28,69 @@ public class PostgresChannelMessageStoreQueryProvider implements ChannelMessageS
@Override
public String getPollFromGroupExcludeIdsQuery() {
return SELECT_COMMON
+ "and %PREFIX%CHANNEL_MESSAGE.MESSAGE_ID not in (:message_ids) "
+ "order by CREATED_DATE, MESSAGE_SEQUENCE LIMIT 1 FOR UPDATE SKIP LOCKED";
return """
delete
from %PREFIX%CHANNEL_MESSAGE
where CTID = (select CTID
from %PREFIX%CHANNEL_MESSAGE
where %PREFIX%CHANNEL_MESSAGE.GROUP_KEY = :group_key
and %PREFIX%CHANNEL_MESSAGE.REGION = :region
and %PREFIX%CHANNEL_MESSAGE.MESSAGE_ID not in (:message_ids)
order by CREATED_DATE, MESSAGE_SEQUENCE
limit 1 for update skip locked)
returning MESSAGE_ID, MESSAGE_BYTES;
""";
}
@Override
public String getPollFromGroupQuery() {
return SELECT_COMMON +
"order by CREATED_DATE, MESSAGE_SEQUENCE LIMIT 1 FOR UPDATE SKIP LOCKED";
return """
delete
from %PREFIX%CHANNEL_MESSAGE
where CTID = (select CTID
from %PREFIX%CHANNEL_MESSAGE
where %PREFIX%CHANNEL_MESSAGE.GROUP_KEY = :group_key
and %PREFIX%CHANNEL_MESSAGE.REGION = :region
order by CREATED_DATE, MESSAGE_SEQUENCE
limit 1 for update skip locked)
returning MESSAGE_ID, MESSAGE_BYTES;
""";
}
@Override
public String getPriorityPollFromGroupExcludeIdsQuery() {
return SELECT_COMMON +
"and %PREFIX%CHANNEL_MESSAGE.MESSAGE_ID not in (:message_ids) " +
"order by MESSAGE_PRIORITY DESC NULLS LAST, CREATED_DATE, MESSAGE_SEQUENCE " +
"LIMIT 1 FOR UPDATE SKIP LOCKED";
return """
delete
from %PREFIX%CHANNEL_MESSAGE
where CTID = (select CTID
from %PREFIX%CHANNEL_MESSAGE
where %PREFIX%CHANNEL_MESSAGE.GROUP_KEY = :group_key
and %PREFIX%CHANNEL_MESSAGE.REGION = :region
and %PREFIX%CHANNEL_MESSAGE.MESSAGE_ID not in (:message_ids)
order by MESSAGE_PRIORITY DESC NULLS LAST, CREATED_DATE, MESSAGE_SEQUENCE
limit 1 for update skip locked)
returning MESSAGE_ID, MESSAGE_BYTES;
""";
}
@Override
public String getPriorityPollFromGroupQuery() {
return SELECT_COMMON +
"order by MESSAGE_PRIORITY DESC NULLS LAST, CREATED_DATE, MESSAGE_SEQUENCE " +
"LIMIT 1 FOR UPDATE SKIP LOCKED";
return """
delete
from %PREFIX%CHANNEL_MESSAGE
where CTID = (select CTID
from %PREFIX%CHANNEL_MESSAGE
where %PREFIX%CHANNEL_MESSAGE.GROUP_KEY = :group_key
and %PREFIX%CHANNEL_MESSAGE.REGION = :region
order by MESSAGE_PRIORITY DESC NULLS LAST, CREATED_DATE, MESSAGE_SEQUENCE
limit 1 for update skip locked)
returning MESSAGE_ID, MESSAGE_BYTES;
""";
}
@Override
public boolean isSingleStatementForPoll() {
return true;
}
}

View File

@@ -1,12 +1,12 @@
-- Autogenerated: do not edit this file
DROP INDEX INT_MESSAGE_IX1 ;
DROP INDEX INT_CHANNEL_MSG_DATE_IDX ;
DROP INDEX INT_CHANNEL_MSG_PRIORITY_IDX ;
DROP TABLE INT_MESSAGE ;
DROP TABLE INT_MESSAGE_GROUP ;
DROP TABLE INT_GROUP_TO_MESSAGE ;
DROP TABLE INT_LOCK ;
DROP TABLE INT_CHANNEL_MESSAGE ;
DROP TABLE INT_METADATA_STORE ;
DROP SEQUENCE INT_MESSAGE_SEQ ;
DROP INDEX IF EXISTS INT_MESSAGE_IX1 ;
DROP INDEX IF EXISTS INT_CHANNEL_MSG_DATE_IDX ;
DROP INDEX IF EXISTS INT_CHANNEL_MSG_PRIORITY_IDX ;
DROP TABLE IF EXISTS INT_MESSAGE ;
DROP TABLE IF EXISTS INT_MESSAGE_GROUP ;
DROP TABLE IF EXISTS INT_GROUP_TO_MESSAGE ;
DROP TABLE IF EXISTS INT_LOCK ;
DROP TABLE IF EXISTS INT_CHANNEL_MESSAGE ;
DROP TABLE IF EXISTS INT_METADATA_STORE ;
DROP SEQUENCE IF EXISTS INT_MESSAGE_SEQ ;