Skip to content

Commit

Permalink
spring-projectsGH-9192: Deprecate LobHandler usage
Browse files Browse the repository at this point in the history
Fixes: spring-projects#9192

With modern drivers we don't need BLOB-specific handling anymore.
The regular `PreparedStatement.setBytes()` and `ResultSet.getBytes()`
are enough for our serialized messages
  • Loading branch information
artembilan authored and EddieChoCho committed Jun 26, 2024
1 parent bb2c947 commit a9f0df6
Show file tree
Hide file tree
Showing 10 changed files with 610 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ private enum Query {

private SerializingConverter serializer;

private LobHandler lobHandler = new DefaultLobHandler();

private MessageRowMapper messageRowMapper;

private ChannelMessageStorePreparedStatementSetter preparedStatementSetter;
Expand Down Expand Up @@ -231,10 +229,10 @@ public void setJdbcTemplate(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}
* @deprecated since 6.4 (for removal) (with no replacement) in favor of plain JDBC driver support for byte arrays.
*/
@Deprecated(forRemoval = true, since = "6.4")
public void setLobHandler(LobHandler lobHandler) {
Assert.notNull(lobHandler, "The provided LobHandler must not be null.");
this.lobHandler = lobHandler;
}

/**
Expand Down Expand Up @@ -396,8 +394,8 @@ protected MessageGroupFactory getMessageGroupFactory() {
* and {@link ChannelMessageStorePreparedStatementSetter} was explicitly set using
* {@link #setMessageRowMapper(MessageRowMapper)} and
* {@link #setPreparedStatementSetter(ChannelMessageStorePreparedStatementSetter)} respectively, the default
* {@link MessageRowMapper} and {@link ChannelMessageStorePreparedStatementSetter} will be instantiate using the
* specified {@link #deserializer} and {@link #lobHandler}.
* {@link MessageRowMapper} and {@link ChannelMessageStorePreparedStatementSetter} will be instantiated using the
* specified {@link #deserializer}.
* 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
Expand All @@ -409,16 +407,15 @@ public void afterPropertiesSet() {
Assert.notNull(this.channelMessageStoreQueryProvider, "A channelMessageStoreQueryProvider must be provided.");

if (this.messageRowMapper == null) {
this.messageRowMapper = new MessageRowMapper(this.deserializer, this.lobHandler);
this.messageRowMapper = new MessageRowMapper(this.deserializer);
}

if (this.jdbcTemplate.getFetchSize() != 1) {
LOGGER.warn("The jdbcTemplate's fetch size is not 1. This may cause FIFO issues with Oracle databases.");
}

if (this.preparedStatementSetter == null) {
this.preparedStatementSetter = new ChannelMessageStorePreparedStatementSetter(this.serializer,
this.lobHandler);
this.preparedStatementSetter = new ChannelMessageStorePreparedStatementSetter(this.serializer);
}
this.jdbcTemplate.afterPropertiesSet();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -16,8 +16,6 @@

package org.springframework.integration.jdbc.store;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -38,6 +36,7 @@
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.integration.jdbc.store.channel.MessageRowMapper;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupMetadata;
Expand All @@ -49,9 +48,7 @@
import org.springframework.integration.util.UUIDConverter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -253,8 +250,6 @@ public String getSql() {
}
}

private final MessageMapper mapper = new MessageMapper();

private final JdbcOperations jdbcTemplate;

private final Map<Query, String> queryCache = new ConcurrentHashMap<>();
Expand All @@ -270,9 +265,9 @@ public String getSql() {

private boolean deserializerExplicitlySet;

private SerializingConverter serializer;
private MessageRowMapper mapper = new MessageRowMapper(this.deserializer);

private LobHandler lobHandler = new DefaultLobHandler();
private SerializingConverter serializer;

private boolean checkDatabaseOnStart = true;

Expand Down Expand Up @@ -325,9 +320,11 @@ public void setRegion(String region) {
* 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}
* @deprecated since 6.4 (for removal) (with no replacement) in favor of plain JDBC driver support for byte arrays.
*/
@Deprecated(forRemoval = true, since = "6.4")
public void setLobHandler(LobHandler lobHandler) {
this.lobHandler = lobHandler;

}

/**
Expand All @@ -347,6 +344,7 @@ public void setSerializer(Serializer<? super Message<?>> serializer) {
public void setDeserializer(Deserializer<? extends Message<?>> deserializer) {
this.deserializer = new AllowListDeserializingConverter((Deserializer) deserializer);
this.deserializerExplicitlySet = true;
this.mapper = new MessageRowMapper(this.deserializer);
}

/**
Expand Down Expand Up @@ -459,8 +457,7 @@ public <T> Message<T> addMessage(final Message<T> message) {
ps.setString(1, messageId); // NOSONAR - magic number
ps.setString(2, this.region); // NOSONAR - magic number
ps.setTimestamp(3, new Timestamp(System.currentTimeMillis())); // NOSONAR - magic number

this.lobHandler.getLobCreator().setBlobAsBytes(ps, 4, messageBytes); // NOSONAR - magic number
ps.setBytes(4, messageBytes); // NOSONAR - magic number
});
}
catch (DataIntegrityViolationException ex) {
Expand Down Expand Up @@ -780,23 +777,4 @@ private String getKey(Object input) {
return input == null ? null : UUIDConverter.getUUID(input).toString();
}

/**
* Convenience class to be used to unpack a message from a result set row. Uses column named in the result set to
* extract the required data, so that select clause ordering is unimportant.
*/
private final class MessageMapper implements RowMapper<Message<?>> {

@Override
public Message<?> mapRow(ResultSet rs, int rowNum) throws SQLException {
byte[] messageBytes = JdbcMessageStore.this.lobHandler.getBlobAsBytes(rs, "MESSAGE_BYTES");
if (messageBytes == null) {
return null;
}
else {
return (Message<?>) JdbcMessageStore.this.deserializer.convert(messageBytes);
}
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2024 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.
Expand Down Expand Up @@ -39,7 +39,7 @@
* <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
* In this case the {@link #serializer} is {@code null} to avoid
* extra serialization actions if the target custom behavior doesn't imply them.
*
* @author Meherzad Lahewala
Expand All @@ -53,33 +53,42 @@ 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
* @deprecated since 6.4 (for removal) (if favor of {@link #ChannelMessageStorePreparedStatementSetter(SerializingConverter)})
* with a plain JDBC driver support for byte arrays.
*/
@Deprecated(forRemoval = true, since = "6.4")
public ChannelMessageStorePreparedStatementSetter(SerializingConverter serializer, LobHandler lobHandler) {
this(serializer);
}

/**
* 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
* @since 6.4
*/
public ChannelMessageStorePreparedStatementSetter(SerializingConverter serializer) {
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,
* The {@link #serializer} is {@code 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;
}

/**
Expand All @@ -91,15 +100,15 @@ protected ChannelMessageStorePreparedStatementSetter() {
* <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.
* <li>6 - serialized message if {@link #serializer} is provided.
* </ul>
* 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 region the region in the target table to distinguish different database clients
* @param priorityEnabled the flag to indicate if priority has to be stored
* @throws SQLException the exception throws during data population
*/
Expand All @@ -126,7 +135,7 @@ public void setValues(PreparedStatement preparedStatement, Message<?> requestMes

if (this.serializer != null) {
byte[] messageBytes = this.serializer.convert(requestMessage);
this.lobHandler.getLobCreator().setBlobAsBytes(preparedStatement, 6, messageBytes); // NOSONAR magic number
preparedStatement.setBytes(6, messageBytes); // NOSONAR magic number
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -23,6 +23,7 @@
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;

/**
* Convenience class to be used to unpack a {@link Message} from a result set
Expand All @@ -40,22 +41,32 @@ public class MessageRowMapper implements RowMapper<Message<?>> {

private final AllowListDeserializingConverter deserializer;

private final LobHandler lobHandler;

/**
* Construct an instance based on the provided {@link AllowListDeserializingConverter}
* and {@link LobHandler}.
* @param deserializer the {@link AllowListDeserializingConverter} to use.
* @param lobHandler the {@link LobHandler} to use.
* @deprecated since 6.4 (for removal) (if favor of {@link #MessageRowMapper(AllowListDeserializingConverter)})
* with a plain JDBC driver support for byte arrays.
*/
@Deprecated(forRemoval = true, since = "6.4")
public MessageRowMapper(AllowListDeserializingConverter deserializer, LobHandler lobHandler) {
this(deserializer);
}

/**
* Construct an instance based on the provided {@link AllowListDeserializingConverter}.
* @param deserializer the {@link AllowListDeserializingConverter} to use.
* @since 6.4
*/
public MessageRowMapper(AllowListDeserializingConverter deserializer) {
Assert.notNull(deserializer, "'deserializer' must not be null");
this.deserializer = deserializer;
this.lobHandler = lobHandler;
}

@Override
public Message<?> mapRow(ResultSet rs, int rowNum) throws SQLException {
byte[] blobAsBytes = this.lobHandler.getBlobAsBytes(rs, "MESSAGE_BYTES");
byte[] blobAsBytes = rs.getBytes("MESSAGE_BYTES");
if (blobAsBytes == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.messaging.Message;
import org.springframework.test.util.ReflectionTestUtils;

Expand All @@ -52,24 +51,24 @@ public class JdbcMessageStoreParserTests {
public void testSimpleMessageStoreWithDataSource() {
setUp("defaultJdbcMessageStore.xml", getClass());
MessageStore store = context.getBean("messageStore", MessageStore.class);
assertThat(store instanceof JdbcMessageStore).isTrue();
assertThat(store).isInstanceOf(JdbcMessageStore.class);
}

@Test
public void testSimpleMessageStoreWithTemplate() {
setUp("jdbcOperationsJdbcMessageStore.xml", getClass());
MessageStore store = context.getBean("messageStore", MessageStore.class);
assertThat(store instanceof JdbcMessageStore).isTrue();
assertThat(store).isInstanceOf(JdbcMessageStore.class);
}

@Test
public void testSimpleMessageStoreWithSerializer() {
setUp("serializerJdbcMessageStore.xml", getClass());
MessageStore store = context.getBean("messageStore", MessageStore.class);
Object serializer = TestUtils.getPropertyValue(store, "serializer.serializer");
assertThat(serializer instanceof EnhancedSerializer).isTrue();
assertThat(serializer).isInstanceOf(EnhancedSerializer.class);
Object deserializer = TestUtils.getPropertyValue(store, "deserializer.deserializer");
assertThat(deserializer instanceof EnhancedSerializer).isTrue();
assertThat(deserializer).isInstanceOf(EnhancedSerializer.class);
}

@Test
Expand All @@ -78,7 +77,6 @@ public void testMessageStoreWithAttributes() {
MessageStore store = context.getBean("messageStore", MessageStore.class);
assertThat(ReflectionTestUtils.getField(store, "region")).isEqualTo("FOO");
assertThat(ReflectionTestUtils.getField(store, "tablePrefix")).isEqualTo("BAR_");
assertThat(ReflectionTestUtils.getField(store, "lobHandler")).isEqualTo(context.getBean(LobHandler.class));
}

@AfterEach
Expand All @@ -99,8 +97,7 @@ public static class EnhancedSerializer implements Serializer<Object>, Deserializ
private final Deserializer<Object> targetDeserializer = new DefaultDeserializer();

public Object deserialize(InputStream inputStream) throws IOException {
Message<?> message = (Message<?>) targetDeserializer.deserialize(inputStream);
return message;
return targetDeserializer.deserialize(inputStream);
}

public void serialize(Object object, OutputStream outputStream) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@

<bean id="messageStore" class="org.springframework.integration.jdbc.store.JdbcMessageStore">
<constructor-arg ref="dataSource"/>
<property name="lobHandler" ref="lobHandler"/>
<property name="region" value="FOO"/>
<property name="tablePrefix" value="BAR_"/>
<property name="checkDatabaseOnStart" value="false"/>
</bean>

<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"/>

</beans>
Loading

0 comments on commit a9f0df6

Please sign in to comment.