#59 - Consider custom conversion in MappingR2dbcConverter.
MappingR2dbcConverter now considers custom conversions for inbound and outbound conversion of top-level types (Row to Entity, Entity to OutboundRow) and on property level (e.g. convert an object to String and vice versa). Original pull request: #70.
This commit is contained in:
committed by
Jens Schauder
parent
39936c67fa
commit
6654db34c4
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2019 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.data.r2dbc.domain;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SettableValue}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SettableValueUnitTests {
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldCreateSettableValue() {
|
||||
|
||||
SettableValue value = SettableValue.from("foo");
|
||||
|
||||
assertThat(value.isEmpty()).isFalse();
|
||||
assertThat(value.hasValue()).isTrue();
|
||||
assertThat(value).isEqualTo(SettableValue.from("foo"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldCreateEmpty() {
|
||||
|
||||
SettableValue value = SettableValue.empty(Object.class);
|
||||
|
||||
assertThat(value.isEmpty()).isTrue();
|
||||
assertThat(value.hasValue()).isFalse();
|
||||
assertThat(value).isEqualTo(SettableValue.empty(Object.class));
|
||||
assertThat(value).isNotEqualTo(SettableValue.empty(String.class));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldCreatePotentiallyEmpty() {
|
||||
|
||||
assertThat(SettableValue.fromOrEmpty("foo", Object.class).isEmpty()).isFalse();
|
||||
assertThat(SettableValue.fromOrEmpty(null, Object.class).isEmpty()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import java.util.Map;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.function.convert.SettableValue;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultReactiveDataAccessStrategy}.
|
||||
|
||||
@@ -21,9 +21,20 @@ import static org.mockito.Mockito.*;
|
||||
import io.r2dbc.spi.Row;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.r2dbc.domain.OutboundRow;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
|
||||
/**
|
||||
@@ -33,7 +44,21 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
*/
|
||||
public class MappingR2dbcConverterUnitTests {
|
||||
|
||||
MappingR2dbcConverter converter = new MappingR2dbcConverter(new RelationalMappingContext());
|
||||
RelationalMappingContext mappingContext = new RelationalMappingContext();
|
||||
MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
R2dbcCustomConversions conversions = new R2dbcCustomConversions(
|
||||
Arrays.asList(StringToMapConverter.INSTANCE, MapToStringConverter.INSTANCE,
|
||||
CustomConversionPersonToOutboundRowConverter.INSTANCE, RowToCustomConversionPerson.INSTANCE));
|
||||
|
||||
mappingContext = new RelationalMappingContext();
|
||||
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
|
||||
|
||||
converter = new MappingR2dbcConverter(mappingContext, conversions);
|
||||
}
|
||||
|
||||
@Test // gh-61
|
||||
public void shouldIncludeAllPropertiesInOutboundRow() {
|
||||
@@ -42,9 +67,9 @@ public class MappingR2dbcConverterUnitTests {
|
||||
|
||||
converter.write(new Person("id", "Walter", "White"), row);
|
||||
|
||||
assertThat(row).containsEntry("id", new SettableValue("id", String.class));
|
||||
assertThat(row).containsEntry("firstname", new SettableValue("Walter", String.class));
|
||||
assertThat(row).containsEntry("lastname", new SettableValue("White", String.class));
|
||||
assertThat(row).containsEntry("id", SettableValue.fromOrEmpty("id", String.class));
|
||||
assertThat(row).containsEntry("firstname", SettableValue.fromOrEmpty("Walter", String.class));
|
||||
assertThat(row).containsEntry("lastname", SettableValue.fromOrEmpty("White", String.class));
|
||||
}
|
||||
|
||||
@Test // gh-41
|
||||
@@ -68,9 +93,188 @@ public class MappingR2dbcConverterUnitTests {
|
||||
assertThat(result).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldFailOnUnsupportedEntity() {
|
||||
|
||||
PersonWithConversions withMap = new PersonWithConversions(null, null, new NonMappableEntity());
|
||||
OutboundRow row = new OutboundRow();
|
||||
|
||||
assertThatThrownBy(() -> converter.write(withMap, row)).isInstanceOf(InvalidDataAccessApiUsageException.class);
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldConvertMapToString() {
|
||||
|
||||
PersonWithConversions withMap = new PersonWithConversions("foo", Collections.singletonMap("map", "value"), null);
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(withMap, row);
|
||||
|
||||
assertThat(row).containsEntry("nested", SettableValue.from("map"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldReadMapFromString() {
|
||||
|
||||
Row rowMock = mock(Row.class);
|
||||
when(rowMock.get("nested")).thenReturn("map");
|
||||
|
||||
PersonWithConversions result = converter.read(PersonWithConversions.class, rowMock);
|
||||
|
||||
assertThat(result.nested).isEqualTo(Collections.singletonMap("map", "map"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldConvertEnum() {
|
||||
|
||||
WithEnum withMap = new WithEnum("foo", Condition.Mint);
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(withMap, row);
|
||||
|
||||
assertThat(row).containsEntry("condition", SettableValue.from("Mint"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldConvertNullEnum() {
|
||||
|
||||
WithEnum withMap = new WithEnum("foo", null);
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(withMap, row);
|
||||
|
||||
assertThat(row).containsEntry("condition", SettableValue.fromOrEmpty(null, String.class));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldReadEnum() {
|
||||
|
||||
Row rowMock = mock(Row.class);
|
||||
when(rowMock.get("condition")).thenReturn("Mint");
|
||||
|
||||
WithEnum result = converter.read(WithEnum.class, rowMock);
|
||||
|
||||
assertThat(result.condition).isEqualTo(Condition.Mint);
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldWriteTopLevelEntity() {
|
||||
|
||||
CustomConversionPerson person = new CustomConversionPerson();
|
||||
person.entity = new NonMappableEntity();
|
||||
person.foo = "bar";
|
||||
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(person, row);
|
||||
|
||||
assertThat(row).containsEntry("foo_column", SettableValue.from("bar")).containsEntry("entity",
|
||||
SettableValue.from("nested_entity"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
public void shouldReadTopLevelEntity() {
|
||||
|
||||
Row rowMock = mock(Row.class);
|
||||
when(rowMock.get("foo_column", String.class)).thenReturn("bar");
|
||||
when(rowMock.get("nested_entity")).thenReturn("map");
|
||||
|
||||
CustomConversionPerson result = converter.read(CustomConversionPerson.class, rowMock);
|
||||
|
||||
assertThat(result.foo).isEqualTo("bar");
|
||||
assertThat(result.entity).isNotNull();
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class Person {
|
||||
@Id String id;
|
||||
String firstname, lastname;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class WithEnum {
|
||||
@Id String id;
|
||||
Condition condition;
|
||||
}
|
||||
|
||||
enum Condition {
|
||||
Mint, Used
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class PersonWithConversions {
|
||||
@Id String id;
|
||||
Map<String, String> nested;
|
||||
NonMappableEntity unsupported;
|
||||
}
|
||||
|
||||
static class CustomConversionPerson {
|
||||
|
||||
String foo;
|
||||
NonMappableEntity entity;
|
||||
}
|
||||
|
||||
static class NonMappableEntity {}
|
||||
|
||||
@ReadingConverter
|
||||
enum StringToMapConverter implements Converter<String, Map<String, String>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, String> convert(String source) {
|
||||
|
||||
if (source != null) {
|
||||
return Collections.singletonMap(source, source);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
enum MapToStringConverter implements Converter<Map<String, String>, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Map<String, String> source) {
|
||||
|
||||
if (!source.isEmpty()) {
|
||||
return source.keySet().iterator().next();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
enum CustomConversionPersonToOutboundRowConverter implements Converter<CustomConversionPerson, OutboundRow> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public OutboundRow convert(CustomConversionPerson source) {
|
||||
|
||||
OutboundRow row = new OutboundRow();
|
||||
row.put("foo_column", SettableValue.from(source.foo));
|
||||
row.put("entity", SettableValue.from("nested_entity"));
|
||||
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
enum RowToCustomConversionPerson implements Converter<Row, CustomConversionPerson> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public CustomConversionPerson convert(Row source) {
|
||||
|
||||
CustomConversionPerson person = new CustomConversionPerson();
|
||||
person.foo = source.get("foo_column", String.class);
|
||||
|
||||
Object nested_entity = source.get("nested_entity");
|
||||
person.entity = nested_entity != null ? new NonMappableEntity() : null;
|
||||
|
||||
return person;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user