Support of embedded properties in Sort.by.

Original pull request #1403
Closes #1286
This commit is contained in:
Mikhail2048
2022-08-19 15:50:58 +03:00
committed by Jens Schauder
parent 706de3f58a
commit 8eb80af044
10 changed files with 181 additions and 4 deletions

View File

@@ -23,6 +23,7 @@ import org.springframework.data.domain.Pageable;
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 @@ class SqlGenerator {
}
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<RelationalPersistentProperty> 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 <code>parameterSource</code>

View File

@@ -18,8 +18,12 @@ package org.springframework.data.jdbc.repository;
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.Bean;
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.context.junit.jupiter.SpringExtension;
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 @@ public class JdbcRepositoryEmbeddedIntegrationTests {
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 class JdbcRepositoryEmbeddedIntegrationTests {
"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<Person> 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<WithDotColumn> 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 @@ public class JdbcRepositoryEmbeddedIntegrationTests {
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
interface PersonRepository extends PagingAndSortingRepository<Person, Long>, CrudRepository<Person, Long> {}
interface WithDotColumnRepo extends PagingAndSortingRepository<WithDotColumn, Integer>, CrudRepository<WithDotColumn, Integer> {}
@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 {

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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));

View File

@@ -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)
);

View File

@@ -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));