#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.
This commit is contained in:
@@ -43,6 +43,6 @@ public class EntityRowMapper<T> implements BiFunction<Row, RowMetadata, T> {
|
||||
*/
|
||||
@Override
|
||||
public T apply(Row row, RowMetadata metadata) {
|
||||
return converter.read(typeRoRead, row);
|
||||
return converter.read(typeRoRead, row, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,21 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
// Entity reading
|
||||
// ----------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityReader#read(java.lang.Class, S)
|
||||
*/
|
||||
@Override
|
||||
public <R> R read(Class<R> type, Row row) {
|
||||
return read(type, row, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#read(java.lang.Class, io.r2dbc.spi.Row, io.r2dbc.spi.RowMetadata)
|
||||
*/
|
||||
@Override
|
||||
public <R> R read(Class<R> type, Row row, @Nullable RowMetadata metadata) {
|
||||
|
||||
TypeInformation<? extends R> typeInfo = ClassTypeInformation.from(type);
|
||||
Class<? extends R> rawType = typeInfo.getType();
|
||||
@@ -99,10 +112,10 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
return getConversionService().convert(row, rawType);
|
||||
}
|
||||
|
||||
return read(getRequiredPersistentEntity(type), row);
|
||||
return read(getRequiredPersistentEntity(type), row, metadata);
|
||||
}
|
||||
|
||||
private <R> R read(RelationalPersistentEntity<R> entity, Row row) {
|
||||
private <R> R read(RelationalPersistentEntity<R> entity, Row row, @Nullable RowMetadata metadata) {
|
||||
|
||||
R result = createInstance(row, "", entity);
|
||||
|
||||
@@ -115,7 +128,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = readFrom(row, property, "");
|
||||
Object value = readFrom(row, metadata, property, "");
|
||||
|
||||
if (value != null) {
|
||||
propertyAccessor.setProperty(property, value);
|
||||
@@ -129,20 +142,27 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
* Read a single value or a complete Entity from the {@link Row} passed as an argument.
|
||||
*
|
||||
* @param row the {@link Row} to extract the value from. Must not be {@literal null}.
|
||||
* @param metadata the {@link RowMetadata}. Can be {@literal null}.
|
||||
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
|
||||
* {@literal null}.
|
||||
* @param prefix to be used for all column names accessed by this method. Must not be {@literal null}.
|
||||
* @return the value read from the {@link Row}. May be {@literal null}.
|
||||
*/
|
||||
private Object readFrom(Row row, RelationalPersistentProperty property, String prefix) {
|
||||
private Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersistentProperty property,
|
||||
String prefix) {
|
||||
|
||||
try {
|
||||
|
||||
if (property.isEntity()) {
|
||||
return readEntityFrom(row, property);
|
||||
return readEntityFrom(row, metadata, property);
|
||||
}
|
||||
|
||||
Object value = row.get(prefix + property.getColumnName());
|
||||
String identifier = prefix + property.getColumnName();
|
||||
if (metadata != null && !metadata.getColumnNames().contains(identifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object value = row.get(identifier);
|
||||
return getPotentiallyConvertedSimpleRead(value, property.getTypeInformation().getType());
|
||||
|
||||
} catch (Exception o_O) {
|
||||
@@ -178,13 +198,13 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> S readEntityFrom(Row row, PersistentProperty<?> property) {
|
||||
private <S> S readEntityFrom(Row row, RowMetadata metadata, PersistentProperty<?> property) {
|
||||
|
||||
String prefix = property.getName() + "_";
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
|
||||
|
||||
if (readFrom(row, entity.getRequiredIdProperty(), prefix) == null) {
|
||||
if (readFrom(row, metadata, entity.getRequiredIdProperty(), prefix) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -195,7 +215,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
|
||||
for (RelationalPersistentProperty p : entity) {
|
||||
if (!entity.isConstructorArgument(property)) {
|
||||
propertyAccessor.setProperty(p, readFrom(row, p, prefix));
|
||||
propertyAccessor.setProperty(p, readFrom(row, metadata, p, prefix));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +233,10 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
// Entity writing
|
||||
// ----------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void write(Object source, OutboundRow sink) {
|
||||
|
||||
@@ -313,6 +337,11 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#getArrayValue(org.springframework.data.r2dbc.dialect.ArrayColumns, org.springframework.data.relational.core.mapping.RelationalPersistentProperty, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentProperty property, Object value) {
|
||||
|
||||
Class<?> targetType = arrayColumns.getArrayType(property.getActualType());
|
||||
@@ -337,6 +366,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
|
||||
|
||||
|
||||
@@ -71,4 +71,14 @@ public interface R2dbcConverter
|
||||
* @return
|
||||
*/
|
||||
<T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object);
|
||||
|
||||
/**
|
||||
* Reads the given source into the given type.
|
||||
*
|
||||
* @param type they type to convert the given source to.
|
||||
* @param source the source to create an object of the given type from.
|
||||
* @param metadata the {@link RowMetadata}.
|
||||
* @return
|
||||
*/
|
||||
<R> R read(Class<R> type, Row source, RowMetadata metadata);
|
||||
}
|
||||
|
||||
@@ -7,13 +7,15 @@ import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.r2dbc.convert.EntityRowMapper;
|
||||
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
|
||||
@@ -30,6 +32,14 @@ public class EntityRowMapperUnitTests {
|
||||
|
||||
Row rowMock = mock(Row.class);
|
||||
RowMetadata metadata = mock(RowMetadata.class);
|
||||
Collection<String> columns = mock(Collection.class);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
when(columns.contains(anyString())).thenReturn(true);
|
||||
when(metadata.getColumnNames()).thenReturn(columns);
|
||||
}
|
||||
|
||||
@Test // gh-22
|
||||
public void shouldMapSimpleEntity() {
|
||||
|
||||
@@ -29,13 +29,13 @@ import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
|
||||
import org.springframework.data.r2dbc.dialect.Dialect;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
|
||||
@@ -177,6 +177,9 @@ public abstract class ReactiveDataAccessStrategyTestSupport {
|
||||
ReactiveDataAccessStrategy strategy = getStrategy();
|
||||
Row rowMock = mock(Row.class);
|
||||
RowMetadata metadataMock = mock(RowMetadata.class);
|
||||
Collection<String> columnNames = mock(Collection.class);
|
||||
when(metadataMock.getColumnNames()).thenReturn(columnNames);
|
||||
when(columnNames.contains(fieldname)).thenReturn(true);
|
||||
|
||||
PrimitiveTypes toSave = new PrimitiveTypes();
|
||||
setter.accept(toSave, testValue);
|
||||
|
||||
@@ -81,7 +81,7 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn
|
||||
Flux<LegoSet> findByNameContains(String name);
|
||||
|
||||
@Override
|
||||
@Query("SELECT * FROM legoset")
|
||||
@Query("SELECT name FROM legoset")
|
||||
Flux<Named> findAsProjection();
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MySqlR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositor
|
||||
Flux<LegoSet> findByNameContains(String name);
|
||||
|
||||
@Override
|
||||
@Query("SELECT * FROM legoset")
|
||||
@Query("SELECT name FROM legoset")
|
||||
Flux<Named> findAsProjection();
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
|
||||
Flux<LegoSet> findByNameContains(String name);
|
||||
|
||||
@Override
|
||||
@Query("SELECT * FROM legoset")
|
||||
@Query("SELECT name FROM legoset")
|
||||
Flux<Named> findAsProjection();
|
||||
|
||||
@Override
|
||||
|
||||
@@ -90,7 +90,7 @@ public class SqlServerR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepos
|
||||
Flux<LegoSet> findByNameContains(String name);
|
||||
|
||||
@Override
|
||||
@Query("SELECT * FROM legoset")
|
||||
@Query("SELECT name FROM legoset")
|
||||
Flux<Named> findAsProjection();
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user