Skip to content

Commit b8163ca

Browse files
Mikhail2048mipo256
authored andcommitted
GH-1286 implemented
1 parent c2d867a commit b8163ca

11 files changed

+182
-12
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,12 +783,51 @@ private List<OrderByField> extractOrderByFields(Sort sort) {
783783
}
784784

785785
private OrderByField orderToOrderByField(Sort.Order order) {
786-
787-
SqlIdentifier columnName = this.entity.getRequiredPersistentProperty(order.getProperty()).getColumnName();
786+
SqlIdentifier columnName = getColumnNameToSortBy(order);
788787
Column column = Column.create(columnName, this.getTable());
789788
return OrderByField.from(column, order.getDirection()).withNullHandling(order.getNullHandling());
790789
}
791790

791+
private SqlIdentifier getColumnNameToSortBy(Sort.Order order) {
792+
SqlIdentifier columnName = null;
793+
RelationalPersistentProperty propertyToSortBy = entity.getPersistentProperty(order.getProperty());
794+
if (propertyToSortBy != null) {
795+
return propertyToSortBy.getColumnName();
796+
}
797+
798+
propertyToSortBy = this.entity.getRequiredPersistentProperty(extractEmbeddedPropertyName(order));
799+
800+
if (!propertyToSortBy.isEmbedded()) {
801+
throwPropertyNotMarkedAsEmbeddedException(order);
802+
} else {
803+
RelationalPersistentEntity<?> embeddedEntity = mappingContext.getRequiredPersistentEntity(propertyToSortBy.getType());
804+
columnName = embeddedEntity.getRequiredPersistentProperty(extractFieldNameFromEmbeddedProperty(order)).getColumnName();
805+
}
806+
return columnName;
807+
}
808+
809+
private void throwPropertyNotMarkedAsEmbeddedException(Sort.Order order) {
810+
throw new IllegalArgumentException(
811+
String.format(
812+
"Specified sorting field '%s' is expected to " +
813+
"be the property, named '%s', of embedded entity '%s', but property '%s' is " +
814+
"not marked with @Embedded",
815+
order.getProperty(),
816+
extractFieldNameFromEmbeddedProperty(order),
817+
extractEmbeddedPropertyName(order),
818+
order.getProperty()
819+
)
820+
);
821+
}
822+
823+
public String extractEmbeddedPropertyName(Sort.Order order) {
824+
return order.getProperty().substring(0, order.getProperty().indexOf("."));
825+
}
826+
827+
public String extractFieldNameFromEmbeddedProperty(Sort.Order order) {
828+
return order.getProperty().substring(order.getProperty().indexOf(".") + 1);
829+
}
830+
792831
/**
793832
* Constructs a single sql query that performs select based on the provided query. Additional the bindings for the
794833
* where clause are stored after execution into the <code>parameterSource</code>

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedIntegrationTests.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,45 @@
1818
import static java.util.Arrays.*;
1919
import static org.assertj.core.api.Assertions.*;
2020

21+
import lombok.AllArgsConstructor;
2122
import lombok.Data;
2223

24+
import lombok.NoArgsConstructor;
25+
import org.assertj.core.api.Assertions;
26+
import org.junit.jupiter.api.Disabled;
2327
import org.junit.jupiter.api.Test;
2428
import org.junit.jupiter.api.extension.ExtendWith;
2529
import org.springframework.beans.factory.annotation.Autowired;
2630
import org.springframework.context.annotation.Bean;
2731
import org.springframework.context.annotation.Configuration;
2832
import org.springframework.context.annotation.Import;
2933
import org.springframework.data.annotation.Id;
34+
import org.springframework.data.domain.Sort;
3035
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
36+
import org.springframework.data.jdbc.testing.EnabledOnFeature;
3137
import org.springframework.data.jdbc.testing.TestConfiguration;
38+
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
39+
import org.springframework.data.relational.core.mapping.Column;
3240
import org.springframework.data.relational.core.mapping.Embedded;
3341
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
42+
import org.springframework.data.relational.core.mapping.Table;
3443
import org.springframework.data.repository.CrudRepository;
44+
import org.springframework.data.repository.PagingAndSortingRepository;
3545
import org.springframework.jdbc.core.JdbcTemplate;
3646
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
3747
import org.springframework.test.context.ContextConfiguration;
3848
import org.springframework.test.context.junit.jupiter.SpringExtension;
3949
import org.springframework.test.jdbc.JdbcTestUtils;
4050
import org.springframework.transaction.annotation.Transactional;
4151

52+
import java.util.List;
53+
4254
/**
4355
* Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities.
4456
*
4557
* @author Bastian Wilhelm
4658
* @author Christoph Strobl
59+
* @author Mikhail Polivakha
4760
*/
4861
@ContextConfiguration
4962
@Transactional
@@ -66,10 +79,21 @@ DummyEntityRepository dummyEntityRepository() {
6679
return factory.getRepository(DummyEntityRepository.class);
6780
}
6881

