org.springframework.integration.jdbc.store.JdbcChannelMessageStore Maven / Gradle / Ivy
Show all versions of spring-integration-jdbc Show documentation
/*
* Copyright 2002-2021 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
*
* https://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;
import java.sql.Types;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.log.LogAccessor;
import org.springframework.core.log.LogMessage;
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.MessageRowMapper;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupFactory;
import org.springframework.integration.store.PriorityCapableChannelMessageStore;
import org.springframework.integration.store.SimpleMessageGroupFactory;
import org.springframework.integration.support.converter.AllowListDeserializingConverter;
import org.springframework.integration.util.UUIDConverter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
*
* Channel-specific implementation of
* {@link org.springframework.integration.store.MessageGroupStore} using a relational
* database via JDBC.
*
* This message store shall be used for message channels only.
*
* As such, the {@link JdbcChannelMessageStore} uses database specific SQL queries.
*
* Contrary to the {@link JdbcMessageStore}, this implementation uses a single database table,
* optimized to operate like a queue.
* The SQL scripts for creating the table are packaged
* under {@code org/springframework/integration/jdbc/schema-*.sql},
* where {@code *} denotes the target database type.
*
* @author Gunnar Hillert
* @author Artem Bilan
* @author Gary Russell
* @author Meherzad Lahewala
* @author Trung Pham
*
* @since 2.2
*/
@ManagedResource
public class JdbcChannelMessageStore implements PriorityCapableChannelMessageStore, InitializingBean {
private static final LogAccessor LOGGER = new LogAccessor(JdbcChannelMessageStore.class);
/**
* Default region property, used to partition the message store. For example,
* a separate Spring Integration application with overlapping channel names
* may use the same message store by providing a distinct region name.
*/
public static final String DEFAULT_REGION = "DEFAULT";
/**
* Default value for the table prefix property.
*/
public static final String DEFAULT_TABLE_PREFIX = "INT_";
private enum Query {
CREATE_MESSAGE,
COUNT_GROUPS,
GROUP_SIZE,
DELETE_GROUP,
POLL,
POLL_WITH_EXCLUSIONS,
PRIORITY,
PRIORITY_WITH_EXCLUSIONS,
DELETE_MESSAGE
}
private final Map queryCache = new ConcurrentHashMap<>();
private final Set idCache = new HashSet<>();
private final ReadWriteLock idCacheLock = new ReentrantReadWriteLock();
private final Lock idCacheReadLock = this.idCacheLock.readLock();
private final Lock idCacheWriteLock = this.idCacheLock.writeLock();
private ChannelMessageStoreQueryProvider channelMessageStoreQueryProvider;
private String region = DEFAULT_REGION;
private String tablePrefix = DEFAULT_TABLE_PREFIX;
private JdbcTemplate jdbcTemplate;
private AllowListDeserializingConverter deserializer;
private SerializingConverter serializer;
private LobHandler lobHandler = new DefaultLobHandler();
private MessageRowMapper messageRowMapper;
private ChannelMessageStorePreparedStatementSetter preparedStatementSetter;
private MessageGroupFactory messageGroupFactory = new SimpleMessageGroupFactory();
private boolean usingIdCache = false;
private boolean priorityEnabled;
/**
* Convenient constructor for configuration use.
*/
public JdbcChannelMessageStore() {
this.deserializer = new AllowListDeserializingConverter();
this.serializer = new SerializingConverter();
}
/**
* Create a {@link org.springframework.integration.store.MessageStore}
* with all mandatory properties. The passed-in
* {@link DataSource} is used to instantiate a {@link JdbcTemplate}
* with {@link JdbcTemplate#setFetchSize(int)} set to 1
* and with {@link JdbcTemplate#setMaxRows(int)} set to 1
.
* @param dataSource a {@link DataSource}
*/
public JdbcChannelMessageStore(DataSource dataSource) {
this();
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.jdbcTemplate.setFetchSize(1);
this.jdbcTemplate.setMaxRows(1);
}
/**
* The JDBC {@link DataSource} to use when interacting with the database.
* The passed-in {@link DataSource} is used to instantiate a {@link JdbcTemplate}
* with {@link JdbcTemplate#setFetchSize(int)} set to 1
* and with {@link JdbcTemplate#setMaxRows(int)} set to 1
.
* @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" })
public void setDeserializer(Deserializer> deserializer) {
this.deserializer = new AllowListDeserializingConverter((Deserializer) deserializer);
}
/**
* Add patterns for packages/classes that are allowed to be deserialized. A class can
* be fully qualified or a wildcard '*' is allowed at the beginning or end of the
* class name. Examples: {@code com.foo.*}, {@code *.MyClass}.
* @param patterns the patterns.
* @since 5.4
*/
public void addAllowedPatterns(String... patterns) {
this.deserializer.addAllowedPatterns(patterns);
}
/**
* The {@link org.springframework.jdbc.core.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 org.springframework.jdbc.core.JdbcOperations}
*/
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
Assert.notNull(jdbcTemplate, "The provided jdbcTemplate must not be null.");
this.jdbcTemplate = jdbcTemplate;
}
/**
* 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) {
Assert.notNull(lobHandler, "The provided LobHandler must not be null.");
this.lobHandler = lobHandler;
}
/**
* Allow 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) {
Assert.notNull(messageRowMapper, "The provided MessageRowMapper must not be null.");
this.messageRowMapper = messageRowMapper;
}
/**
* 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 setPreparedStatementSetter(ChannelMessageStorePreparedStatementSetter preparedStatementSetter) {
Assert.notNull(preparedStatementSetter,
"The provided ChannelMessageStorePreparedStatementSetter must not be null.");
this.preparedStatementSetter = preparedStatementSetter;
}
/**
* Set the database specific {@link ChannelMessageStoreQueryProvider} to use.
* The {@link JdbcChannelMessageStore} provides the SQL queries to retrieve messages from
* the database. See the JavaDocs {@link ChannelMessageStoreQueryProvider} (all known
* implementing classes) to see those implementations provided by the framework.
* You can provide your own query implementations, if you need to support additional
* databases and/or need to fine-tune the queries for your requirements.
* @param channelMessageStoreQueryProvider Must not be null.
*/
public void setChannelMessageStoreQueryProvider(ChannelMessageStoreQueryProvider channelMessageStoreQueryProvider) {
Assert.notNull(channelMessageStoreQueryProvider,
"The provided channelMessageStoreQueryProvider must not be null.");
this.channelMessageStoreQueryProvider = channelMessageStoreQueryProvider;
}
/**
* 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 {@link #DEFAULT_REGION}.
* @param region the region name to set
*/
public void setRegion(String region) {
this.region = region;
}
/**
* A converter for serializing messages to byte arrays for storage.
* @param serializer The serializer to set
*/
@SuppressWarnings("unchecked")
public void setSerializer(Serializer> serializer) {
Assert.notNull(serializer, "The provided serializer must not be null.");
this.serializer = new SerializingConverter((Serializer