Skip to content

Commit 21589a6

Browse files
committed
Eagerly check for custom converters when reading rows.
We now eagerly check for the presence of custom converters, before attempting to materialize a nested entity when reading a Row. Closes #585
1 parent 69584df commit 21589a6

File tree

2 files changed

+109
-11
lines changed

2 files changed

+109
-11
lines changed

src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,23 @@ private Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersi
162162

163163
try {
164164

165+
Object value = null;
166+
if (metadata == null || metadata.getColumnNames().contains(identifier)) {
167+
value = row.get(identifier);
168+
}
169+
170+
if (value != null && getConversions().hasCustomReadTarget(value.getClass(), property.getType())) {
171+
return readValue(value, property.getTypeInformation());
172+
}
173+
165174
if (property.isEntity()) {
166175
return readEntityFrom(row, metadata, property);
167176
}
168177

169-
if (metadata != null && !metadata.getColumnNames().contains(identifier)) {
178+
if (value == null) {
170179
return null;
171180
}
172181

173-
Object value = row.get(identifier);
174182
return readValue(value, property.getTypeInformation());
175183

176184
} catch (Exception o_O) {
@@ -270,7 +278,7 @@ private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullab
270278
}
271279

272280
@SuppressWarnings("unchecked")
273-
private <S> S readEntityFrom(Row row, RowMetadata metadata, PersistentProperty<?> property) {
281+
private <S> S readEntityFrom(Row row, @Nullable RowMetadata metadata, PersistentProperty<?> property) {
274282

275283
String prefix = property.getName() + "_";
276284

src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,21 @@
2424
import lombok.AllArgsConstructor;
2525

2626
import java.util.ArrayList;
27+
import java.util.Arrays;
2728
import java.util.Collections;
2829
import java.util.List;
30+
import java.util.Set;
2931

3032
import org.junit.jupiter.api.BeforeEach;
3133
import org.junit.jupiter.api.Test;
3234

35+
import org.springframework.core.convert.TypeDescriptor;
36+
import org.springframework.core.convert.converter.ConditionalConverter;
37+
import org.springframework.core.convert.converter.GenericConverter;
3338
import org.springframework.data.annotation.Id;
3439
import org.springframework.data.convert.CustomConversions;
40+
import org.springframework.data.convert.ReadingConverter;
41+
import org.springframework.data.convert.WritingConverter;
3542
import org.springframework.data.r2dbc.dialect.PostgresDialect;
3643
import org.springframework.data.r2dbc.mapping.OutboundRow;
3744
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
@@ -44,28 +51,29 @@
4451
*
4552
* @author Mark Paluch
4653
*/
47-
public class PostgresMappingR2dbcConverterUnitTests {
54+
class PostgresMappingR2dbcConverterUnitTests {
4855

49-
RelationalMappingContext mappingContext = new R2dbcMappingContext();
50-
MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext);
56+
private RelationalMappingContext mappingContext = new R2dbcMappingContext();
57+
private MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext);
5158

5259
@BeforeEach
53-
public void before() {
60+
void before() {
5461

5562
List<Object> converters = new ArrayList<>(PostgresDialect.INSTANCE.getConverters());
5663
converters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
5764
CustomConversions.StoreConversions storeConversions = CustomConversions.StoreConversions
5865
.of(PostgresDialect.INSTANCE.getSimpleTypeHolder(), converters);
5966

60-
R2dbcCustomConversions customConversions = new R2dbcCustomConversions(storeConversions, Collections.emptyList());
67+
R2dbcCustomConversions customConversions = new R2dbcCustomConversions(storeConversions,
68+
Arrays.asList(JsonToJsonHolderConverter.INSTANCE, JsonHolderToJsonConverter.INSTANCE));
6169

6270
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
6371

6472
converter = new MappingR2dbcConverter(mappingContext, customConversions);
6573
}
6674

6775
@Test // gh-318
68-
public void shouldPassThruJson() {
76+
void shouldPassThruJson() {
6977

7078
JsonPerson person = new JsonPerson(null, Json.of("{\"hello\":\"world\"}"));
7179

@@ -76,7 +84,7 @@ public void shouldPassThruJson() {
7684
}
7785

7886
@Test // gh-453
79-
public void shouldConvertJsonToString() {
87+
void shouldConvertJsonToString() {
8088

8189
MockRow row = MockRow.builder().identified("json_string", Object.class, Json.of("{\"hello\":\"world\"}")).build();
8290

@@ -88,7 +96,7 @@ public void shouldConvertJsonToString() {
8896
}
8997

9098
@Test // gh-453
91-
public void shouldConvertJsonToByteArray() {
99+
void shouldConvertJsonToByteArray() {
92100

93101
MockRow row = MockRow.builder().identified("json_bytes", Object.class, Json.of("{\"hello\":\"world\"}")).build();
94102

@@ -99,6 +107,32 @@ public void shouldConvertJsonToByteArray() {
99107
assertThat(result.jsonBytes).isEqualTo("{\"hello\":\"world\"}".getBytes());
100108
}
101109

110+
@Test // gh-585
111+
void shouldApplyCustomReadingConverter() {
112+
113+
MockRow row = MockRow.builder().identified("holder", Object.class, Json.of("{\"hello\":\"world\"}")).build();
114+
115+
MockRowMetadata metadata = MockRowMetadata.builder()
116+
.columnMetadata(MockColumnMetadata.builder().name("holder").build()).build();
117+
118+
WithJsonHolder result = converter.read(WithJsonHolder.class, row, metadata);
119+
assertThat(result.holder).isNotNull();
120+
assertThat(result.holder.json).isNotNull();
121+
}
122+
123+
@Test // gh-585
124+
void shouldApplyCustomWritingConverter() {
125+
126+
WithJsonHolder object = new WithJsonHolder(new JsonHolder(Json.of("{\"hello\":\"world\"}")));
127+
128+
OutboundRow row = new OutboundRow();
129+
converter.write(object, row);
130+
131+
Parameter parameter = row.get(SqlIdentifier.unquoted("holder"));
132+
assertThat(parameter).isNotNull();
133+
assertThat(parameter.getValue()).isInstanceOf(Json.class);
134+
}
135+
102136
@AllArgsConstructor
103137
static class JsonPerson {
104138

@@ -116,4 +150,60 @@ static class ConvertedJson {
116150

117151
byte[] jsonBytes;
118152
}
153+
154+
@AllArgsConstructor
155+
static class WithJsonHolder {
156+
157+
JsonHolder holder;
158+
}
159+
160+
@ReadingConverter
161+
enum JsonToJsonHolderConverter implements GenericConverter, ConditionalConverter {
162+
163+
INSTANCE;
164+
165+
@Override
166+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
167+
return Json.class.isAssignableFrom(sourceType.getType());
168+
}
169+
170+
@Override
171+
public Set<ConvertiblePair> getConvertibleTypes() {
172+
return Collections.singleton(new GenericConverter.ConvertiblePair(Json.class, Object.class));
173+
}
174+
175+
@Override
176+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
177+
return new JsonHolder((Json) source);
178+
}
179+
}
180+
181+
@WritingConverter
182+
enum JsonHolderToJsonConverter implements GenericConverter, ConditionalConverter {
183+
184+
INSTANCE;
185+
186+
@Override
187+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
188+
return JsonHolder.class.isAssignableFrom(sourceType.getType());
189+
}
190+
191+
@Override
192+
public Set<ConvertiblePair> getConvertibleTypes() {
193+
return Collections.singleton(new GenericConverter.ConvertiblePair(JsonHolder.class, Json.class));
194+
}
195+
196+
@Override
197+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
198+
return ((JsonHolder) source).json;
199+
}
200+
}
201+
202+
@AllArgsConstructor
203+
private static class JsonHolder {
204+
205+
private final Json json;
206+
207+
}
208+
119209
}

0 commit comments

Comments
 (0)