Skip to content

Commit 28dee12

Browse files
committed
#2 - Add updates to SimpleR2dbcRepository.
1 parent 590df39 commit 28dee12

File tree

3 files changed

+166
-14
lines changed

3 files changed

+166
-14
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.core.function;
17+
18+
import io.r2dbc.spi.Row;
19+
import io.r2dbc.spi.RowMetadata;
20+
21+
import java.util.LinkedHashMap;
22+
import java.util.Map;
23+
import java.util.Optional;
24+
import java.util.function.BiFunction;
25+
26+
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
27+
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty;
28+
import org.springframework.data.mapping.PersistentPropertyAccessor;
29+
import org.springframework.data.mapping.context.MappingContext;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.ClassUtils;
32+
33+
/**
34+
* Converter for R2DBC.
35+
*
36+
* @author Mark Paluch
37+
*/
38+
public class MappingR2dbcConverter {
39+
40+
private final MappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext;
41+
42+
public MappingR2dbcConverter(MappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> mappingContext) {
43+
this.mappingContext = mappingContext;
44+
}
45+
46+
/**
47+
* Returns a {@link Map} that maps column names to an {@link Optional} value. Used {@link Optional#empty()} if the
48+
* underlying property is {@literal null}.
49+
*
50+
* @param object must not be {@literal null}.
51+
* @return
52+
*/
53+
public Map<String, Optional<Object>> getFieldsToUpdate(Object object) {
54+
55+
Assert.notNull(object, "Entity object must not be null!");
56+
57+
Class<?> userClass = ClassUtils.getUserClass(object);
58+
JdbcPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(userClass);
59+
60+
Map<String, Optional<Object>> update = new LinkedHashMap<>();
61+
62+
PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object);
63+
64+
for (JdbcPersistentProperty property : entity) {
65+
update.put(property.getColumnName(), Optional.ofNullable(propertyAccessor.getProperty(property)));
66+
}
67+
68+
return update;
69+
}
70+
71+
/**
72+
* Returns a {@link java.util.function.Function} that populates the id property of the {@code object} from a
73+
* {@link Row}.
74+
*
75+
* @param object must not be {@literal null}.
76+
* @return
77+
*/
78+
@SuppressWarnings("unchecked")
79+
public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
80+
81+
Assert.notNull(object, "Entity object must not be null!");
82+
83+
Class<?> userClass = ClassUtils.getUserClass(object);
84+
JdbcPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(userClass);
85+
86+
return (row, metadata) -> {
87+
88+
PersistentPropertyAccessor propertyAccessor = entity.getPropertyAccessor(object);
89+
JdbcPersistentProperty idProperty = entity.getRequiredIdProperty();
90+
91+
if (propertyAccessor.getProperty(idProperty) == null) {
92+
93+
propertyAccessor.setProperty(idProperty, row.get(idProperty.getColumnName(), idProperty.getColumnType()));
94+
return (T) propertyAccessor.getBean();
95+
}
96+
97+
return object;
98+
};
99+
}
100+
}

src/main/java/org/springframework/data/jdbc/repository/support/SimpleR2dbcRepository.java

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import reactor.core.publisher.Mono;
2020

2121
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Optional;
2224
import java.util.stream.Collectors;
2325
import java.util.stream.IntStream;
2426

