From a0f3aa5a17c367455ce4e164b13616d1fdcfd037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Tue, 7 Nov 2023 11:42:50 +0100 Subject: [PATCH] Add a reproducer for spring-projects/spring-framework#31534 --- data/data-jpa/build.gradle | 3 + .../data/jpa/DataJpaApplicationAotTests.java | 10 +++ .../data/jpa/NonTransactionalRunner.java | 18 ++++- .../example/data/jpa/RecipientRepository.java | 8 +++ .../com/example/data/jpa/model/Address.java | 9 +++ .../data/jpa/model/AddressInstantiator.java | 27 ++++++++ .../com/example/data/jpa/model/Recipient.java | 65 +++++++++++++++++++ 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 data/data-jpa/src/main/java/com/example/data/jpa/RecipientRepository.java create mode 100644 data/data-jpa/src/main/java/com/example/data/jpa/model/Address.java create mode 100644 data/data-jpa/src/main/java/com/example/data/jpa/model/AddressInstantiator.java create mode 100644 data/data-jpa/src/main/java/com/example/data/jpa/model/Recipient.java diff --git a/data/data-jpa/build.gradle b/data/data-jpa/build.gradle index 78a2b660..75c15589 100644 --- a/data/data-jpa/build.gradle +++ b/data/data-jpa/build.gradle @@ -7,6 +7,9 @@ plugins { dependencies { implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + constraints { + implementation("org.springframework:spring-orm:6.1.0-SNAPSHOT") + } implementation("org.springframework.boot:spring-boot-starter-data-jpa") runtimeOnly("com.h2database:h2") diff --git a/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java b/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java index e5014746..1e62b73c 100644 --- a/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java +++ b/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationAotTests.java @@ -108,4 +108,14 @@ class DataJpaApplicationAotTests { }); } + @Test + void listRecipients(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output) + .hasSingleLineContaining( + "listRecipients(): recipient = Recipient{id=1, address=Address[street=Paul Bert, city=Lyon, postalCode=69003]}"); + + }); + } + } diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/NonTransactionalRunner.java b/data/data-jpa/src/main/java/com/example/data/jpa/NonTransactionalRunner.java index f4ee0440..a67b0752 100644 --- a/data/data-jpa/src/main/java/com/example/data/jpa/NonTransactionalRunner.java +++ b/data/data-jpa/src/main/java/com/example/data/jpa/NonTransactionalRunner.java @@ -2,6 +2,8 @@ package com.example.data.jpa; import java.util.List; +import com.example.data.jpa.model.Address; +import com.example.data.jpa.model.Recipient; import com.example.data.jpa.model.Voucher; import org.springframework.boot.CommandLineRunner; @@ -12,14 +14,19 @@ public class NonTransactionalRunner implements CommandLineRunner { private final VoucherRepository voucherRepository; - public NonTransactionalRunner(VoucherRepository voucherRepository) { + private final RecipientRepository recipientRepository; + + public NonTransactionalRunner(VoucherRepository voucherRepository, RecipientRepository recipientRepository) { this.voucherRepository = voucherRepository; + this.recipientRepository = recipientRepository; } @Override public void run(String... args) { insertVouchers(); + insertRecipients(); listVouchers(); + listRecipients(); } private void insertVouchers() { @@ -32,4 +39,13 @@ public class NonTransactionalRunner implements CommandLineRunner { vouchers.forEach(voucher -> System.out.printf("listVouchers(): voucher = %s%n", voucher)); } + private void insertRecipients() { + this.recipientRepository.save(new Recipient(1L, new Address("Paul Bert", "Lyon", "69003"))); + } + + private void listRecipients() { + List recipients = this.recipientRepository.findAll(); + recipients.forEach(recipient -> System.out.printf("listRecipients(): recipient = %s%n", recipient)); + } + } diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/RecipientRepository.java b/data/data-jpa/src/main/java/com/example/data/jpa/RecipientRepository.java new file mode 100644 index 00000000..44ca382a --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/RecipientRepository.java @@ -0,0 +1,8 @@ +package com.example.data.jpa; + +import com.example.data.jpa.model.Recipient; + +import org.springframework.data.repository.ListCrudRepository; + +interface RecipientRepository extends ListCrudRepository { +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/model/Address.java b/data/data-jpa/src/main/java/com/example/data/jpa/model/Address.java new file mode 100644 index 00000000..cc56bf45 --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/model/Address.java @@ -0,0 +1,9 @@ +package com.example.data.jpa.model; + +import jakarta.persistence.Embeddable; +import org.hibernate.annotations.EmbeddableInstantiator; + +@Embeddable +@EmbeddableInstantiator(AddressInstantiator.class) +public record Address(String street, String city, String postalCode) { +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/model/AddressInstantiator.java b/data/data-jpa/src/main/java/com/example/data/jpa/model/AddressInstantiator.java new file mode 100644 index 00000000..09af6fe5 --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/model/AddressInstantiator.java @@ -0,0 +1,27 @@ +package com.example.data.jpa.model; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.metamodel.spi.ValueAccess; + +public class AddressInstantiator implements EmbeddableInstantiator { + + @Override + public Object instantiate(ValueAccess valueAccess, SessionFactoryImplementor sessionFactory) { + final Object[] values = valueAccess.getValues(); + final String city = (String) values[0]; + final String postalCode = (String) values[1]; + final String street = (String) values[2]; + return new Address( street, city, postalCode ); + } + + @Override + public boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) { + return object instanceof Address; + } + + @Override + public boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) { + return object.getClass().equals(Address.class); + } +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/model/Recipient.java b/data/data-jpa/src/main/java/com/example/data/jpa/model/Recipient.java new file mode 100644 index 00000000..c760929c --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/model/Recipient.java @@ -0,0 +1,65 @@ +package com.example.data.jpa.model; + +import java.util.Objects; + +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Recipient { + + @Id + @GeneratedValue + private Long id; + + @Embedded + private Address address; + + public Recipient() { + } + + public Recipient(Long id, Address address) { + this.id = id; + this.address = address; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Recipient recipient = (Recipient) o; + return Objects.equals(id, recipient.id) && Objects.equals(address, recipient.address); + } + + @Override + public int hashCode() { + return Objects.hash(id, address); + } + + @Override + public String toString() { + return "Recipient{" + + "id=" + id + + ", address=" + address + + '}'; + } + +}