Fall back to canonical constructor in constructor resolution when using Java Records.

We now fall back to the canonical constructor of records when we cannot disambiguate which constructor to use.

Also, reflect the Kotlin persistence creator mechanism.

Closes #2625
Original pull request: #2694.
This commit is contained in:
Mark Paluch
2022-10-04 11:24:20 +02:00
parent e003edccc3
commit 6a0a404ef2
5 changed files with 143 additions and 15 deletions

View File

@@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*;
import lombok.Value;
import org.junit.jupiter.api.Test;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.mapping.context.SamplePersistentProperty;
import org.springframework.data.mapping.model.EntityInstantiators;
@@ -44,8 +45,7 @@ class InstantiationAwarePersistentPropertyAccessorUnitTests {
var sample = new Sample("Dave", "Matthews", 42);
PersistentPropertyAccessor<Sample> wrapper = new InstantiationAwarePropertyAccessor<>(sample,
entity::getPropertyAccessor,
instantiators);
entity::getPropertyAccessor, instantiators);
wrapper.setProperty(entity.getRequiredPersistentProperty("firstname"), "Oliver August");
@@ -71,10 +71,38 @@ class InstantiationAwarePersistentPropertyAccessorUnitTests {
assertThat(wrapper.getBean()).isEqualTo(new Sample("Oliver August", "Heisenberg", 42));
}
@Test // GH-2625
void shouldSetPropertyOfRecordUsingCanonicalConstructor() {
var instantiators = new EntityInstantiators();
var context = new SampleMappingContext();
PersistentEntity<Object, SamplePersistentProperty> entity = context
.getRequiredPersistentEntity(WithSingleArgConstructor.class);
var bean = new WithSingleArgConstructor(42L, "Dave");
PersistentPropertyAccessor<WithSingleArgConstructor> wrapper = new InstantiationAwarePropertyAccessor<>(bean,
entity::getPropertyAccessor, instantiators);
wrapper.setProperty(entity.getRequiredPersistentProperty("name"), "Oliver August");
wrapper.setProperty(entity.getRequiredPersistentProperty("id"), 41L);
assertThat(wrapper.getBean()).isEqualTo(new WithSingleArgConstructor(41L, "Oliver August"));
}
@Value
static class Sample {
String firstname, lastname;
int age;
}
public record WithSingleArgConstructor(Long id, String name) {
public WithSingleArgConstructor(String name) {
this(null, name);
}
}
}

View File

@@ -26,6 +26,7 @@ import java.util.Iterator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.mapping.PreferredConstructorDiscovererUnitTests.Outer.Inner;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
@@ -149,6 +150,34 @@ class PreferredConstructorDiscovererUnitTests<P extends PersistentProperty<P>> {
});
}
@Test // GH-2332
void detectsCanonicalRecordConstructor() {
assertThat(PreferredConstructorDiscoverer.discover(RecordWithSingleArgConstructor.class)).satisfies(ctor -> {
assertThat(ctor.getParameters()).hasSize(2);
assertThat(ctor.getParameters().get(0).getRawType()).isEqualTo(Long.class);
assertThat(ctor.getParameters().get(1).getRawType()).isEqualTo(String.class);
});
assertThat(PreferredConstructorDiscoverer.discover(RecordWithNoArgConstructor.class)).satisfies(ctor -> {
assertThat(ctor.getParameters()).hasSize(2);
assertThat(ctor.getParameters().get(0).getRawType()).isEqualTo(Long.class);
assertThat(ctor.getParameters().get(1).getRawType()).isEqualTo(String.class);
});
}
@Test // GH-2332
void detectsAnnotatedRecordConstructor() {
assertThat(PreferredConstructorDiscoverer.discover(RecordWithPersistenceCreator.class)).satisfies(ctor -> {
assertThat(ctor.getParameters()).hasSize(1);
assertThat(ctor.getParameters().get(0).getRawType()).isEqualTo(String.class);
});
}
static class SyntheticConstructor {
@PersistenceConstructor
private SyntheticConstructor(String x) {}
@@ -227,5 +256,28 @@ class PreferredConstructorDiscovererUnitTests<P extends PersistentProperty<P>> {
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Value("${hello-world}")
@interface MyValue {}
@interface MyValue {
}
public record RecordWithSingleArgConstructor(Long id, String name) {
public RecordWithSingleArgConstructor(String name) {
this(null, name);
}
}
public record RecordWithNoArgConstructor(Long id, String name) {
public RecordWithNoArgConstructor(String name) {
this(null, null);
}
}
public record RecordWithPersistenceCreator(Long id, String name) {
@PersistenceCreator
public RecordWithPersistenceCreator(String name) {
this(null, name);
}
}
}