82+
@Bean
83+
PersonRepository personRepository() {
84+
return factory.getRepository(PersonRepository.class);
85+
}
86+
87+
@Bean
88+
WithDotColumnRepo withDotColumnRepo() { return factory.getRepository(WithDotColumnRepo.class);}
89+
6990
}
7091

7192
@Autowired NamedParameterJdbcTemplate template;
7293
@Autowired DummyEntityRepository repository;
94+
@Autowired PersonRepository personRepository;
95+
96+
@Autowired WithDotColumnRepo withDotColumnRepo;
7397

7498
@Test // DATAJDBC-111
7599
public void savesAnEntity() {
@@ -220,6 +244,35 @@ public void saveWithNullValueEmbeddable() {
220244
"id = " + entity.getId())).isEqualTo(1);
221245
}
222246

247+
@Test // GH-1286
248+
public void findOrderedByEmbeddedProperty() {
249+
Person first = new Person(null, "Bob", "Seattle", new PersonContacts("ddd@example.com", "+1 111 1111 11 11"));
250+
Person second = new Person(null, "Alex", "LA", new PersonContacts("aaa@example.com", "+2 222 2222 22 22"));
251+
Person third = new Person(null, "Sarah", "NY", new PersonContacts("ggg@example.com", "+3 333 3333 33 33"));
252+
253+
personRepository.saveAll(List.of(first, second, third));
254+
255+
Iterable<Person> fetchedPersons = personRepository.findAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "personContacts.email")));
256+
257+
Assertions.assertThat(fetchedPersons).hasSize(3);
258+
Assertions.assertThat(fetchedPersons).containsExactly(second, first, third);
259+
}
260+
261+
@Test // GH-1286
262+
public void sortingWorksCorrectlyIfColumnHasDotInItsName() {
263+
264+
WithDotColumn first = new WithDotColumn(null, "Salt Lake City");
265+
WithDotColumn second = new WithDotColumn(null, "Istanbul");
266+
WithDotColumn third = new WithDotColumn(null, "Tokyo");
267+
268+
withDotColumnRepo.saveAll(List.of(first, second, third));
269+
270+
Iterable<WithDotColumn> fetchedPersons = withDotColumnRepo.findAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "address")));
271+
272+
Assertions.assertThat(fetchedPersons).hasSize(3);
273+
Assertions.assertThat(fetchedPersons).containsExactly(second, first, third);
274+
}
275+
223276
private static DummyEntity createDummyEntity() {
224277
DummyEntity entity = new DummyEntity();
225278

@@ -246,6 +299,43 @@ private static DummyEntity createDummyEntity() {
246299

247300
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
248301

302+
interface PersonRepository extends PagingAndSortingRepository<Person, Long>, CrudRepository<Person, Long> {}
303+
304+
interface WithDotColumnRepo extends PagingAndSortingRepository<WithDotColumn, Integer>, CrudRepository<WithDotColumn, Integer> {}
305+
306+
@Data
307+
@AllArgsConstructor
308+
@NoArgsConstructor
309+
static class WithDotColumn {
310+
311+
@Id
312+
private Integer id;
313+
@Column("address.city")
314+
private String address;
315+
}
316+
317+
@Data
318+
@AllArgsConstructor
319+
@NoArgsConstructor
320+
@Table("SORT_EMBEDDED_ENTITY")
321+
static class Person {
322+
@Id
323+
private Long id;
324+
private String firstName;
325+
private String address;
326+
327+
@Embedded.Nullable
328+
private PersonContacts personContacts;
329+
}
330+
331+
@Data
332+
@AllArgsConstructor
333+
@NoArgsConstructor
334+
static class PersonContacts {
335+
private String email;
336+
private String phoneNumber;
337+
}
338+
249339
@Data
250340
static class DummyEntity {
251341

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
*/
1616
package org.springframework.data.jdbc.testing;
1717

18-
import static org.assertj.core.api.Assumptions.*;
18+
import org.springframework.jdbc.core.ConnectionCallback;
19+
import org.springframework.jdbc.core.JdbcOperations;
1920

2021
import java.util.Arrays;
2122
import java.util.Locale;
2223
import java.util.function.Consumer;
2324

24-
import org.springframework.jdbc.core.ConnectionCallback;
25-
import org.springframework.jdbc.core.JdbcOperations;
25+
import static org.assertj.core.api.Assumptions.assumeThat;
2626

2727
/**
2828
* This class provides information about which features a database integration supports in order to react on the
@@ -52,8 +52,7 @@ private void supportsHugeNumbers() {
5252

5353
/**
5454
* Oracles JDBC driver seems to have a bug that makes it impossible to acquire generated keys when the column is
55-
* quoted. See
56-
* https://stackoverflow.com/questions/62263576/how-to-get-the-generated-key-for-a-column-with-lowercase-characters-from-oracle
55+
* quoted. See <a href="https://stackoverflow.com/questions/62263576/how-to-get-the-generated-key-for-a-column-with-lowercase-characters-from-oracle">question on stackoverflow</a>
5756
*/
5857
private void supportsQuotedIds() {
5958
assumeThat(database).isNotEqualTo(Database.Oracle);
@@ -62,19 +61,17 @@ private void supportsQuotedIds() {
6261
/**
6362
* Microsoft SqlServer does not allow explicitly setting ids in columns where the value gets generated by the
6463
* database. Such columns therefore must not be used in referenced entities, since we do a delete and insert, which
65-
* must not recreate an id. See https://github.com/spring-projects/spring-data-jdbc/issues/437
64+
* must not recreate an id. See <a href="https://github.com/spring-projects/spring-data-jdbc/issues/437">github issue</a>
6665
*/
6766
private void supportsGeneratedIdsInReferencedEntities() {
6867
assumeThat(database).isNotEqualTo(Database.SqlServer);
6968
}
7069

7170
private void supportsArrays() {
72-
7371
assumeThat(database).isNotIn(Database.MySql, Database.MariaDb, Database.SqlServer, Database.Db2, Database.Oracle);
7472
}
7573

7674
private void supportsNanosecondPrecision() {
77-
7875
assumeThat(database).isNotIn(Database.MySql, Database.PostgreSql, Database.MariaDb, Database.SqlServer);
7976
}
8077

Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
DROP TABLE dummy_entity;
22
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);
3+
4+
DROP TABLE SORT_EMBEDDED_ENTITY;
5+
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));
6+
7+
DROP TABLE WITH_DOT_COLUMN;
8+
CREATE TABLE WITH_DOT_COLUMN (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, "address.city" VARCHAR(255));
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
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)
1+
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);
2+
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));
3+
CREATE TABLE WITH_DOT_COLUMN (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, "address.city" VARCHAR(255));
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
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)
1+
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);
2+
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));
3+
CREATE TABLE WITH_DOT_COLUMN (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, "address.city" VARCHAR(255));
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT);
2+
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));
3+
CREATE TABLE WITH_DOT_COLUMN (id BIGINT AUTO_INCREMENT PRIMARY KEY, `address.city` VARCHAR(255));
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
DROP TABLE IF EXISTS dummy_entity;
22
CREATE TABLE dummy_entity (id BIGINT IDENTITY PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT);
3+
4+
DROP TABLE IF EXISTS SORT_EMBEDDED_ENTITY;
5+
CREATE TABLE SORT_EMBEDDED_ENTITY (id BIGINT IDENTITY PRIMARY KEY, first_name VARCHAR(100), address VARCHAR(255), email VARCHAR(255), phone_number VARCHAR(255));
6+
7+
DROP TABLE IF EXISTS WITH_DOT_COLUMN;
8+
CREATE TABLE WITH_DOT_COLUMN (id BIGINT IDENTITY PRIMARY KEY, "address.city" VARCHAR(255));
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
CREATE TABLE dummy_entity (id BIGINT AUTO_INCREMENT PRIMARY KEY, TEST VARCHAR(100), PREFIX2_ATTR BIGINT, PREFIX_TEST VARCHAR(100), PREFIX_PREFIX2_ATTR BIGINT);
2+
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));
3+
CREATE TABLE WITH_DOT_COLUMN (id BIGINT AUTO_INCREMENT PRIMARY KEY, `address.city` VARCHAR(255));

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,20 @@ CREATE TABLE DUMMY_ENTITY (
77
PREFIX_TEST VARCHAR2(100),
88
PREFIX_PREFIX2_ATTR NUMBER
99
);
10+
11+
DROP TABLE SORT_EMBEDDED_ENTITY CASCADE CONSTRAINTS PURGE;
12+
13+
CREATE TABLE SORT_EMBEDDED_ENTITY (
14+
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
15+
first_name VARCHAR(100),
16+
address VARCHAR(255),
17+
email VARCHAR(255),
18+
phone_number VARCHAR(255)
19+
);
20+
21+
DROP TABLE WITH_DOT_COLUMN CASCADE CONSTRAINTS PURGE;
22+
23+
CREATE TABLE WITH_DOT_COLUMN (
24+
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
25+
"address.city" VARCHAR(255)
26+
);

0 commit comments

Comments
 (0)