2527
import org.reactivestreams.Publisher;
2628
import org.springframework.data.jdbc.core.function.DatabaseClient;
2729
import org.springframework.data.jdbc.core.function.DatabaseClient.BindSpec;
2830
import org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec;
31+
import org.springframework.data.jdbc.core.function.FetchSpec;
32+
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter;
2933
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
3034
import org.springframework.data.mapping.IdentifierAccessor;
3135
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
@@ -39,17 +43,22 @@
3943
public class SimpleR2dbcRepository<T, ID> implements ReactiveCrudRepository<T, ID> {
4044

4145
private final DatabaseClient databaseClient;
46+
private final MappingR2dbcConverter converter;
4247
private final JdbcPersistentEntity<T> entity;
4348

4449
/**
4550
* Create a new {@link SimpleR2dbcRepository} given {@link DatabaseClient} and {@link JdbcPersistentEntity}.
4651
*
4752
* @param databaseClient must not be {@literal null}.
53+
* @param converter must not be {@literal null}.
4854
* @param entity must not be {@literal null}.
4955
*/
50-
public SimpleR2dbcRepository(DatabaseClient databaseClient, JdbcPersistentEntity<T> entity) {
56+
public SimpleR2dbcRepository(DatabaseClient databaseClient, MappingR2dbcConverter converter,
57+
JdbcPersistentEntity<T> entity) {
58+
this.converter = converter;
5159

5260
Assert.notNull(databaseClient, "DatabaseClient must not be null!");
61+
Assert.notNull(converter, "MappingR2dbcConverter must not be null!");
5362
Assert.notNull(entity, "PersistentEntity must not be null!");
5463

5564
this.databaseClient = databaseClient;
@@ -66,13 +75,57 @@ public <S extends T> Mono<S> save(S objectToSave) {
6675

6776
if (entity.isNew(objectToSave)) {
6877

69-
// TODO populate Id back to model
70-
return databaseClient.insert().into(entity.getType()).using(objectToSave).then().thenReturn(objectToSave);
78+
return databaseClient.insert() //
79+
.into(entity.getType()) //
80+
.using(objectToSave) //
81+
.exchange() //
82+
.flatMap(it -> it.extract(converter.populateIdIfNecessary(objectToSave)).one());
7183
}
7284

73-
// TODO update
85+
// TODO: Extract in some kind of SQL generator
86+
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(objectToSave);
87+
Object id = identifierAccessor.getRequiredIdentifier();
7488

75-
return null;
89+
Map<String, Optional<Object>> fields = converter.getFieldsToUpdate(objectToSave);
90+
91+
String setClause = getSetClause(fields);
92+
93+
GenericExecuteSpec exec = databaseClient.execute()
94+
.sql(String.format("UPDATE %s SET %s WHERE %s = $1", entity.getTableName(), setClause, getIdColumnName())) //
95+
.bind(0, id);
96+
97+
int index = 1;
98+
for (Optional<Object> setValue : fields.values()) {
99+
100+
Object value = setValue.orElse(null);
101+
if (value != null) {
102+
exec = exec.bind(index++, value);
103+
} else {
104+
exec = exec.bindNull(index++);
105+
}
106+
}
107+
108+
return exec.as(entity.getType()) //
109+
.exchange() //
110+
.flatMap(FetchSpec::rowsUpdated) //
111+
.thenReturn(objectToSave);
112+
}
113+
114+
private static String getSetClause(Map<String, Optional<Object>> fields) {
115+
116+
StringBuilder setClause = new StringBuilder();
117+
118+
int index = 2;
119+
for (String field : fields.keySet()) {
120+
121+
if (setClause.length() != 0) {
122+
setClause.append(", ");
123+
}
124+
125+
setClause.append(field).append('=').append('$').append(index++);
126+
}
127+
128+
return setClause.toString();
76129
}
77130

78131
/* (non-Javadoc)

src/test/java/org/springframework/data/jdbc/repository/support/SimpleR2dbcRepositoryIntegrationTests.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
import java.util.Map;
3232

3333
import org.junit.Before;
34-
import org.junit.Ignore;
3534
import org.junit.Test;
3635
import org.springframework.data.annotation.Id;
3736
import org.springframework.data.convert.EntityInstantiators;
3837
import org.springframework.data.jdbc.core.function.DatabaseClient;
3938
import org.springframework.data.jdbc.core.function.DefaultReactiveDataAccessStrategy;
39+
import org.springframework.data.jdbc.core.function.MappingR2dbcConverter;
4040
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4141
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
4242
import org.springframework.data.jdbc.core.mapping.Table;
@@ -66,6 +66,7 @@ public void before() {
6666
this.databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory)
6767
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(mappingContext, new EntityInstantiators())).build();
6868
this.repository = new SimpleR2dbcRepository<>(databaseClient,
69+
new MappingR2dbcConverter(mappingContext),
6970
(JdbcPersistentEntity<LegoSet>) mappingContext.getRequiredPersistentEntity(LegoSet.class));
7071

7172
this.jdbc = createJdbcTemplate(createDataSource());
@@ -84,24 +85,22 @@ public void shouldSaveNewObject() {
8485

8586
repository.save(legoSet) //
8687
.as(StepVerifier::create) //
87-
.expectNextCount(1) //
88+
.consumeNextWith(actual -> {
89+
90+
assertThat(actual.getId()).isNotNull();
91+
})
8892
.verifyComplete();
8993

9094
Map<String, Object> map = jdbc.queryForMap("SELECT * FROM repo_legoset");
9195
assertThat(map).containsEntry("name", "SCHAUFELRADBAGGER").containsEntry("manual", 12).containsKey("id");
9296
}
9397

9498
@Test
95-
@Ignore("Implement me")
9699
public void shouldUpdateObject() {
97100

98-
LegoSet legoSet = new LegoSet(null, "SCHAUFELRADBAGGER", 12);
99-
100-
repository.save(legoSet) //
101-
.as(StepVerifier::create) //
102-
.expectNextCount(1) //
103-
.verifyComplete();
101+
jdbc.execute("INSERT INTO repo_legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
104102

103+
LegoSet legoSet = new LegoSet(42055, "SCHAUFELRADBAGGER", 12);
105104
legoSet.setManual(14);
106105

107106
repository.save(legoSet) //

0 commit comments

Comments
 (0)