#2 - Add updates to SimpleR2dbcRepository.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2018 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.jdbc.core.function;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Converter for R2DBC.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MappingR2dbcConverter {
|
||||
|
||||
private final MappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext;
|
||||
|
||||
public MappingR2dbcConverter(MappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Map} that maps column names to an {@link Optional} value. Used {@link Optional#empty()} if the
|
||||
* underlying property is {@literal null}.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Optional<Object>> getFieldsToUpdate(Object object) {
|
||||
|
||||
Assert.notNull(object, "Entity object must not be null!");
|
||||
|
||||
Class<?> userClass = ClassUtils.getUserClass(object);
|
||||
JdbcPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(userClass);
|
||||
|
||||
Map<String, Optional<Object>> update = new LinkedHashMap<>();
|
||||
|
||||
PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object);
|
||||
|
||||
for (JdbcPersistentProperty property : entity) {
|
||||
update.put(property.getColumnName(), Optional.ofNullable(propertyAccessor.getProperty(property)));
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link java.util.function.Function} that populates the id property of the {@code object} from a
|
||||
* {@link Row}.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
|
||||
|
||||
Assert.notNull(object, "Entity object must not be null!");
|
||||
|
||||
Class<?> userClass = ClassUtils.getUserClass(object);
|
||||
JdbcPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(userClass);
|
||||
|
||||
return (row, metadata) -> {
|
||||
|
||||
PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object);
|
||||
JdbcPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
if (propertyAccessor.getProperty(idProperty) == null) {
|
||||
|
||||
propertyAccessor.setProperty(idProperty, row.get(idProperty.getColumnName(), idProperty.getColumnType()));
|
||||
return (T) propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
return object;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -26,6 +28,8 @@ import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient;
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient.BindSpec;
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec;
|
||||
import org.springframework.data.jdbc.core.function.FetchSpec;
|
||||
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
@@ -39,17 +43,22 @@ import org.springframework.util.Assert;
|
||||
public class SimpleR2dbcRepository<T, ID> implements ReactiveCrudRepository<T, ID> {
|
||||
|
||||
private final DatabaseClient databaseClient;
|
||||
private final MappingR2dbcConverter converter;
|
||||
private final JdbcPersistentEntity<T> entity;
|
||||
|
||||
/**
|
||||
* Create a new {@link SimpleR2dbcRepository} given {@link DatabaseClient} and {@link JdbcPersistentEntity}.
|
||||
*
|
||||
* @param databaseClient must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
*/
|
||||
public SimpleR2dbcRepository(DatabaseClient databaseClient, JdbcPersistentEntity<T> entity) {
|
||||
public SimpleR2dbcRepository(DatabaseClient databaseClient, MappingR2dbcConverter converter,
|
||||
JdbcPersistentEntity<T> entity) {
|
||||
this.converter = converter;
|
||||
|
||||
Assert.notNull(databaseClient, "DatabaseClient must not be null!");
|
||||
Assert.notNull(converter, "MappingR2dbcConverter must not be null!");
|
||||
Assert.notNull(entity, "PersistentEntity must not be null!");
|
||||
|
||||
this.databaseClient = databaseClient;
|
||||
@@ -66,13 +75,57 @@ public class SimpleR2dbcRepository<T, ID> implements ReactiveCrudRepository<T, I
|
||||
|
||||
if (entity.isNew(objectToSave)) {
|
||||
|
||||
// TODO populate Id back to model
|
||||
return databaseClient.insert().into(entity.getType()).using(objectToSave).then().thenReturn(objectToSave);
|
||||
return databaseClient.insert() //
|
||||
.into(entity.getType()) //
|
||||
.using(objectToSave) //
|
||||
.exchange() //
|
||||
.flatMap(it -> it.extract(converter.populateIdIfNecessary(objectToSave)).one());
|
||||
}
|
||||
|
||||
// TODO update
|
||||
// TODO: Extract in some kind of SQL generator
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(objectToSave);
|
||||
Object id = identifierAccessor.getRequiredIdentifier();
|
||||
|
||||
return null;
|
||||
Map<String, Optional<Object>> fields = converter.getFieldsToUpdate(objectToSave);
|
||||
|
||||
String setClause = getSetClause(fields);
|
||||
|
||||
GenericExecuteSpec exec = databaseClient.execute()
|
||||
.sql(String.format("UPDATE %s SET %s WHERE %s = $1", entity.getTableName(), setClause, getIdColumnName())) //
|
||||
.bind(0, id);
|
||||
|
||||
int index = 1;
|
||||
for (Optional<Object> setValue : fields.values()) {
|
||||
|
||||
Object value = setValue.orElse(null);
|
||||
if (value != null) {
|
||||
exec = exec.bind(index++, value);
|
||||
} else {
|
||||
exec = exec.bindNull(index++);
|
||||
}
|
||||
}
|
||||
|
||||
return exec.as(entity.getType()) //
|
||||
.exchange() //
|
||||
.flatMap(FetchSpec::rowsUpdated) //
|
||||
.thenReturn(objectToSave);
|
||||
}
|
||||
|
||||
private static String getSetClause(Map<String, Optional<Object>> fields) {
|
||||
|
||||
StringBuilder setClause = new StringBuilder();
|
||||
|
||||
int index = 2;
|
||||
for (String field : fields.keySet()) {
|
||||
|
||||
if (setClause.length() != 0) {
|
||||
setClause.append(", ");
|
||||
}
|
||||
|
||||
setClause.append(field).append('=').append('$').append(index++);
|
||||
}
|
||||
|
||||
return setClause.toString();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
@@ -31,12 +31,12 @@ import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
import org.springframework.data.jdbc.core.function.DatabaseClient;
|
||||
import org.springframework.data.jdbc.core.function.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.core.mapping.Table;
|
||||
@@ -66,6 +66,7 @@ public class SimpleR2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestS
|
||||
this.databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory)
|
||||
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(mappingContext, new EntityInstantiators())).build();
|
||||
this.repository = new SimpleR2dbcRepository<>(databaseClient,
|
||||
new MappingR2dbcConverter(mappingContext),
|
||||
(JdbcPersistentEntity<LegoSet>) mappingContext.getRequiredPersistentEntity(LegoSet.class));
|
||||
|
||||
this.jdbc = createJdbcTemplate(createDataSource());
|
||||
@@ -84,7 +85,10 @@ public class SimpleR2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestS
|
||||
|
||||
repository.save(legoSet) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.consumeNextWith(actual -> {
|
||||
|
||||
assertThat(actual.getId()).isNotNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
Map<String, Object> map = jdbc.queryForMap("SELECT * FROM repo_legoset");
|
||||
@@ -92,16 +96,11 @@ public class SimpleR2dbcRepositoryIntegrationTests extends R2dbcIntegrationTestS
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Implement me")
|
||||
public void shouldUpdateObject() {
|
||||
|
||||
LegoSet legoSet = new LegoSet(null, "SCHAUFELRADBAGGER", 12);
|
||||
|
||||
repository.save(legoSet) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
jdbc.execute("INSERT INTO repo_legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
|
||||
|
||||
LegoSet legoSet = new LegoSet(42055, "SCHAUFELRADBAGGER", 12);
|
||||
legoSet.setManual(14);
|
||||
|
||||
repository.save(legoSet) //
|
||||
|
||||
Reference in New Issue
Block a user