INT-4081: Post-merge polishing and improvements

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

* Rename `MessageGroupPreparedStatementSetter` to `ChannelMessageStorePreparedStatementSetter`
* Add JavaDocs to the `ChannelMessageStorePreparedStatementSetter`
* Polishing `JdbcChannelMessageStore` JavaDocs; remove unused `BeanFactory`
* Polishing Docs for typos and proper text flow
This commit is contained in:
Artem Bilan
2017-10-23 11:41:04 -04:00
parent 34d0859515
commit d7d56a0444
6 changed files with 211 additions and 207 deletions

View File

@@ -32,16 +32,13 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.integration.jdbc.store.channel.ChannelMessageStorePreparedStatementSetter;
import org.springframework.integration.jdbc.store.channel.ChannelMessageStoreQueryProvider;
import org.springframework.integration.jdbc.store.channel.MessageGroupPreparedStatementSetter;
import org.springframework.integration.jdbc.store.channel.MessageRowMapper;
import org.springframework.integration.jdbc.store.channel.OracleChannelMessageStoreQueryProvider;
import org.springframework.integration.store.MessageGroup;
@@ -88,26 +85,14 @@ import org.springframework.util.StringUtils;
* @author Artem Bilan
* @author Gary Russell
* @author Meherzad Lahewala
*
* @since 2.2
*/
@ManagedResource
public class JdbcChannelMessageStore implements PriorityCapableChannelMessageStore, InitializingBean, BeanFactoryAware {
public class JdbcChannelMessageStore implements PriorityCapableChannelMessageStore, InitializingBean {
private static final Log logger = LogFactory.getLog(JdbcChannelMessageStore.class);
private final Set<String> idCache = new HashSet<String>();
private final ReadWriteLock idCacheLock = new ReentrantReadWriteLock();
private final Lock idCacheReadLock = this.idCacheLock.readLock();
private final Lock idCacheWriteLock = this.idCacheLock.writeLock();
/**
* Default value for the table prefix property.
*/
public static final String DEFAULT_TABLE_PREFIX = "INT_";
/**
* Default region property, used to partition the message store. For example,
* a separate Spring Integration application with overlapping channel names
@@ -115,7 +100,10 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
*/
public static final String DEFAULT_REGION = "DEFAULT";
private ChannelMessageStoreQueryProvider channelMessageStoreQueryProvider;
/**
* Default value for the table prefix property.
*/
public static final String DEFAULT_TABLE_PREFIX = "INT_";
/**
* The name of the message header that stores a flag to indicate that the message has been saved. This is an
@@ -132,33 +120,40 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
@Deprecated
public static final String CREATED_DATE_KEY = JdbcChannelMessageStore.class.getSimpleName() + ".CREATED_DATE";
private volatile String region = DEFAULT_REGION;
private final Set<String> idCache = new HashSet<>();
private volatile String tablePrefix = DEFAULT_TABLE_PREFIX;
private final ReadWriteLock idCacheLock = new ReentrantReadWriteLock();
private volatile JdbcTemplate jdbcTemplate;
private final Lock idCacheReadLock = this.idCacheLock.readLock();
private volatile WhiteListDeserializingConverter deserializer;
private final Lock idCacheWriteLock = this.idCacheLock.writeLock();
private volatile SerializingConverter serializer;
private ChannelMessageStoreQueryProvider channelMessageStoreQueryProvider;
private volatile LobHandler lobHandler = new DefaultLobHandler();
private String region = DEFAULT_REGION;
private volatile MessageRowMapper messageRowMapper;
private String tablePrefix = DEFAULT_TABLE_PREFIX;
private volatile MessageGroupPreparedStatementSetter messageGroupPreparedStatementSetter;
private JdbcTemplate jdbcTemplate;
private volatile Map<String, String> queryCache = new HashMap<String, String>();
private WhiteListDeserializingConverter deserializer;
private volatile MessageGroupFactory messageGroupFactory = new SimpleMessageGroupFactory();
private SerializingConverter serializer;
private LobHandler lobHandler = new DefaultLobHandler();
private MessageRowMapper messageRowMapper;
private ChannelMessageStorePreparedStatementSetter preparedStatementSetter;
private Map<String, String> queryCache = new HashMap<>();
private MessageGroupFactory messageGroupFactory = new SimpleMessageGroupFactory();
private boolean usingIdCache = false;
private boolean priorityEnabled;
@SuppressWarnings("unused")
private BeanFactory beanFactory;
/**
* Convenient constructor for configuration use.
*/
@@ -170,16 +165,13 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Create a {@link MessageStore} with all mandatory properties. The passed-in
* {@link DataSource} is used to instantiate a {@link JdbcTemplate}
*
* with {@link JdbcTemplate#setFetchSize(int)} set to <code>1</code>
* and with {@link JdbcTemplate#setMaxRows(int)} set to <code>1</code>.
*
* @param dataSource a {@link DataSource}
*/
public JdbcChannelMessageStore(DataSource dataSource) {
this();
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.jdbcTemplate.setFetchSize(1);
this.jdbcTemplate.setMaxRows(1);
}
@@ -189,22 +181,19 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* The passed-in {@link DataSource} is used to instantiate a {@link JdbcTemplate}
* with {@link JdbcTemplate#setFetchSize(int)} set to <code>1</code>
* and with {@link JdbcTemplate#setMaxRows(int)} set to <code>1</code>.
*
* @param dataSource a {@link DataSource}
*/
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.jdbcTemplate.setFetchSize(1);
this.jdbcTemplate.setMaxRows(1);
}
/**
* A converter for deserializing byte arrays to messages.
*
* @param deserializer the deserializer to set
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setDeserializer(Deserializer<? extends Message<?>> deserializer) {
this.deserializer = new WhiteListDeserializingConverter((Deserializer) deserializer);
}
@@ -223,11 +212,9 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* The {@link JdbcOperations} to use when interacting with the database. Either
* this property can be set or the {@link #setDataSource(DataSource) dataSource}.
*
* Please consider passing in a {@link JdbcTemplate} with a fetchSize property
* of 1. This is particularly important for Oracle to ensure First In, First Out (FIFO)
* message retrieval characteristics.
*
* @param jdbcTemplate a {@link JdbcOperations}
*/
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
@@ -238,7 +225,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Override the {@link LobHandler} that is used to create and unpack large objects in SQL queries. The default is
* fine for almost all platforms, but some Oracle drivers require a native implementation.
*
* @param lobHandler a {@link LobHandler}
*/
public void setLobHandler(LobHandler lobHandler) {
@@ -250,7 +236,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* Allows for passing in a custom {@link MessageRowMapper}. The {@link MessageRowMapper}
* is used to convert the selected database row representing the persisted
* message into the actual {@link Message} object.
*
* @param messageRowMapper Must not be null
*/
public void setMessageRowMapper(MessageRowMapper messageRowMapper) {
@@ -259,17 +244,15 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
}
/**
* Allows for passing in a custom {@link MessageGroupPreparedStatementSetter}.
* The {@link MessageGroupPreparedStatementSetter} is used to insert message into the database.
*
* @param messageGroupPreparedStatementSetter Must not be null
* Set a {@link ChannelMessageStorePreparedStatementSetter} to insert message into the database.
* @param preparedStatementSetter {@link ChannelMessageStorePreparedStatementSetter} to use.
* Must not be null
* @since 5.0
*/
public void setMessageGroupPreparedStatementSetter(
MessageGroupPreparedStatementSetter messageGroupPreparedStatementSetter) {
Assert.notNull(messageGroupPreparedStatementSetter,
"The provided MessageGroupPreparedStatementSetter must not be null.");
this.messageGroupPreparedStatementSetter = messageGroupPreparedStatementSetter;
public void setPreparedStatementSetter(ChannelMessageStorePreparedStatementSetter preparedStatementSetter) {
Assert.notNull(preparedStatementSetter,
"The provided ChannelMessageStorePreparedStatementSetter must not be null.");
this.preparedStatementSetter = preparedStatementSetter;
}
/**
@@ -291,7 +274,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* A unique grouping identifier for all messages persisted with this store.
* Using multiple regions allows the store to be partitioned (if necessary)
* for different purposes. Defaults to <code>{@link #DEFAULT_REGION}</code>.
*
* @param region the region name to set
*/
public void setRegion(String region) {
@@ -300,7 +282,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* A converter for serializing messages to byte arrays for storage.
*
* @param serializer The serializer to set
*/
@SuppressWarnings("unchecked")
@@ -312,7 +293,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Public setter for the table prefix property. This will be prefixed to all the table names before queries are
* executed. Defaults to {@link #DEFAULT_TABLE_PREFIX}.
*
* @param tablePrefix the tablePrefix to set
*/
public void setTablePrefix(String tablePrefix) {
@@ -323,23 +303,18 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* <p>Consider using this property when polling the database transactionally
* using multiple parallel threads, meaning when the configured poller is configured
* using a task executor.</p>
*
* <p>The issue is that the {@link #pollMessageFromGroup(Object)} looks for the
* oldest entry for a giving channel (groupKey) and region ({@link #setRegion(String)}).
* If you do that with multiple threads and you are using transactions, other
* threads may be waiting for that same locked row.</p>
*
* <p>If using the provided {@link OracleChannelMessageStoreQueryProvider}, don't set {@link #usingIdCache}
* to true, as the Oracle query will ignore locked rows.</p>
*
* <p>Using the id cache, the {@link JdbcChannelMessageStore} will store each
* message id in an in-memory collection for the duration of processing. With
* that, any polling threads will explicitly exclude those messages from
* being polled.</p>
*
* <p>For this to work, you must setup the corresponding
* {@link TransactionSynchronizationFactory}:</p>
*
* <pre class="code">
* {@code
* <int:transaction-synchronization-factory id="syncFactory">
@@ -348,10 +323,8 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* </int:transaction-synchronization-factory>
* }
* </pre>
*
* This {@link TransactionSynchronizationFactory} is then referenced in the
* transaction configuration of the poller:
*
* <pre class="code">
* {@code
* <int:poller fixed-delay="300" receive-timeout="500"
@@ -361,7 +334,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* </int:poller>
* }
* </pre>
*
* @param usingIdCache When <code>true</code> the id cache will be used.
*/
public void setUsingIdCache(boolean usingIdCache) {
@@ -393,25 +365,18 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
return this.messageGroupFactory;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* Check mandatory properties ({@link DataSource} and
* {@link #setChannelMessageStoreQueryProvider(ChannelMessageStoreQueryProvider)}). If no {@link MessageRowMapper}
* and {@link MessageGroupPreparedStatementSetter} was explicitly set using
* and {@link ChannelMessageStorePreparedStatementSetter} was explicitly set using
* {@link #setMessageRowMapper(MessageRowMapper)} and
* {@link #setMessageGroupPreparedStatementSetter(MessageGroupPreparedStatementSetter)} respectively, the default
* {@link MessageRowMapper} and {@link MessageGroupPreparedStatementSetter} will be instantiate using the
* {@link #setPreparedStatementSetter(ChannelMessageStorePreparedStatementSetter)} respectively, the default
* {@link MessageRowMapper} and {@link ChannelMessageStorePreparedStatementSetter} will be instantiate using the
* specified {@link #deserializer} and {@link #lobHandler}.
*
* Also, if the jdbcTemplate's fetchSize property ({@link JdbcTemplate#getFetchSize()})
* is not 1, a warning will be logged. When using the {@link JdbcChannelMessageStore}
* with Oracle, the fetchSize value of 1 is needed to ensure FIFO characteristics
* of polled messages. Please see the Oracle {@link ChannelMessageStoreQueryProvider} for more details.
*
* @throws Exception Any Exception.
*/
@Override
@@ -427,8 +392,8 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
logger.warn("The jdbcTemplate's fetch size is not 1. This may cause FIFO issues with Oracle databases.");
}
if (this.messageGroupPreparedStatementSetter == null) {
this.messageGroupPreparedStatementSetter = new MessageGroupPreparedStatementSetter(this.serializer,
if (this.preparedStatementSetter == null) {
this.preparedStatementSetter = new ChannelMessageStorePreparedStatementSetter(this.serializer,
this.lobHandler);
}
this.jdbcTemplate.afterPropertiesSet();
@@ -437,9 +402,7 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Store a message in the database. The groupId identifies the channel for which
* the message is to be stored.
*
* Keep in mind that the actual groupId (Channel Identifier) is converted to a String-based UUID identifier.
*
* @param groupId the group id to store the message under
* @param message a message
*/
@@ -447,7 +410,7 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
public MessageGroup addMessageToGroup(Object groupId, final Message<?> message) {
try {
this.jdbcTemplate.update(getQuery(this.channelMessageStoreQueryProvider.getCreateMessageQuery()),
ps -> this.messageGroupPreparedStatementSetter.setValues(ps, message, groupId, this.region,
ps -> this.preparedStatementSetter.setValues(ps, message, groupId, this.region,
this.priorityEnabled));
}
catch (DuplicateKeyException e) {
@@ -462,7 +425,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Helper method that converts the channel id to a UUID using
* {@link UUIDConverter#getUUID(Object)}.
*
* @param input Parameter may be null
* @return Returns null when the input is null otherwise the UUID as String.
*/
@@ -480,13 +442,13 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Method not implemented.
*
* @return The message group count.
* @throws UnsupportedOperationException Method not supported.
*/
@ManagedAttribute
public int getMessageGroupCount() {
return this.jdbcTemplate.queryForObject(this.getQuery("SELECT COUNT(DISTINCT GROUP_KEY) from %PREFIX%CHANNEL_MESSAGE where REGION = ?"),
return this.jdbcTemplate.queryForObject(
getQuery("SELECT COUNT(DISTINCT GROUP_KEY) from %PREFIX%CHANNEL_MESSAGE where REGION = ?"),
Integer.class, this.region);
}
@@ -494,7 +456,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* Replace patterns in the input to produce a valid SQL query. This implementation lazily initializes a
* simple map-based cache, only replacing the table prefix on the first access to a named query. Further
* accesses will be resolved from the cache.
*
* @param sqlQuery The SQL query to be transformed.
* @return A transformed query with replacements.
*/
@@ -512,7 +473,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Returns the number of messages persisted for the specified channel id (groupId)
* and the specified region ({@link #setRegion(String)}).
*
* @return The message group size.
*/
@Override
@@ -552,7 +512,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* This method executes a call to the DB to get the oldest Message in the
* MessageGroup which in the context of the {@link JdbcChannelMessageStore}
* means the channel identifier.
*
* @param groupIdKey String representation of message group (Channel) ID
* @return a message; could be null if query produced no Messages
*/
@@ -624,7 +583,8 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
final UUID id = messageToRemove.getHeaders().getId();
int updated = this.jdbcTemplate.update(getQuery(this.channelMessageStoreQueryProvider.getDeleteMessageQuery()),
new Object[]{getKey(id), getKey(groupId), this.region}, new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR});
new Object[] { getKey(id), getKey(groupId), this.region },
new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR });
boolean result = updated != 0;
if (result) {
@@ -644,7 +604,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
* rolled back.</p>
* <p>Only applicable if {@link #setUsingIdCache(boolean)} is set to
* <code>true</code></p>.
*
* @param messageId The message identifier.
*/
public void removeFromIdCache(String messageId) {
@@ -663,7 +622,6 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto
/**
* Returns the size of the Message Id Cache, which caches Message Ids for
* those messages that are currently being processed.
*
* @return The size of the Message Id Cache
*/
@ManagedMetric

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jdbc.store.channel;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.util.UUIDConverter;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* Callback to be used with the {@link org.springframework.integration.jdbc.store.JdbcChannelMessageStore}.
* <p>
* Behavior is same as standard {@link org.springframework.jdbc.core.PreparedStatementSetter},
* it takes in additional {@code Message<?> requestMessage}, {@code Object groupId},
* {@code String region} and {@code boolean priorityEnabled} parameters used
* for {@code addMessageToGroup} method
* in the {@link org.springframework.integration.jdbc.store.JdbcChannelMessageStore}.
* <p>
* This class can be extended for any custom data structure or columns types.
* For this purpose the {@code protected} constructor is provided for inheritors.
* In this case the {@link #serializer} and {@link #lobHandler} are null to avoid
* extra serialization actions if the target custom behavior doesn't imply them.
*
* @author Meherzad Lahewala
* @author Artem Bilan
*
* @since 5.0
*
* @see org.springframework.jdbc.core.PreparedStatementSetter
*/
public class ChannelMessageStorePreparedStatementSetter {
private final SerializingConverter serializer;
private final LobHandler lobHandler;
/**
* Instantiate a {@link ChannelMessageStorePreparedStatementSetter} with the provided
* serializer and lobHandler, which both must not be null.
* @param serializer the {@link SerializingConverter} to build {@code byte[]} from
* the request message
* @param lobHandler the {@link LobHandler} to store {@code byte[]} of the request
* message to prepared statement
*/
public ChannelMessageStorePreparedStatementSetter(SerializingConverter serializer, LobHandler lobHandler) {
Assert.notNull(serializer, "'serializer' must not be null");
Assert.notNull(lobHandler, "'lobHandler' must not be null");
this.serializer = serializer;
this.lobHandler = lobHandler;
}
/**
* The default constructor for inheritors who are not interested in the message
* serialization to {@code byte[]}.
* The {@link #serializer} and {@link #lobHandler} are null from this constructor,
* therefore any serialization isn't happened in the default {@link #setValues} implementation.
* A target implementor must ensure the proper custom logic for storing message.
*/
protected ChannelMessageStorePreparedStatementSetter() {
this.serializer = null;
this.lobHandler = null;
}
/**
* Perform a preparedStatement parameters population according provided arguments.
* The default functionality is (parameter - data):
* <p>
* <ul>
* <li>1 - messageId
* <li>2 - groupKey
* <li>3 - region
* <li>4 - createdDate
* <li>5 - priority if enabled, otherwise null
* <li>6 - serialized message if {@link #serializer} and {@link #lobHandler} are provided.
* </ul>
* <p>
* An inheritor may consider to call this method for population common properties and perform
* custom message serialization logic for the parameter #6.
* Any custom data structure population can be achieved with full overriding of this method.
* @param preparedStatement the {@link PreparedStatement} to populate columns based on the provided arguments
* @param requestMessage the {@link Message} to store
* @param groupId the group id for the message to store
* @param region the region in the target table to distinguish different data base clients
* @param priorityEnabled the flag to indicate if priority has to be stored
* @throws SQLException the exception throws during data population
*/
public void setValues(PreparedStatement preparedStatement, Message<?> requestMessage, Object groupId, String region,
boolean priorityEnabled) throws SQLException {
String groupKey = Objects.toString(UUIDConverter.getUUID(groupId), null);
long createdDate = System.currentTimeMillis();
String messageId = Objects.toString(UUIDConverter.getUUID(requestMessage.getHeaders().getId()), null);
preparedStatement.setString(1, messageId);
preparedStatement.setString(2, groupKey);
preparedStatement.setString(3, region);
preparedStatement.setLong(4, createdDate);
Integer priority = requestMessage.getHeaders().get(IntegrationMessageHeaderAccessor.PRIORITY, Integer.class);
if (priorityEnabled && priority != null) {
preparedStatement.setInt(5, priority);
}
else {
preparedStatement.setNull(5, Types.NUMERIC);
}
if (this.serializer != null) {
byte[] messageBytes = this.serializer.convert(requestMessage);
this.lobHandler.getLobCreator().setBlobAsBytes(preparedStatement, 6, messageBytes);
}
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright 2002-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jdbc.store.channel;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.util.UUIDConverter;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.messaging.Message;
/**
* Callback to be used with {@link org.springframework.integration.jdbc.store.JdbcChannelMessageStore}.
* <p>
* Behavior is same as standard {@link org.springframework.jdbc.core.PreparedStatementSetter}, it takes in
* extra {@code Message<?> requestMessage}, {@code Object groupId},
* {@code String region} and {@code boolean priorityEnabled} as parameters used
* for {@code addMessageToGroup} method in {@link org.springframework.integration.jdbc.store.JdbcChannelMessageStore}.
*
* @author Meherzad Lahewala
* @since 5.0
* @see org.springframework.jdbc.core.PreparedStatementSetter
*/
public class MessageGroupPreparedStatementSetter {
private final SerializingConverter serializer;
private final LobHandler lobHandler;
public MessageGroupPreparedStatementSetter(SerializingConverter serializer, LobHandler lobHandler) {
this.serializer = serializer;
this.lobHandler = lobHandler;
}
protected MessageGroupPreparedStatementSetter() {
this.serializer = null;
this.lobHandler = null;
}
public void setValues(PreparedStatement preparedStatement, Message<?> requestMessage, Object groupId, String region,
boolean priorityEnabled) throws SQLException {
String groupKey = getKey(groupId);
long createdDate = System.currentTimeMillis();
String messageId = getKey(requestMessage.getHeaders().getId());
byte[] messageBytes = null;
if (this.serializer != null) {
messageBytes = this.serializer.convert(requestMessage);
}
preparedStatement.setString(1, messageId);
preparedStatement.setString(2, groupKey);
preparedStatement.setString(3, region);
preparedStatement.setLong(4, createdDate);
Integer priority = requestMessage.getHeaders().get(IntegrationMessageHeaderAccessor.PRIORITY, Integer.class);
if (priorityEnabled && priority != null) {
preparedStatement.setInt(5, priority);
}
else {
preparedStatement.setNull(5, Types.NUMERIC);
}
if (this.lobHandler != null) {
this.lobHandler.getLobCreator().setBlobAsBytes(preparedStatement, 6, messageBytes);
}
}
protected String getKey(Object input) {
return input == null ? null : UUIDConverter.getUUID(input).toString();
}
}

View File

@@ -37,7 +37,7 @@ import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.messaging.Message;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
@@ -49,10 +49,11 @@ import org.springframework.transaction.support.TransactionTemplate;
* @author Gunnar Hillert
* @author Gary Russell
* @author Meherzad Lahewala
* @author Artem Bilan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext // close at the end after class
@RunWith(SpringRunner.class)
@DirtiesContext
public abstract class AbstractJdbcChannelMessageStoreTests {
protected static final String TEST_MESSAGE_GROUP = "AbstractJdbcChannelMessageStoreTests";
@@ -97,6 +98,7 @@ public abstract class AbstractJdbcChannelMessageStoreTests {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
messageStore.addMessageToGroup(TEST_MESSAGE_GROUP, message);
@@ -111,7 +113,7 @@ public abstract class AbstractJdbcChannelMessageStoreTests {
@Test
public void testAddAndGetCustomStatementSetter() {
messageStore.setMessageGroupPreparedStatementSetter(getMessageGroupPreparedStatementSetter());
messageStore.setPreparedStatementSetter(getMessageGroupPreparedStatementSetter());
final Message<String> message = MessageBuilder.withPayload("Cartman and Kenny").build();
final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
@@ -120,6 +122,7 @@ public abstract class AbstractJdbcChannelMessageStoreTests {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
messageStore.addMessageToGroup(TEST_MESSAGE_GROUP, message);
@@ -130,19 +133,22 @@ public abstract class AbstractJdbcChannelMessageStoreTests {
assertEquals(message.getHeaders().getId(), messageFromDb.getHeaders().getId());
}
private MessageGroupPreparedStatementSetter getMessageGroupPreparedStatementSetter() {
return new MessageGroupPreparedStatementSetter() {
private ChannelMessageStorePreparedStatementSetter getMessageGroupPreparedStatementSetter() {
return new ChannelMessageStorePreparedStatementSetter() {
private SerializingConverter serializer = new SerializingConverter();
private LobHandler lobHandler = new DefaultLobHandler();
@Override
public void setValues(PreparedStatement preparedStatement, Message<?> requestMessage, Object groupId,
String region, boolean priorityEnabled) throws SQLException {
super.setValues(preparedStatement, requestMessage, groupId, region, priorityEnabled);
byte[] messageBytes = serializer.convert(requestMessage);
lobHandler.getLobCreator().setBlobAsBytes(preparedStatement, 6, messageBytes);
byte[] messageBytes = this.serializer.convert(requestMessage);
this.lobHandler.getLobCreator().setBlobAsBytes(preparedStatement, 6, messageBytes);
}
};
}
}

View File

@@ -365,35 +365,29 @@ If your database is not listed, you can easily extend the `AbstractChannelMessag
Since _version 4.0_, the `MESSAGE_SEQUENCE` column has been added to the table to ensure first-in-first-out (FIFO) queueing even when messages are stored in the same millisecond.
Since_version 5.0, by overloading `MessageGroupPreparedStatementSetter` class you can provide custom
implementation to `PreparedStatementSetter` to support different columns or table structure
or serialization strategy.
For example, instead of default serialization to byte[], we can store its structure in JSON string.
Since _version 5.0_, by overloading `ChannelMessageStorePreparedStatementSetter` class you can provide custom implementation for message insertion in the `JdbcChannelMessageStore`.
It might be different columns or table structure or serialization strategy.
For example, instead of default serialization to `byte[]`, we can store its structure in JSON string.
Below example uses the default implementation of `setValues` to store common columns and overrides the behavior
just to store the message payload as varchar.
Below example uses the default implementation of `setValues` to store common columns and overrides the behavior just to store the message payload as varchar.
[source,java]
----
public class JsonPreparedStatementSetter extends ChannelMessageStorePreparedStatementSetter {
public class CustomMessageGroupPreparedStatementSetter
extends MessageGroupPreparedStatementSetter {
public JsonPreparedStatementSetter() {
super();
}
public CustomMessageGroupPreparedStatementSetter() {
super();
}
@Override
public void setValues(PreparedStatement preparedStatement, Message<?> requestMessage,
Object groupId, String region, boolean priorityEnabled) throws SQLException {
// Populate common columns
super.setValues(preparedStatement, requestMessage, groupId, region, priorityEnabled);
// Store message payload as varchar
preparedStatement.setString(6, requestMessage.getPayload().toString());
}
@Override
public void setValues(PreparedStatement preparedStatement, Message<?> requestMessage,
Object groupId, String region, boolean priorityEnabled) throws SQLException {
// Populate common columns
super.setValues(preparedStatement, requestMessage, groupId, region, priorityEnabled);
// Store message payload as varchar
preparedStatement.setString(6, requestMessage.getPayload().toString());
}
}
----
[IMPORTANT]

View File

@@ -294,7 +294,6 @@ See <<gemfire>> for more information.
==== Jdbc Message Channel Store Changes
The `JdbcMessageChannelStore` now provides setter to set `MessageGroupPreparedStatementSetter`,
allowing users to easily customize `PreparedStatementSetter` in the store.
The `JdbcMessageChannelStore` now provides setter for the `ChannelMessageStorePreparedStatementSetter` allowing users to customize a message insertion in the store.
See <<jdbc-message-store-channels>> for more information.