#215 - Add support for EntityCallbacks.

We now support entity callbacks for:

* AfterConvertCallback
* BeforeConvertCallback
* BeforeSaveCallback
* AfterSaveCallback

through R2dbcEntityTemplate.

Original pull request: #397.
This commit is contained in:
Mark Paluch
2020-07-14 15:06:22 +02:00
committed by Jens Schauder
parent 06cc4e2701
commit bc698c3885
11 changed files with 584 additions and 43 deletions

View File

@@ -21,11 +21,15 @@ import io.r2dbc.spi.test.MockColumnMetadata;
import io.r2dbc.spi.test.MockResult;
import io.r2dbc.spi.test.MockRow;
import io.r2dbc.spi.test.MockRowMetadata;
import lombok.ToString;
import lombok.Value;
import lombok.With;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -33,13 +37,22 @@ import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.mapping.SettableValue;
import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback;
import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback;
import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback;
import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback;
import org.springframework.data.r2dbc.testing.StatementRecorder;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.core.query.Update;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* Unit tests for {@link R2dbcEntityTemplate}.
@@ -119,6 +132,31 @@ public class R2dbcEntityTemplateUnitTests {
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
}
@Test // gh-215
public void selectShouldInvokeCallback() {
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(MockColumnMetadata.builder().name("id").build())
.columnMetadata(MockColumnMetadata.builder().name("THE_NAME").build()).build();
MockResult result = MockResult.builder().rowMetadata(metadata).row(MockRow.builder()
.identified("id", Object.class, "Walter").identified("THE_NAME", Object.class, "some-name").build()).build();
recorder.addStubbing(s -> s.startsWith("SELECT"), result);
ValueCapturingAfterConvertCallback callback = new ValueCapturingAfterConvertCallback();
entityTemplate.setEntityCallbacks(ReactiveEntityCallbacks.create(callback));
entityTemplate.select(Query.empty(), Person.class) //
.as(StepVerifier::create) //
.consumeNextWith(actual -> {
assertThat(actual.id).isEqualTo("after-convert");
assertThat(actual.name).isEqualTo("some-name");
}).verifyComplete();
assertThat(callback.getValues()).hasSize(1);
}
@Test // gh-220
public void shouldSelectOne() {
@@ -215,6 +253,34 @@ public class R2dbcEntityTemplateUnitTests {
SettableValue.from(1L));
}
@Test // gh-215
public void insertShouldInvokeCallback() {
MockRowMetadata metadata = MockRowMetadata.builder().build();
MockResult result = MockResult.builder().rowMetadata(metadata).rowsUpdated(1).build();
recorder.addStubbing(s -> s.startsWith("INSERT"), result);
ValueCapturingBeforeConvertCallback beforeConvert = new ValueCapturingBeforeConvertCallback();
ValueCapturingBeforeSaveCallback beforeSave = new ValueCapturingBeforeSaveCallback();
ValueCapturingAfterSaveCallback afterSave = new ValueCapturingAfterSaveCallback();
entityTemplate.setEntityCallbacks(ReactiveEntityCallbacks.create(beforeConvert, beforeSave, afterSave));
entityTemplate.insert(new Person()).as(StepVerifier::create) //
.assertNext(actual -> {
assertThat(actual.id).isEqualTo("after-save");
assertThat(actual.name).isEqualTo("before-convert");
assertThat(actual.description).isNull();
}) //
.verifyComplete();
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT"));
assertThat(statement.getSql()).isEqualTo("INSERT INTO person (THE_NAME, description) VALUES ($1, $2)");
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("before-convert"))
.containsEntry(1, SettableValue.from("before-save"));
}
@Test // gh-365
public void shouldUpdateVersioned() {
@@ -237,12 +303,48 @@ public class R2dbcEntityTemplateUnitTests {
SettableValue.from(1L));
}
@Test // gh-215
public void updateShouldInvokeCallback() {
MockRowMetadata metadata = MockRowMetadata.builder().build();
MockResult result = MockResult.builder().rowMetadata(metadata).rowsUpdated(1).build();
recorder.addStubbing(s -> s.startsWith("UPDATE"), result);
ValueCapturingBeforeConvertCallback beforeConvert = new ValueCapturingBeforeConvertCallback();
ValueCapturingBeforeSaveCallback beforeSave = new ValueCapturingBeforeSaveCallback();
ValueCapturingAfterSaveCallback afterSave = new ValueCapturingAfterSaveCallback();
Person person = new Person();
person.id = "the-id";
person.name = "name";
person.description = "description";
entityTemplate.setEntityCallbacks(ReactiveEntityCallbacks.create(beforeConvert, beforeSave, afterSave));
entityTemplate.update(person).as(StepVerifier::create) //
.assertNext(actual -> {
assertThat(actual.id).isEqualTo("after-save");
assertThat(actual.name).isEqualTo("before-convert");
assertThat(actual.description).isNull();
}) //
.verifyComplete();
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE"));
assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1, description = $2 WHERE person.id = $3");
assertThat(statement.getBindings()).hasSize(3).containsEntry(0, SettableValue.from("before-convert"))
.containsEntry(1, SettableValue.from("before-save"));
}
@ToString
static class Person {
@Id String id;
@Column("THE_NAME") String name;
String description;
public String getName() {
return name;
}
@@ -262,4 +364,77 @@ public class R2dbcEntityTemplateUnitTests {
String name;
}
static class ValueCapturingEntityCallback<T> {
private final List<T> values = new ArrayList<>(1);
protected void capture(T value) {
values.add(value);
}
public List<T> getValues() {
return values;
}
@Nullable
public T getValue() {
return CollectionUtils.lastElement(values);
}
}
static class ValueCapturingBeforeConvertCallback extends ValueCapturingEntityCallback<Person>
implements BeforeConvertCallback<Person> {
@Override
public Mono<Person> onBeforeConvert(Person entity, SqlIdentifier table) {
capture(entity);
entity.name = "before-convert";
return Mono.just(entity);
}
}
static class ValueCapturingBeforeSaveCallback extends ValueCapturingEntityCallback<Person>
implements BeforeSaveCallback<Person> {
@Override
public Mono<Person> onBeforeSave(Person entity, OutboundRow outboundRow, SqlIdentifier table) {
capture(entity);
outboundRow.put(SqlIdentifier.unquoted("description"), SettableValue.from("before-save"));
return Mono.just(entity);
}
}
static class ValueCapturingAfterSaveCallback extends ValueCapturingEntityCallback<Person>
implements AfterSaveCallback<Person> {
@Override
public Mono<Person> onAfterSave(Person entity, OutboundRow outboundRow, SqlIdentifier table) {
capture(entity);
Person person = new Person();
person.id = "after-save";
person.name = entity.name;
return Mono.just(person);
}
}
static class ValueCapturingAfterConvertCallback extends ValueCapturingEntityCallback<Person>
implements AfterConvertCallback<Person> {
@Override
public Mono<Person> onAfterConvert(Person entity, SqlIdentifier table) {
capture(entity);
Person person = new Person();
person.id = "after-convert";
person.name = entity.name;
return Mono.just(person);
}
}
}