#139 - Consider value type in Dialect-specific array type conversion.

We now inspect the value type of a SettableValue before attempting to convert a value into an array type. This check allows applying custom conversions to map objects to a simple type and bypassing the array conversion afterwards.

Previously, we just relied on the property type without checking whether the value qualifies for array type conversion.
This commit is contained in:
Mark Paluch
2019-12-06 10:36:04 +01:00
parent 96706cf7ed
commit f2a76fa74f
3 changed files with 111 additions and 3 deletions

View File

@@ -101,7 +101,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra
storeConverters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
R2dbcCustomConversions customConversions = new R2dbcCustomConversions(
StoreConversions.of(dialect.getSimpleTypeHolder(), storeConverters), storeConverters);
StoreConversions.of(dialect.getSimpleTypeHolder(), storeConverters), converters);
R2dbcMappingContext context = new R2dbcMappingContext();
context.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
@@ -215,7 +215,20 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra
}
private boolean shouldConvertArrayValue(RelationalPersistentProperty property, SettableValue value) {
return property.isCollectionLike();
if (!property.isCollectionLike()) {
return false;
}
if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) {
return true;
}
if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) {
return true;
}
return false;
}
private SettableValue getArrayValue(SettableValue value, RelationalPersistentProperty property) {

View File

@@ -56,7 +56,13 @@ public interface ReactiveDataAccessStrategy {
*/
OutboundRow getOutboundRow(Object object);
// TODO: Broaden T to Mono<T>/Flux<T> for reactive relational data access?
/**
* Returns a {@link BiFunction row mapping function} to map {@link Row rows} to {@code T}.
*
* @param typeToRead
* @param <T>
* @return
*/
<T> BiFunction<Row, RowMetadata, T> getRowMapper(Class<T> typeToRead);
/**

View File

@@ -20,12 +20,16 @@ import static org.assertj.core.api.Assertions.*;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.mapping.SettableValue;
/**
* {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}.
@@ -69,6 +73,56 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
assertThat((Integer[]) row.get("myarray").getValue()).contains(1, 2, 3);
}
@Test // gh-139
public void shouldConvertToArray() {
DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE);
WithArray withArray = new WithArray();
withArray.stringArray = new String[] { "hello", "world" };
withArray.stringList = Arrays.asList("hello", "world");
OutboundRow outboundRow = strategy.getOutboundRow(withArray);
assertThat(outboundRow) //
.containsEntry("string_array", SettableValue.from(new String[] { "hello", "world" }))
.containsEntry("string_list", SettableValue.from(new String[] { "hello", "world" }));
}
@Test // gh-139
public void shouldApplyCustomConversion() {
DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE,
Collections.singletonList(MyObjectsToStringConverter.INSTANCE));
WithConversion withConversion = new WithConversion();
withConversion.myObjects = Arrays.asList(new MyObject("one"), new MyObject("two"));
OutboundRow outboundRow = strategy.getOutboundRow(withConversion);
assertThat(outboundRow) //
.containsEntry("my_objects", SettableValue.from("[one, two]"));
}
@Test // gh-139
public void shouldApplyCustomConversionForNull() {
DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE,
Collections.singletonList(MyObjectsToStringConverter.INSTANCE));
WithConversion withConversion = new WithConversion();
withConversion.myObjects = null;
OutboundRow outboundRow = strategy.getOutboundRow(withConversion);
assertThat(outboundRow) //
.containsKey("my_objects");
SettableValue value = outboundRow.get("my_objects");
assertThat(value.isEmpty()).isTrue();
assertThat(value.getType()).isEqualTo(String.class);
}
@RequiredArgsConstructor
static class WithMultidimensionalArray {
@@ -80,4 +134,39 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
final List<Integer> myarray;
}
static class WithArray {
String[] stringArray;
List<String> stringList;
}
static class WithConversion {
List<MyObject> myObjects;
}
static class MyObject {
String foo;
public MyObject(String foo) {
this.foo = foo;
}
@Override
public String toString() {
return foo;
}
}
@WritingConverter
enum MyObjectsToStringConverter implements Converter<List<MyObject>, String> {
INSTANCE;
@Override
public String convert(List<MyObject> myObjects) {
return myObjects.toString();
}
}
}