From 1de4074bd67cb9e6765c7b5403ddadb92790ec2c Mon Sep 17 00:00:00 2001 From: Mikhail2048 Date: Fri, 19 Aug 2022 15:50:58 +0300 Subject: [PATCH] GH-1286 implemented --- .../data/jdbc/core/convert/SqlGenerator.java | 48 +++++++++- ...dbcRepositoryEmbeddedIntegrationTests.java | 90 +++++++++++++++++++ ...RepositoryEmbeddedIntegrationTests-db2.sql | 6 ++ ...cRepositoryEmbeddedIntegrationTests-h2.sql | 4 +- ...epositoryEmbeddedIntegrationTests-hsql.sql | 4 +- ...sitoryEmbeddedIntegrationTests-mariadb.sql | 2 + ...positoryEmbeddedIntegrationTests-mssql.sql | 6 ++ ...positoryEmbeddedIntegrationTests-mysql.sql | 2 + ...ositoryEmbeddedIntegrationTests-oracle.sql | 17 ++++ ...itoryEmbeddedIntegrationTests-postgres.sql | 6 ++ 10 files changed, 181 insertions(+), 4 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index 42e1466a0d..fab5fc9e9a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -23,6 +23,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.RenderContextFactory; @@ -783,12 +784,55 @@ private List extractOrderByFields(Sort sort) { } private OrderByField orderToOrderByField(Sort.Order order) { - - SqlIdentifier columnName = this.entity.getRequiredPersistentProperty(order.getProperty()).getColumnName(); + SqlIdentifier columnName = getColumnNameToSortBy(order); Column column = Column.create(columnName, this.getTable()); return OrderByField.from(column, order.getDirection()).withNullHandling(order.getNullHandling()); } + private SqlIdentifier getColumnNameToSortBy(Sort.Order order) { + SqlIdentifier columnName = null; + RelationalPersistentProperty propertyToSortBy = entity.getPersistentProperty(order.getProperty()); + if (propertyToSortBy != null) { + return propertyToSortBy.getColumnName(); + } + + PersistentPropertyPath persistentPropertyPath = mappingContext.getPersistentPropertyPath( + order.getProperty(), entity.getType() + ); + + propertyToSortBy = persistentPropertyPath.getBaseProperty(); + + if (propertyToSortBy == null || !propertyToSortBy.isEmbedded()) { + throwPropertyNotMarkedAsEmbeddedException(order); + } else { + RelationalPersistentEntity embeddedEntity = mappingContext.getRequiredPersistentEntity(propertyToSortBy.getType()); + columnName = embeddedEntity.getRequiredPersistentProperty(extractFieldNameFromEmbeddedProperty(order)).getColumnName(); + } + return columnName; + } + + private void throwPropertyNotMarkedAsEmbeddedException(Sort.Order order) { + throw new IllegalArgumentException( + String.format( + "Specified sorting property '%s' is expected to " + + "be the property, named '%s', of embedded entity '%s', but field '%s' is " + + "not marked with @Embedded", + order.getProperty(), + extractFieldNameFromEmbeddedProperty(order), + extractEmbeddedPropertyName(order), + extractEmbeddedPropertyName(order) + ) + ); + } + + public String extractEmbeddedPropertyName(Sort.Order order) { + return order.getProperty().substring(0, order.getProperty().indexOf(".")); + } + + public String extractFieldNameFromEmbeddedProperty(Sort.Order order) { + return order.getProperty().substring(order.getProperty().indexOf(".") + 1); + } + /** * Constructs a single sql query that performs select based on the provided query. Additional the bindings for the * where clause are stored after execution into the parameterSource diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java index 6babd2787a..2ceea55519 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java @@ -18,8 +18,12 @@ import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -27,11 +31,17 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; +import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.EnabledOnFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; +import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; +import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ContextConfiguration; @@ -39,11 +49,14 @@ import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + /** * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. * * @author Bastian Wilhelm * @author Christoph Strobl + * @author Mikhail Polivakha */ @ContextConfiguration @Transactional @@ -66,10 +79,21 @@ DummyEntityRepository dummyEntityRepository() { return factory.getRepository(DummyEntityRepository.class); } + @Bean + PersonRepository personRepository() { + return factory.getRepository(PersonRepository.class); + } + + @Bean + WithDotColumnRepo withDotColumnRepo() { return factory.getRepository(WithDotColumnRepo.class);} + } @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; + @Autowired PersonRepository personRepository; + + @Autowired WithDotColumnRepo withDotColumnRepo; @Test // DATAJDBC-111 public void savesAnEntity() { @@ -220,6 +244,35 @@ public void saveWithNullValueEmbeddable() { "id = " + entity.getId())).isEqualTo(1); } + @Test // GH-1286 + public void findOrderedByEmbeddedProperty() { + Person first = new Person(null, "Bob", "Seattle", new PersonContacts("ddd@example.com", "+1 111 1111 11 11")); + Person second = new Person(null, "Alex", "LA", new PersonContacts("aaa@example.com", "+2 222 2222 22 22")); + Person third = new Person(null, "Sarah", "NY", new PersonContacts("ggg@example.com", "+3 333 3333 33 33")); + + personRepository.saveAll(List.of(first, second, third)); + + Iterable fetchedPersons = personRepository.findAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "personContacts.email"))); + + Assertions.assertThat(fetchedPersons).hasSize(3); + Assertions.assertThat(fetchedPersons).containsExactly(second, first, third); + } + + @Test // GH-1286 + public void sortingWorksCorrectlyIfColumnHasDotInItsName() { + + WithDotColumn first = new WithDotColumn(null, "Salt Lake City"); + WithDotColumn second = new WithDotColumn(null, "Istanbul"); + WithDotColumn third = new WithDotColumn(null, "Tokyo"); + + withDotColumnRepo.saveAll(List.of(first, second, third)); + + Iterable fetchedPersons = withDotColumnRepo.findAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "address"))); + + Assertions.assertThat(fetchedPersons).hasSize(3); + Assertions.assertThat(fetchedPersons).containsExactly(second, first, third); + } + private static DummyEntity createDummyEntity() { DummyEntity entity = new DummyEntity(); @@ -246,6 +299,43 @@ private static DummyEntity createDummyEntity() { interface DummyEntityRepository extends CrudRepository {} + interface PersonRepository extends PagingAndSortingRepository, CrudRepository {} + + interface WithDotColumnRepo extends PagingAndSortingRepository, CrudRepository {} + + @Data + @AllArgsConstructor + @NoArgsConstructor + static class WithDotColumn { + + @Id + private Integer id; + @Column("address.city") + private String address; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Table("SORT_EMBEDDED_ENTITY") + static class Person { + @Id + private Long id; + private String firstName; + private String address; + + @Embedded.Nullable + private PersonContacts personContacts; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + static class PersonContacts { + private String email; + private String phoneNumber; + } + @Data static class DummyEntity { diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-db2.sql index 1e790d582e..f61158e9cf 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-db2.sql @@ -1,2 +1,8 @@ DROP TABLE dummy_entity; CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); + +DROP TABLE SORT_EMBEDDED_ENTITY; +CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); + +DROP TABLE WITH_DOT_COLUMN; +CREATE TABLE WITH_DOT_COLUMN (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, "address.city" VARCHAR(255)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-h2.sql index b6619706d3..91d10ca8f1 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-h2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-h2.sql @@ -1 +1,3 @@ -CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT) +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); +CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); +CREATE TABLE WITH_DOT_COLUMN (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, "address.city" VARCHAR(255)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql index b6619706d3..91d10ca8f1 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-hsql.sql @@ -1 +1,3 @@ -CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT) +CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); +CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); +CREATE TABLE WITH_DOT_COLUMN (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, "address.city" VARCHAR(255)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql index 2faa643a99..e203ab3b9f 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mariadb.sql @@ -1 +1,3 @@ CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); +CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); +CREATE TABLE WITH_DOT_COLUMN (id BIGINT AUTO_INCREMENT PRIMARY KEY, `address.city` VARCHAR(255)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql index 2832a7ace8..7b934c7b06 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mssql.sql @@ -1,2 +1,8 @@ DROP TABLE IF EXISTS dummy_entity; CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); + +DROP TABLE IF EXISTS SORT_EMBEDDED_ENTITY; +CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT IDENTITY PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); + +DROP TABLE IF EXISTS WITH_DOT_COLUMN; +CREATE TABLE WITH_DOT_COLUMN (id BIGINT IDENTITY PRIMARY KEY, "address.city" VARCHAR(255)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql index 2faa643a99..e203ab3b9f 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-mysql.sql @@ -1 +1,3 @@ CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); +CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); +CREATE TABLE WITH_DOT_COLUMN (id BIGINT AUTO_INCREMENT PRIMARY KEY, `address.city` VARCHAR(255)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql index 4ad7130964..02766179a4 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql @@ -7,3 +7,20 @@ CREATE TABLE DUMMY_ENTITY ( PREFIX_TEST VARCHAR2(100), PREFIX_PREFIX2_ATTR NUMBER ); + +DROP TABLE SORT_EMBEDDED_ENTITY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE SORT_EMBEDDED_ENTITY ( + id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + first_name VARCHAR(100), + address VARCHAR(255), + email VARCHAR(255), + phone_number VARCHAR(255) +); + +DROP TABLE WITH_DOT_COLUMN CASCADE CONSTRAINTS PURGE; + +CREATE TABLE WITH_DOT_COLUMN ( + id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + "address.city" VARCHAR(255) +); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql index a5d589d4f8..c56a4b289f 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-postgres.sql @@ -1,2 +1,8 @@ DROP TABLE dummy_entity; CREATE TABLE dummy_entity (id SERIAL PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT); + +DROP TABLE "SORT_EMBEDDED_ENTITY"; +CREATE TABLE "SORT_EMBEDDED_ENTITY" (id SERIAL PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255)); + +DROP TABLE WITH_DOT_COLUMN; +CREATE TABLE WITH_DOT_COLUMN (id SERIAL PRIMARY KEY, "address.city" VARCHAR(255)); \ No newline at end of file