Skip to content

Commit 4608214

Browse files
committed
#69 - Allow object creation with a subset of columns.
We now allow object creation when not all columns are present by leveraging R2DBC RowMetadata. A column subset is necessary for projections.
1 parent 972bc09 commit 4608214

9 files changed

+70
-17
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ public EntityRowMapper(Class<T> typeRoRead, R2dbcConverter converter) {
4343
*/
4444
@Override
4545
public T apply(Row row, RowMetadata metadata) {
46-
return converter.read(typeRoRead, row);
46+
return converter.read(typeRoRead, row, metadata);
4747
}
4848
}

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

+39-9
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,21 @@ public MappingR2dbcConverter(
8484
// Entity reading
8585
// ----------------------------------
8686

87+
/*
88+
* (non-Javadoc)
89+
* @see org.springframework.data.convert.EntityReader#read(java.lang.Class, S)
90+
*/
8791
@Override
8892
public <R> R read(Class<R> type, Row row) {
93+
return read(type, row, null);
94+
}
95+
96+
/*
97+
* (non-Javadoc)
98+
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#read(java.lang.Class, io.r2dbc.spi.Row, io.r2dbc.spi.RowMetadata)
99+
*/
100+
@Override
101+
public <R> R read(Class<R> type, Row row, @Nullable RowMetadata metadata) {
89102

90103
TypeInformation<? extends R> typeInfo = ClassTypeInformation.from(type);
91104
Class<? extends R> rawType = typeInfo.getType();
@@ -99,10 +112,10 @@ public <R> R read(Class<R> type, Row row) {
99112
return getConversionService().convert(row, rawType);
100113
}
101114

102-
return read(getRequiredPersistentEntity(type), row);
115+
return read(getRequiredPersistentEntity(type), row, metadata);
103116
}
104117

105-
private <R> R read(RelationalPersistentEntity<R> entity, Row row) {
118+
private <R> R read(RelationalPersistentEntity<R> entity, Row row, @Nullable RowMetadata metadata) {
106119

107120
R result = createInstance(row, "", entity);
108121

@@ -115,7 +128,7 @@ private <R> R read(RelationalPersistentEntity<R> entity, Row row) {
115128
continue;
116129
}
117130

118-
Object value = readFrom(row, property, "");
131+
Object value = readFrom(row, metadata, property, "");
119132

120133
if (value != null) {
121134
propertyAccessor.setProperty(property, value);
@@ -129,20 +142,27 @@ private <R> R read(RelationalPersistentEntity<R> entity, Row row) {
129142
* Read a single value or a complete Entity from the {@link Row} passed as an argument.
130143
*
131144
* @param row the {@link Row} to extract the value from. Must not be {@literal null}.
145+
* @param metadata the {@link RowMetadata}. Can be {@literal null}.
132146
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
133147
* {@literal null}.
134148
* @param prefix to be used for all column names accessed by this method. Must not be {@literal null}.
135149
* @return the value read from the {@link Row}. May be {@literal null}.
136150
*/
137-
private Object readFrom(Row row, RelationalPersistentProperty property, String prefix) {
151+
private Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersistentProperty property,
152+
String prefix) {
138153

139154
try {
140155

141156
if (property.isEntity()) {
142-
return readEntityFrom(row, property);
157+
return readEntityFrom(row, metadata, property);
143158
}
144159

145-
Object value = row.get(prefix + property.getColumnName());
160+
String identifier = prefix + property.getColumnName();
161+
if (metadata != null && !metadata.getColumnNames().contains(identifier)) {
162+
return null;
163+
}
164+
165+
Object value = row.get(identifier);
146166
return getPotentiallyConvertedSimpleRead(value, property.getTypeInformation().getType());
147167

148168
} catch (Exception o_O) {
@@ -178,13 +198,13 @@ private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullab
178198
}
179199

180200
@SuppressWarnings("unchecked")
181-
private <S> S readEntityFrom(Row row, PersistentProperty<?> property) {
201+
private <S> S readEntityFrom(Row row, RowMetadata metadata, PersistentProperty<?> property) {
182202

183203
String prefix = property.getName() + "_";
184204

185205
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
186206

187-
if (readFrom(row, entity.getRequiredIdProperty(), prefix) == null) {
207+
if (readFrom(row, metadata, entity.getRequiredIdProperty(), prefix) == null) {
188208
return null;
189209
}
190210

@@ -195,7 +215,7 @@ private <S> S readEntityFrom(Row row, PersistentProperty<?> property) {
195215

196216
for (RelationalPersistentProperty p : entity) {
197217
if (!entity.isConstructorArgument(property)) {
198-
propertyAccessor.setProperty(p, readFrom(row, p, prefix));
218+
propertyAccessor.setProperty(p, readFrom(row, metadata, p, prefix));
199219
}
200220
}
201221

@@ -213,6 +233,10 @@ private <S> S createInstance(Row row, String prefix, RelationalPersistentEntity<
213233
// Entity writing
214234
// ----------------------------------
215235

236+
/*
237+
* (non-Javadoc)
238+
* @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object)
239+
*/
216240
@Override
217241
public void write(Object source, OutboundRow sink) {
218242

@@ -313,6 +337,11 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
313337
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
314338
}
315339

340+
/*
341+
* (non-Javadoc)
342+
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#getArrayValue(org.springframework.data.r2dbc.dialect.ArrayColumns, org.springframework.data.relational.core.mapping.RelationalPersistentProperty, java.lang.Object)
343+
*/
344+
@Override
316345
public Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentProperty property, Object value) {
317346

318347
Class<?> targetType = arrayColumns.getArrayType(property.getActualType());
@@ -337,6 +366,7 @@ public Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentPrope
337366
* @param object must not be {@literal null}.
338367
* @return
339368
*/
369+
@Override
340370
@SuppressWarnings("unchecked")
341371
public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
342372

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

+10
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,14 @@ public interface R2dbcConverter
7171
* @return
7272
*/
7373
<T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object);
74+
75+
/**
76+
* Reads the given source into the given type.
77+
*
78+
* @param type they type to convert the given source to.
79+
* @param source the source to create an object of the given type from.
80+
* @param metadata the {@link RowMetadata}.
81+
* @return
82+
*/
83+
<R> R read(Class<R> type, Row source, RowMetadata metadata);
7484
}

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
import io.r2dbc.spi.RowMetadata;
88
import lombok.RequiredArgsConstructor;
99

10+
import java.util.Collection;
1011
import java.util.List;
1112
import java.util.Set;
1213

14+
import org.junit.Before;
1315
import org.junit.Test;
1416
import org.junit.runner.RunWith;
1517
import org.mockito.junit.MockitoJUnitRunner;
16-
import org.springframework.data.r2dbc.convert.EntityRowMapper;
18+
1719
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
1820
import org.springframework.data.r2dbc.dialect.PostgresDialect;
1921

@@ -30,6 +32,14 @@ public class EntityRowMapperUnitTests {
3032

3133
Row rowMock = mock(Row.class);
3234
RowMetadata metadata = mock(RowMetadata.class);
35+
Collection<String> columns = mock(Collection.class);
36+
37+
@Before
38+
public void before() {
39+
40+
when(columns.contains(anyString())).thenReturn(true);
41+
when(metadata.getColumnNames()).thenReturn(columns);
42+
}
3343

3444
@Test // gh-22
3545
public void shouldMapSimpleEntity() {

src/test/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategyTestSupport.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929
import java.time.LocalTime;
3030
import java.time.OffsetDateTime;
3131
import java.time.ZonedDateTime;
32+
import java.util.Collection;
3233
import java.util.UUID;
3334
import java.util.function.BiConsumer;
3435
import java.util.function.Function;
3536

3637
import org.junit.Test;
37-
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
38-
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
38+
3939
import org.springframework.data.r2dbc.dialect.Dialect;
4040
import org.springframework.data.r2dbc.mapping.SettableValue;
4141

@@ -177,6 +177,9 @@ private <T> void testType(BiConsumer<PrimitiveTypes, T> setter, Function<Primiti
177177
ReactiveDataAccessStrategy strategy = getStrategy();
178178
Row rowMock = mock(Row.class);
179179
RowMetadata metadataMock = mock(RowMetadata.class);
180+
Collection<String> columnNames = mock(Collection.class);
181+
when(metadataMock.getColumnNames()).thenReturn(columnNames);
182+
when(columnNames.contains(fieldname)).thenReturn(true);
180183

181184
PrimitiveTypes toSave = new PrimitiveTypes();
182185
setter.accept(toSave, testValue);

src/test/java/org/springframework/data/r2dbc/repository/H2R2dbcRepositoryIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ interface H2LegoSetRepository extends LegoSetRepository {
8181
Flux<LegoSet> findByNameContains(String name);
8282

8383
@Override
84-
@Query("SELECT * FROM legoset")
84+
@Query("SELECT name FROM legoset")
8585
Flux<Named> findAsProjection();
8686

8787
@Override

src/test/java/org/springframework/data/r2dbc/repository/MySqlR2dbcRepositoryIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ interface MySqlLegoSetRepository extends LegoSetRepository {
8585
Flux<LegoSet> findByNameContains(String name);
8686

8787
@Override
88-
@Query("SELECT * FROM legoset")
88+
@Query("SELECT name FROM legoset")
8989
Flux<Named> findAsProjection();
9090

9191
@Override

src/test/java/org/springframework/data/r2dbc/repository/PostgresR2dbcRepositoryIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ interface PostgresLegoSetRepository extends LegoSetRepository {
8585
Flux<LegoSet> findByNameContains(String name);
8686

8787
@Override
88-
@Query("SELECT * FROM legoset")
88+
@Query("SELECT name FROM legoset")
8989
Flux<Named> findAsProjection();
9090

9191
@Override

src/test/java/org/springframework/data/r2dbc/repository/SqlServerR2dbcRepositoryIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ interface SqlServerLegoSetRepository extends LegoSetRepository {
9090
Flux<LegoSet> findByNameContains(String name);
9191

9292
@Override
93-
@Query("SELECT * FROM legoset")
93+
@Query("SELECT name FROM legoset")
9494
Flux<Named> findAsProjection();
9595

9696
@Override

0 commit comments

Comments
 (0)