From c776c68a5a5493b0eb29ebe75ec79e2515d70074 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Thu, 27 Mar 2025 00:01:43 +0900 Subject: [PATCH 01/20] Add utilities for Pageable to be used with Doma Criteria. --- .../doma/boot/PageablesForCriteria.java | 153 ++++++++++++++++++ .../doma/boot/PropertyMetamodelResolver.java | 21 +++ .../doma/boot/PageablesForCriteriaTest.java | 142 ++++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java create mode 100644 doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java create mode 100644 doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java new file mode 100644 index 0000000..043c1ff --- /dev/null +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java @@ -0,0 +1,153 @@ +package org.seasar.doma.boot; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; +import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; +import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; +import org.seasar.doma.jdbc.criteria.statement.EntityQueryable; +import org.springframework.data.domain.Pageable; + +/** + * Converts Utilities for {@link Pageable} to be used with Doma Criteria. + * + * @author mazeneko + */ +public class PageablesForCriteria { + /** + * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} + * + * @param pageable {@link Pageable} object to convert + * @return the limit. + * if {@link Pageable#isUnpaged()} is {@code true} then null. + */ + public static Integer limit(Pageable pageable) { + return pageable.isUnpaged() ? null : pageable.getPageSize(); + } + + /** + * Converts {@link Pageable} to {@link EntityQueryable#offset(Integer)} + * + * @param pageable {@link Pageable} object to convert + * @return the offset. + * if {@link Pageable#isUnpaged()} is {@code true} then null. + */ + public static Integer offset(Pageable pageable) { + return pageable.isUnpaged() ? null : Math.multiplyExact(pageable.getPageNumber(), pageable.getPageSize()); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer for a single entity based + * on the {@link Pageable}'s sort information. + *

+ * This method resolves property names for ordering within the specified entity + * using its metamodel, and generates + * a consumer that can be used to apply ascending/descending orders. + *

+ * If the {@link Pageable} is unsorted, no ordering is applied. + * + * @param pageable the {@link Pageable} containing sorting information + * @param entityMetamodel the {@link EntityMetamodel} corresponding to the + * target entity + * @return a consumer that configures ordering on the target entity + */ + public static Consumer orderBySingleEntity( + Pageable pageable, + EntityMetamodel entityMetamodel) { + return orderBySingleEntity(pageable, entityMetamodel, c -> { + }); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer for a single entity based + * on the {@link Pageable}'s sort information. + *

+ * This method resolves property names for ordering within the specified entity + * using its metamodel, and generates + * a consumer that can be used to apply ascending/descending orders. + *

+ * a default ordering via {@code defaultOrder} if the given {@link Pageable} is + * unsorted. + * + * @param pageable the {@link Pageable} containing sorting information + * @param entityMetamodel the {@link EntityMetamodel} corresponding to the + * target entity + * @param defaultOrder a consumer that applies default ordering if the + * {@link Pageable} is unsorted + * @return a consumer that configures ordering on the target entity + */ + public static Consumer orderBySingleEntity( + Pageable pageable, + EntityMetamodel entityMetamodel, + Consumer defaultOrder) { + final var nameToMetamodel = entityMetamodel + .allPropertyMetamodels() + .stream() + .collect(Collectors.toMap(PropertyMetamodel::getName, Function.identity())); + return orderBy( + pageable, + propertyName -> Optional.ofNullable(nameToMetamodel.get(propertyName)), + defaultOrder); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer based on the + * {@link Pageable}'s sort information + * using the provided {@link PropertyMetamodelResolver}. + *

+ * If the {@link Pageable} is unsorted, no ordering is applied. + * + * @param pageable the {@link Pageable} containing sorting + * information + * @param propertyMetamodelResolver a resolver that maps property names to + * {@link PropertyMetamodel} + * @return a consumer that configures ordering based on the resolved + * {@link PropertyMetamodel} instances + */ + public static Consumer orderBy( + Pageable pageable, + PropertyMetamodelResolver propertyMetamodelResolver) { + return orderBy(pageable, propertyMetamodelResolver, c -> { + }); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer based on the + * {@link Pageable}'s sort information + * using the provided {@link PropertyMetamodelResolver}. + *

+ * a default ordering via {@code defaultOrder} if the given {@link Pageable} is + * unsorted. + * + * @param pageable the {@link Pageable} containing sorting + * information + * @param propertyMetamodelResolver a resolver that maps property names to + * {@link PropertyMetamodel} + * @param defaultOrder a consumer that applies default ordering if + * the {@link Pageable} is unsorted + * @return a consumer that configures ordering based on the resolved + * {@link PropertyMetamodel} instances + */ + public static Consumer orderBy( + Pageable pageable, + PropertyMetamodelResolver propertyMetamodelResolver, + Consumer defaultOrder) { + if (pageable.getSort().isUnsorted()) { + return defaultOrder; + } + final var orderSpecifiers = pageable + .getSort() + .flatMap(order -> propertyMetamodelResolver + .resolve(order.getProperty()) + .>map(propertyMetamodel -> switch (order.getDirection()) { + case ASC -> c -> c.asc(propertyMetamodel); + case DESC -> c -> c.desc(propertyMetamodel); + }) + .stream()) + .toList(); + return c -> orderSpecifiers.forEach(orderSpecifier -> orderSpecifier.accept(c)); + } +} diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java new file mode 100644 index 0000000..1f0a7bc --- /dev/null +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java @@ -0,0 +1,21 @@ +package org.seasar.doma.boot; + +import java.util.Optional; + +import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; + +/** + * A resolver that maps property names to {@link PropertyMetamodel} + */ +@FunctionalInterface +public interface PropertyMetamodelResolver { + /** + * Resolves the specified property name into a {@link PropertyMetamodel}. + * + * @param propertyName the name of the property to resolve + * @return an {@link Optional} containing the resolved {@link PropertyMetamodel} + * if found, + * or an empty {@link Optional} if the property name cannot be resolved + */ + Optional> resolve(String propertyName); +} diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java new file mode 100644 index 0000000..0eea863 --- /dev/null +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java @@ -0,0 +1,142 @@ +package org.seasar.doma.boot; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; +import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; +import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class PageablesForCriteriaTest { + @Test + public void testOffsetAndLimit() { + Pageable pageable = PageRequest.of(0, 10); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, is(0)); + assertThat(limit, is(10)); + } + + @Test + public void testOffsetAndLimit2() { + Pageable pageable = PageRequest.of(2, 10); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, is(20)); + assertThat(limit, is(10)); + } + + @Test + public void testOffsetAndLimit3() { + Pageable pageable = PageRequest.of(2, 5); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, is(10)); + assertThat(limit, is(5)); + } + + @Test + public void testOffsetAndLimit4() { + Pageable pageable = Pageable.unpaged(); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, nullValue()); + assertThat(limit, nullValue()); + } + + @Test + public void testOrderBy() { + Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending()); + + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> switch (propertyName) { + case "name" -> Optional.of(nameProp); + default -> Optional.empty(); + }); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).asc(nameProp); + } + + @Test + public void testOrderBy2() { + Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); + + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + PropertyMetamodel ageProp = mock(PropertyMetamodel.class); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> switch (propertyName) { + case "name" -> Optional.of(nameProp); + case "age" -> Optional.of(ageProp); + default -> Optional.empty(); + }); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).desc(nameProp); + verify(orderByNameDeclaration, times(1)).asc(ageProp); + } + + @Test + public void testOrderByWhenNonSort() { + Pageable pageable = PageRequest.of(0, 10); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> Optional.empty()); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verifyNoMoreInteractions(orderByNameDeclaration); + } + + @Test + public void testOrderByWhenNonSortAndSetDefault() { + Pageable pageable = PageRequest.of(0, 10); + + PropertyMetamodel idProp = mock(PropertyMetamodel.class); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> Optional.empty(), + t -> t.asc(idProp)); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).asc(idProp); + } + + @Test + public void testOrderBySingleEntity() { + Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); + + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + when(nameProp.getName()).thenReturn("name"); + PropertyMetamodel ageProp = mock(PropertyMetamodel.class); + when(ageProp.getName()).thenReturn("age"); + EntityMetamodel entity = mock(EntityMetamodel.class); + when(entity.allPropertyMetamodels()).thenReturn(List.of(nameProp, ageProp)); + + Consumer consumer = PageablesForCriteria.orderBySingleEntity(pageable, entity); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).desc(nameProp); + verify(orderByNameDeclaration, times(1)).asc(ageProp); + } +} From 18b1a4d07580293824fe7509744333bb300301f7 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Thu, 27 Mar 2025 22:02:06 +0900 Subject: [PATCH 02/20] Apply format --- .../doma/boot/PageablesForCriteria.java | 308 +++++++++--------- .../doma/boot/PropertyMetamodelResolver.java | 42 +-- .../doma/boot/PageablesForCriteriaTest.java | 287 ++++++++-------- 3 files changed, 321 insertions(+), 316 deletions(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java index 043c1ff..b28d458 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java @@ -1,153 +1,155 @@ -package org.seasar.doma.boot; - -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; -import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; -import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; -import org.seasar.doma.jdbc.criteria.statement.EntityQueryable; -import org.springframework.data.domain.Pageable; - -/** - * Converts Utilities for {@link Pageable} to be used with Doma Criteria. - * - * @author mazeneko - */ -public class PageablesForCriteria { - /** - * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} - * - * @param pageable {@link Pageable} object to convert - * @return the limit. - * if {@link Pageable#isUnpaged()} is {@code true} then null. - */ - public static Integer limit(Pageable pageable) { - return pageable.isUnpaged() ? null : pageable.getPageSize(); - } - - /** - * Converts {@link Pageable} to {@link EntityQueryable#offset(Integer)} - * - * @param pageable {@link Pageable} object to convert - * @return the offset. - * if {@link Pageable#isUnpaged()} is {@code true} then null. - */ - public static Integer offset(Pageable pageable) { - return pageable.isUnpaged() ? null : Math.multiplyExact(pageable.getPageNumber(), pageable.getPageSize()); - } - - /** - * Creates an {@link OrderByNameDeclaration} consumer for a single entity based - * on the {@link Pageable}'s sort information. - *

- * This method resolves property names for ordering within the specified entity - * using its metamodel, and generates - * a consumer that can be used to apply ascending/descending orders. - *

- * If the {@link Pageable} is unsorted, no ordering is applied. - * - * @param pageable the {@link Pageable} containing sorting information - * @param entityMetamodel the {@link EntityMetamodel} corresponding to the - * target entity - * @return a consumer that configures ordering on the target entity - */ - public static Consumer orderBySingleEntity( - Pageable pageable, - EntityMetamodel entityMetamodel) { - return orderBySingleEntity(pageable, entityMetamodel, c -> { - }); - } - - /** - * Creates an {@link OrderByNameDeclaration} consumer for a single entity based - * on the {@link Pageable}'s sort information. - *

- * This method resolves property names for ordering within the specified entity - * using its metamodel, and generates - * a consumer that can be used to apply ascending/descending orders. - *

- * a default ordering via {@code defaultOrder} if the given {@link Pageable} is - * unsorted. - * - * @param pageable the {@link Pageable} containing sorting information - * @param entityMetamodel the {@link EntityMetamodel} corresponding to the - * target entity - * @param defaultOrder a consumer that applies default ordering if the - * {@link Pageable} is unsorted - * @return a consumer that configures ordering on the target entity - */ - public static Consumer orderBySingleEntity( - Pageable pageable, - EntityMetamodel entityMetamodel, - Consumer defaultOrder) { - final var nameToMetamodel = entityMetamodel - .allPropertyMetamodels() - .stream() - .collect(Collectors.toMap(PropertyMetamodel::getName, Function.identity())); - return orderBy( - pageable, - propertyName -> Optional.ofNullable(nameToMetamodel.get(propertyName)), - defaultOrder); - } - - /** - * Creates an {@link OrderByNameDeclaration} consumer based on the - * {@link Pageable}'s sort information - * using the provided {@link PropertyMetamodelResolver}. - *

- * If the {@link Pageable} is unsorted, no ordering is applied. - * - * @param pageable the {@link Pageable} containing sorting - * information - * @param propertyMetamodelResolver a resolver that maps property names to - * {@link PropertyMetamodel} - * @return a consumer that configures ordering based on the resolved - * {@link PropertyMetamodel} instances - */ - public static Consumer orderBy( - Pageable pageable, - PropertyMetamodelResolver propertyMetamodelResolver) { - return orderBy(pageable, propertyMetamodelResolver, c -> { - }); - } - - /** - * Creates an {@link OrderByNameDeclaration} consumer based on the - * {@link Pageable}'s sort information - * using the provided {@link PropertyMetamodelResolver}. - *

- * a default ordering via {@code defaultOrder} if the given {@link Pageable} is - * unsorted. - * - * @param pageable the {@link Pageable} containing sorting - * information - * @param propertyMetamodelResolver a resolver that maps property names to - * {@link PropertyMetamodel} - * @param defaultOrder a consumer that applies default ordering if - * the {@link Pageable} is unsorted - * @return a consumer that configures ordering based on the resolved - * {@link PropertyMetamodel} instances - */ - public static Consumer orderBy( - Pageable pageable, - PropertyMetamodelResolver propertyMetamodelResolver, - Consumer defaultOrder) { - if (pageable.getSort().isUnsorted()) { - return defaultOrder; - } - final var orderSpecifiers = pageable - .getSort() - .flatMap(order -> propertyMetamodelResolver - .resolve(order.getProperty()) - .>map(propertyMetamodel -> switch (order.getDirection()) { - case ASC -> c -> c.asc(propertyMetamodel); - case DESC -> c -> c.desc(propertyMetamodel); - }) - .stream()) - .toList(); - return c -> orderSpecifiers.forEach(orderSpecifier -> orderSpecifier.accept(c)); - } -} +package org.seasar.doma.boot; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; +import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; +import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; +import org.seasar.doma.jdbc.criteria.statement.EntityQueryable; +import org.springframework.data.domain.Pageable; + +/** + * Converts Utilities for {@link Pageable} to be used with Doma Criteria. + * + * @author mazeneko + */ +public class PageablesForCriteria { + /** + * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} + * + * @param pageable {@link Pageable} object to convert + * @return the limit. + * if {@link Pageable#isUnpaged()} is {@code true} then null. + */ + public static Integer limit(Pageable pageable) { + return pageable.isUnpaged() ? null : pageable.getPageSize(); + } + + /** + * Converts {@link Pageable} to {@link EntityQueryable#offset(Integer)} + * + * @param pageable {@link Pageable} object to convert + * @return the offset. + * if {@link Pageable#isUnpaged()} is {@code true} then null. + */ + public static Integer offset(Pageable pageable) { + return pageable.isUnpaged() ? null + : Math.multiplyExact(pageable.getPageNumber(), pageable.getPageSize()); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer for a single entity based + * on the {@link Pageable}'s sort information. + *

+ * This method resolves property names for ordering within the specified entity + * using its metamodel, and generates + * a consumer that can be used to apply ascending/descending orders. + *

+ * If the {@link Pageable} is unsorted, no ordering is applied. + * + * @param pageable the {@link Pageable} containing sorting information + * @param entityMetamodel the {@link EntityMetamodel} corresponding to the + * target entity + * @return a consumer that configures ordering on the target entity + */ + public static Consumer orderBySingleEntity( + Pageable pageable, + EntityMetamodel entityMetamodel) { + return orderBySingleEntity(pageable, entityMetamodel, c -> { + }); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer for a single entity based + * on the {@link Pageable}'s sort information. + *

+ * This method resolves property names for ordering within the specified entity + * using its metamodel, and generates + * a consumer that can be used to apply ascending/descending orders. + *

+ * a default ordering via {@code defaultOrder} if the given {@link Pageable} is + * unsorted. + * + * @param pageable the {@link Pageable} containing sorting information + * @param entityMetamodel the {@link EntityMetamodel} corresponding to the + * target entity + * @param defaultOrder a consumer that applies default ordering if the + * {@link Pageable} is unsorted + * @return a consumer that configures ordering on the target entity + */ + public static Consumer orderBySingleEntity( + Pageable pageable, + EntityMetamodel entityMetamodel, + Consumer defaultOrder) { + final var nameToMetamodel = entityMetamodel + .allPropertyMetamodels() + .stream() + .collect(Collectors.toMap(PropertyMetamodel::getName, Function.identity())); + return orderBy( + pageable, + propertyName -> Optional.ofNullable(nameToMetamodel.get(propertyName)), + defaultOrder); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer based on the + * {@link Pageable}'s sort information + * using the provided {@link PropertyMetamodelResolver}. + *

+ * If the {@link Pageable} is unsorted, no ordering is applied. + * + * @param pageable the {@link Pageable} containing sorting + * information + * @param propertyMetamodelResolver a resolver that maps property names to + * {@link PropertyMetamodel} + * @return a consumer that configures ordering based on the resolved + * {@link PropertyMetamodel} instances + */ + public static Consumer orderBy( + Pageable pageable, + PropertyMetamodelResolver propertyMetamodelResolver) { + return orderBy(pageable, propertyMetamodelResolver, c -> { + }); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer based on the + * {@link Pageable}'s sort information + * using the provided {@link PropertyMetamodelResolver}. + *

+ * a default ordering via {@code defaultOrder} if the given {@link Pageable} is + * unsorted. + * + * @param pageable the {@link Pageable} containing sorting + * information + * @param propertyMetamodelResolver a resolver that maps property names to + * {@link PropertyMetamodel} + * @param defaultOrder a consumer that applies default ordering if + * the {@link Pageable} is unsorted + * @return a consumer that configures ordering based on the resolved + * {@link PropertyMetamodel} instances + */ + public static Consumer orderBy( + Pageable pageable, + PropertyMetamodelResolver propertyMetamodelResolver, + Consumer defaultOrder) { + if (pageable.getSort().isUnsorted()) { + return defaultOrder; + } + final var orderSpecifiers = pageable + .getSort() + .flatMap(order -> propertyMetamodelResolver + .resolve(order.getProperty()) + .> map( + propertyMetamodel -> switch (order.getDirection()) { + case ASC -> c -> c.asc(propertyMetamodel); + case DESC -> c -> c.desc(propertyMetamodel); + }) + .stream()) + .toList(); + return c -> orderSpecifiers.forEach(orderSpecifier -> orderSpecifier.accept(c)); + } +} diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java index 1f0a7bc..0c06a6d 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PropertyMetamodelResolver.java @@ -1,21 +1,21 @@ -package org.seasar.doma.boot; - -import java.util.Optional; - -import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; - -/** - * A resolver that maps property names to {@link PropertyMetamodel} - */ -@FunctionalInterface -public interface PropertyMetamodelResolver { - /** - * Resolves the specified property name into a {@link PropertyMetamodel}. - * - * @param propertyName the name of the property to resolve - * @return an {@link Optional} containing the resolved {@link PropertyMetamodel} - * if found, - * or an empty {@link Optional} if the property name cannot be resolved - */ - Optional> resolve(String propertyName); -} +package org.seasar.doma.boot; + +import java.util.Optional; + +import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; + +/** + * A resolver that maps property names to {@link PropertyMetamodel} + */ +@FunctionalInterface +public interface PropertyMetamodelResolver { + /** + * Resolves the specified property name into a {@link PropertyMetamodel}. + * + * @param propertyName the name of the property to resolve + * @return an {@link Optional} containing the resolved {@link PropertyMetamodel} + * if found, + * or an empty {@link Optional} if the property name cannot be resolved + */ + Optional> resolve(String propertyName); +} diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java index 0eea863..9ad0f23 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java @@ -1,142 +1,145 @@ -package org.seasar.doma.boot; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; -import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; -import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; -import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -public class PageablesForCriteriaTest { - @Test - public void testOffsetAndLimit() { - Pageable pageable = PageRequest.of(0, 10); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); - assertThat(offset, is(0)); - assertThat(limit, is(10)); - } - - @Test - public void testOffsetAndLimit2() { - Pageable pageable = PageRequest.of(2, 10); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); - assertThat(offset, is(20)); - assertThat(limit, is(10)); - } - - @Test - public void testOffsetAndLimit3() { - Pageable pageable = PageRequest.of(2, 5); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); - assertThat(offset, is(10)); - assertThat(limit, is(5)); - } - - @Test - public void testOffsetAndLimit4() { - Pageable pageable = Pageable.unpaged(); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); - assertThat(offset, nullValue()); - assertThat(limit, nullValue()); - } - - @Test - public void testOrderBy() { - Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending()); - - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - - Consumer consumer = PageablesForCriteria.orderBy( - pageable, - propertyName -> switch (propertyName) { - case "name" -> Optional.of(nameProp); - default -> Optional.empty(); - }); - OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); - consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).asc(nameProp); - } - - @Test - public void testOrderBy2() { - Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); - - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - PropertyMetamodel ageProp = mock(PropertyMetamodel.class); - - Consumer consumer = PageablesForCriteria.orderBy( - pageable, - propertyName -> switch (propertyName) { - case "name" -> Optional.of(nameProp); - case "age" -> Optional.of(ageProp); - default -> Optional.empty(); - }); - OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); - consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).desc(nameProp); - verify(orderByNameDeclaration, times(1)).asc(ageProp); - } - - @Test - public void testOrderByWhenNonSort() { - Pageable pageable = PageRequest.of(0, 10); - - Consumer consumer = PageablesForCriteria.orderBy( - pageable, - propertyName -> Optional.empty()); - OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); - consumer.accept(orderByNameDeclaration); - verifyNoMoreInteractions(orderByNameDeclaration); - } - - @Test - public void testOrderByWhenNonSortAndSetDefault() { - Pageable pageable = PageRequest.of(0, 10); - - PropertyMetamodel idProp = mock(PropertyMetamodel.class); - - Consumer consumer = PageablesForCriteria.orderBy( - pageable, - propertyName -> Optional.empty(), - t -> t.asc(idProp)); - OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); - consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).asc(idProp); - } - - @Test - public void testOrderBySingleEntity() { - Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); - - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - when(nameProp.getName()).thenReturn("name"); - PropertyMetamodel ageProp = mock(PropertyMetamodel.class); - when(ageProp.getName()).thenReturn("age"); - EntityMetamodel entity = mock(EntityMetamodel.class); - when(entity.allPropertyMetamodels()).thenReturn(List.of(nameProp, ageProp)); - - Consumer consumer = PageablesForCriteria.orderBySingleEntity(pageable, entity); - OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); - consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).desc(nameProp); - verify(orderByNameDeclaration, times(1)).asc(ageProp); - } -} +package org.seasar.doma.boot; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; +import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; +import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class PageablesForCriteriaTest { + @Test + public void testOffsetAndLimit() { + Pageable pageable = PageRequest.of(0, 10); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, is(0)); + assertThat(limit, is(10)); + } + + @Test + public void testOffsetAndLimit2() { + Pageable pageable = PageRequest.of(2, 10); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, is(20)); + assertThat(limit, is(10)); + } + + @Test + public void testOffsetAndLimit3() { + Pageable pageable = PageRequest.of(2, 5); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, is(10)); + assertThat(limit, is(5)); + } + + @Test + public void testOffsetAndLimit4() { + Pageable pageable = Pageable.unpaged(); + Integer offset = PageablesForCriteria.offset(pageable); + Integer limit = PageablesForCriteria.limit(pageable); + assertThat(offset, nullValue()); + assertThat(limit, nullValue()); + } + + @Test + public void testOrderBy() { + Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending()); + + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> switch (propertyName) { + case "name" -> Optional.of(nameProp); + default -> Optional.empty(); + }); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).asc(nameProp); + } + + @Test + public void testOrderBy2() { + Pageable pageable = PageRequest.of(0, 10, + Sort.by("name").descending().and(Sort.by("age").ascending())); + + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + PropertyMetamodel ageProp = mock(PropertyMetamodel.class); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> switch (propertyName) { + case "name" -> Optional.of(nameProp); + case "age" -> Optional.of(ageProp); + default -> Optional.empty(); + }); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).desc(nameProp); + verify(orderByNameDeclaration, times(1)).asc(ageProp); + } + + @Test + public void testOrderByWhenNonSort() { + Pageable pageable = PageRequest.of(0, 10); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> Optional.empty()); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verifyNoMoreInteractions(orderByNameDeclaration); + } + + @Test + public void testOrderByWhenNonSortAndSetDefault() { + Pageable pageable = PageRequest.of(0, 10); + + PropertyMetamodel idProp = mock(PropertyMetamodel.class); + + Consumer consumer = PageablesForCriteria.orderBy( + pageable, + propertyName -> Optional.empty(), + t -> t.asc(idProp)); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).asc(idProp); + } + + @Test + public void testOrderBySingleEntity() { + Pageable pageable = PageRequest.of(0, 10, + Sort.by("name").descending().and(Sort.by("age").ascending())); + + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + when(nameProp.getName()).thenReturn("name"); + PropertyMetamodel ageProp = mock(PropertyMetamodel.class); + when(ageProp.getName()).thenReturn("age"); + EntityMetamodel entity = mock(EntityMetamodel.class); + when(entity.allPropertyMetamodels()).thenReturn(List.of(nameProp, ageProp)); + + Consumer consumer = PageablesForCriteria + .orderBySingleEntity(pageable, entity); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).desc(nameProp); + verify(orderByNameDeclaration, times(1)).asc(ageProp); + } +} From 5f6fe5c3f8f6c07124d325c125f0e5c492adcd4b Mon Sep 17 00:00:00 2001 From: mazeneko Date: Thu, 27 Mar 2025 22:06:33 +0900 Subject: [PATCH 03/20] Even if a sort order is specified, use the default order when there are no matching columns. --- .../main/java/org/seasar/doma/boot/PageablesForCriteria.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java index b28d458..127cf1f 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java @@ -150,6 +150,9 @@ public static Consumer orderBy( }) .stream()) .toList(); + if (orderSpecifiers.isEmpty()) { + return defaultOrder; + } return c -> orderSpecifiers.forEach(orderSpecifier -> orderSpecifier.accept(c)); } } From 547691dfa995bd5c2f5e8570f3b31eb3c17d4a64 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 14:37:43 +0900 Subject: [PATCH 04/20] Change class name --- ...riteria.java => UnifiedQueryPageable.java} | 2 +- ...est.java => UnifiedQueryPageableTest.java} | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename doma-spring-boot-core/src/main/java/org/seasar/doma/boot/{PageablesForCriteria.java => UnifiedQueryPageable.java} (99%) rename doma-spring-boot-core/src/test/java/org/seasar/doma/boot/{PageablesForCriteriaTest.java => UnifiedQueryPageableTest.java} (83%) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java similarity index 99% rename from doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java rename to doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index 127cf1f..ed3667e 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/PageablesForCriteria.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -16,7 +16,7 @@ * * @author mazeneko */ -public class PageablesForCriteria { +public class UnifiedQueryPageable { /** * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} * diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java similarity index 83% rename from doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java rename to doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index 9ad0f23..caa72eb 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/PageablesForCriteriaTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -21,12 +21,12 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -public class PageablesForCriteriaTest { +public class UnifiedQueryPageableTest { @Test public void testOffsetAndLimit() { Pageable pageable = PageRequest.of(0, 10); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); + Integer offset = UnifiedQueryPageable.offset(pageable); + Integer limit = UnifiedQueryPageable.limit(pageable); assertThat(offset, is(0)); assertThat(limit, is(10)); } @@ -34,8 +34,8 @@ public void testOffsetAndLimit() { @Test public void testOffsetAndLimit2() { Pageable pageable = PageRequest.of(2, 10); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); + Integer offset = UnifiedQueryPageable.offset(pageable); + Integer limit = UnifiedQueryPageable.limit(pageable); assertThat(offset, is(20)); assertThat(limit, is(10)); } @@ -43,8 +43,8 @@ public void testOffsetAndLimit2() { @Test public void testOffsetAndLimit3() { Pageable pageable = PageRequest.of(2, 5); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); + Integer offset = UnifiedQueryPageable.offset(pageable); + Integer limit = UnifiedQueryPageable.limit(pageable); assertThat(offset, is(10)); assertThat(limit, is(5)); } @@ -52,8 +52,8 @@ public void testOffsetAndLimit3() { @Test public void testOffsetAndLimit4() { Pageable pageable = Pageable.unpaged(); - Integer offset = PageablesForCriteria.offset(pageable); - Integer limit = PageablesForCriteria.limit(pageable); + Integer offset = UnifiedQueryPageable.offset(pageable); + Integer limit = UnifiedQueryPageable.limit(pageable); assertThat(offset, nullValue()); assertThat(limit, nullValue()); } @@ -64,7 +64,7 @@ public void testOrderBy() { PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - Consumer consumer = PageablesForCriteria.orderBy( + Consumer consumer = UnifiedQueryPageable.orderBy( pageable, propertyName -> switch (propertyName) { case "name" -> Optional.of(nameProp); @@ -83,7 +83,7 @@ public void testOrderBy2() { PropertyMetamodel nameProp = mock(PropertyMetamodel.class); PropertyMetamodel ageProp = mock(PropertyMetamodel.class); - Consumer consumer = PageablesForCriteria.orderBy( + Consumer consumer = UnifiedQueryPageable.orderBy( pageable, propertyName -> switch (propertyName) { case "name" -> Optional.of(nameProp); @@ -100,7 +100,7 @@ public void testOrderBy2() { public void testOrderByWhenNonSort() { Pageable pageable = PageRequest.of(0, 10); - Consumer consumer = PageablesForCriteria.orderBy( + Consumer consumer = UnifiedQueryPageable.orderBy( pageable, propertyName -> Optional.empty()); OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); @@ -114,7 +114,7 @@ public void testOrderByWhenNonSortAndSetDefault() { PropertyMetamodel idProp = mock(PropertyMetamodel.class); - Consumer consumer = PageablesForCriteria.orderBy( + Consumer consumer = UnifiedQueryPageable.orderBy( pageable, propertyName -> Optional.empty(), t -> t.asc(idProp)); @@ -135,7 +135,7 @@ public void testOrderBySingleEntity() { EntityMetamodel entity = mock(EntityMetamodel.class); when(entity.allPropertyMetamodels()).thenReturn(List.of(nameProp, ageProp)); - Consumer consumer = PageablesForCriteria + Consumer consumer = UnifiedQueryPageable .orderBySingleEntity(pageable, entity); OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); From 9f9d9b8299431f6d357aaf0a6439a703bebdfaf4 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 14:43:24 +0900 Subject: [PATCH 05/20] Create fields --- .../doma/boot/UnifiedQueryPageable.java | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index ed3667e..56b5b53 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -12,11 +12,52 @@ import org.springframework.data.domain.Pageable; /** - * Converts Utilities for {@link Pageable} to be used with Doma Criteria. + * An adapter that integrates {@link Pageable} with Doma Criteria API. + *

+ * This class allows converting {@link Pageable} pagination and sort information + * into Doma Criteria API's limit, offset, and order-by specifications. + *

+ * Example usage: + *

{@code
+ * public Page getPage(Pageable pageable) {
+ *     final var task_ = new Task_();
+ *     final var p = UnifiedQueryPageable.from(pageable, task_);
+ *     final var content = this.queryDsl
+ *         .from(task_)
+ *         .offset(p.offset())
+ *         .limit(p.limit())
+ *         .orderBy(p.orderBy())
+ *         .fetch();
+ *     final var total = this.queryDsl
+ *         .from(task_)
+ *         .select(Expressions.count())
+ *         .fetchOne();
+ *     return new PageImpl<>(content, pageable, total);
+ * }
* * @author mazeneko */ public class UnifiedQueryPageable { + private final Pageable pageable; + private final Optional sortConfig; + + /** + * A configuration holder for sort resolution. + */ + public record SortConfig( + /** a resolver that maps property names to {@link PropertyMetamodel} */ + PropertyMetamodelResolver propertyMetamodelResolver, + /** a consumer that applies default ordering when no valid sort can be determined */ + Consumer defaultOrder) { + } + + private UnifiedQueryPageable( + Pageable pageable, + Optional sortConfig) { + this.pageable = pageable; + this.sortConfig = sortConfig; + } + /** * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} * From 121f407b316e19ed3a48a613faee7c8b1960973b Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 14:44:09 +0900 Subject: [PATCH 06/20] Create static Constructors --- .../doma/boot/UnifiedQueryPageable.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index 56b5b53..47d9cd5 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -58,6 +58,98 @@ private UnifiedQueryPageable( this.sortConfig = sortConfig; } + /** + * Creates a {@link UnifiedQueryPageable}, resolving sort properties based on the entity's property names. + * + * @param pageable {@link Pageable} object to convert + * @param sortTargetEntity the target entity whose properties are used for sorting + * @return the {@link UnifiedQueryPageable} + */ + public static UnifiedQueryPageable from( + Pageable pageable, + EntityMetamodel sortTargetEntity) { + return UnifiedQueryPageable.from(pageable, sortTargetEntity, c -> { + }); + } + + /** + * Creates a {@link UnifiedQueryPageable}, resolving sort properties based on the entity's property names. + * + * @param pageable {@link Pageable} object to convert + * @param sortTargetEntity the target entity whose properties are used for sorting + * @param defaultOrder a consumer that applies default ordering when no valid sort can be determined + * @return the {@link UnifiedQueryPageable} + */ + public static UnifiedQueryPageable from( + Pageable pageable, + EntityMetamodel sortTargetEntity, + Consumer defaultOrder) { + final var nameToMetamodel = sortTargetEntity + .allPropertyMetamodels() + .stream() + .collect(Collectors.toMap(PropertyMetamodel::getName, Function.identity())); + final var sortConfig = new SortConfig( + propertyName -> Optional.ofNullable(nameToMetamodel.get(propertyName)), + defaultOrder); + return new UnifiedQueryPageable( + pageable, + Optional.of(sortConfig)); + } + + /** + * Creates a {@link UnifiedQueryPageable} + * + * @param pageable {@link Pageable} object to convert + * @param sortConfig sort configuration + * @return the {@link UnifiedQueryPageable} + */ + public static UnifiedQueryPageable of(Pageable pageable, SortConfig sortConfig) { + return new UnifiedQueryPageable(pageable, Optional.of(sortConfig)); + } + + /** + * Creates a {@link UnifiedQueryPageable} + * + * @param pageable {@link Pageable} object to convert + * @param propertyMetamodelResolver a resolver that maps property names to {@link PropertyMetamodel} + * @return the {@link UnifiedQueryPageable} + */ + public static UnifiedQueryPageable of( + Pageable pageable, + PropertyMetamodelResolver propertyMetamodelResolver) { + return UnifiedQueryPageable.of(pageable, propertyMetamodelResolver, c -> { + }); + } + + /** + * Creates a {@link UnifiedQueryPageable} + * + * @param pageable {@link Pageable} object to convert + * @param propertyMetamodelResolver a resolver that maps property names to {@link PropertyMetamodel} + * @param defaultOrder a consumer that applies default ordering when no valid sort can be determined + * @return the {@link UnifiedQueryPageable} + */ + public static UnifiedQueryPageable of( + Pageable pageable, + PropertyMetamodelResolver propertyMetamodelResolver, + Consumer defaultOrder) { + final var sortConfig = new SortConfig( + propertyMetamodelResolver, + defaultOrder); + return new UnifiedQueryPageable(pageable, Optional.of(sortConfig)); + } + + /** + * Creates a {@link UnifiedQueryPageable} without sort configuration. + * Only limit and offset will be available. + * + * @param pageable {@link Pageable} object to convert + * @return the {@link UnifiedQueryPageable} without sort support + */ + public static UnifiedQueryPageable ofNonSort(Pageable pageable) { + return new UnifiedQueryPageable(pageable, Optional.empty()); + } + /** * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} * From c02a12d13b8d3dc4573d015727d970f4f09f640b Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 14:52:52 +0900 Subject: [PATCH 07/20] WIP: Remove orderBy variations --- .../doma/boot/UnifiedQueryPageable.java | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index 47d9cd5..a1dda27 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -173,81 +173,6 @@ public static Integer offset(Pageable pageable) { : Math.multiplyExact(pageable.getPageNumber(), pageable.getPageSize()); } - /** - * Creates an {@link OrderByNameDeclaration} consumer for a single entity based - * on the {@link Pageable}'s sort information. - *

- * This method resolves property names for ordering within the specified entity - * using its metamodel, and generates - * a consumer that can be used to apply ascending/descending orders. - *

- * If the {@link Pageable} is unsorted, no ordering is applied. - * - * @param pageable the {@link Pageable} containing sorting information - * @param entityMetamodel the {@link EntityMetamodel} corresponding to the - * target entity - * @return a consumer that configures ordering on the target entity - */ - public static Consumer orderBySingleEntity( - Pageable pageable, - EntityMetamodel entityMetamodel) { - return orderBySingleEntity(pageable, entityMetamodel, c -> { - }); - } - - /** - * Creates an {@link OrderByNameDeclaration} consumer for a single entity based - * on the {@link Pageable}'s sort information. - *

- * This method resolves property names for ordering within the specified entity - * using its metamodel, and generates - * a consumer that can be used to apply ascending/descending orders. - *

- * a default ordering via {@code defaultOrder} if the given {@link Pageable} is - * unsorted. - * - * @param pageable the {@link Pageable} containing sorting information - * @param entityMetamodel the {@link EntityMetamodel} corresponding to the - * target entity - * @param defaultOrder a consumer that applies default ordering if the - * {@link Pageable} is unsorted - * @return a consumer that configures ordering on the target entity - */ - public static Consumer orderBySingleEntity( - Pageable pageable, - EntityMetamodel entityMetamodel, - Consumer defaultOrder) { - final var nameToMetamodel = entityMetamodel - .allPropertyMetamodels() - .stream() - .collect(Collectors.toMap(PropertyMetamodel::getName, Function.identity())); - return orderBy( - pageable, - propertyName -> Optional.ofNullable(nameToMetamodel.get(propertyName)), - defaultOrder); - } - - /** - * Creates an {@link OrderByNameDeclaration} consumer based on the - * {@link Pageable}'s sort information - * using the provided {@link PropertyMetamodelResolver}. - *

- * If the {@link Pageable} is unsorted, no ordering is applied. - * - * @param pageable the {@link Pageable} containing sorting - * information - * @param propertyMetamodelResolver a resolver that maps property names to - * {@link PropertyMetamodel} - * @return a consumer that configures ordering based on the resolved - * {@link PropertyMetamodel} instances - */ - public static Consumer orderBy( - Pageable pageable, - PropertyMetamodelResolver propertyMetamodelResolver) { - return orderBy(pageable, propertyMetamodelResolver, c -> { - }); - } - /** * Creates an {@link OrderByNameDeclaration} consumer based on the * {@link Pageable}'s sort information From c8796d991bfd12f25b74d19146cfe34f34de4334 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 14:53:32 +0900 Subject: [PATCH 08/20] Recreate methods --- .../doma/boot/UnifiedQueryPageable.java | 40 ++++------ .../doma/boot/UnifiedQueryPageableTest.java | 73 +++++++++++++------ 2 files changed, 67 insertions(+), 46 deletions(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index a1dda27..cb444b4 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -153,53 +153,45 @@ public static UnifiedQueryPageable ofNonSort(Pageable pageable) { /** * Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)} * - * @param pageable {@link Pageable} object to convert * @return the limit. * if {@link Pageable#isUnpaged()} is {@code true} then null. */ - public static Integer limit(Pageable pageable) { + public Integer limit() { return pageable.isUnpaged() ? null : pageable.getPageSize(); } /** * Converts {@link Pageable} to {@link EntityQueryable#offset(Integer)} * - * @param pageable {@link Pageable} object to convert * @return the offset. * if {@link Pageable#isUnpaged()} is {@code true} then null. */ - public static Integer offset(Pageable pageable) { - return pageable.isUnpaged() ? null + public Integer offset() { + return pageable.isUnpaged() + ? null : Math.multiplyExact(pageable.getPageNumber(), pageable.getPageSize()); } /** * Creates an {@link OrderByNameDeclaration} consumer based on the - * {@link Pageable}'s sort information - * using the provided {@link PropertyMetamodelResolver}. + * {@link Pageable}'s sort information using the provided {@link PropertyMetamodelResolver}. *

- * a default ordering via {@code defaultOrder} if the given {@link Pageable} is - * unsorted. + * If the {@link Pageable} is unsorted or no matching {@link PropertyMetamodel} is found, + * a default ordering is applied. * - * @param pageable the {@link Pageable} containing sorting - * information - * @param propertyMetamodelResolver a resolver that maps property names to - * {@link PropertyMetamodel} - * @param defaultOrder a consumer that applies default ordering if - * the {@link Pageable} is unsorted - * @return a consumer that configures ordering based on the resolved - * {@link PropertyMetamodel} instances + * @return a consumer that configures ordering based on the resolved {@link PropertyMetamodel} instances + * @throws IllegalStateException if the sort configuration is missing */ - public static Consumer orderBy( - Pageable pageable, - PropertyMetamodelResolver propertyMetamodelResolver, - Consumer defaultOrder) { + public Consumer orderBy() { + final var sortConfig = this.sortConfig.orElseThrow( + () -> new IllegalStateException("Sort configuration is required but not present.")); if (pageable.getSort().isUnsorted()) { - return defaultOrder; + return sortConfig.defaultOrder(); } final var orderSpecifiers = pageable .getSort() - .flatMap(order -> propertyMetamodelResolver + .flatMap(order -> sortConfig + .propertyMetamodelResolver() .resolve(order.getProperty()) .> map( propertyMetamodel -> switch (order.getDirection()) { @@ -209,7 +201,7 @@ public static Consumer orderBy( .stream()) .toList(); if (orderSpecifiers.isEmpty()) { - return defaultOrder; + return sortConfig.defaultOrder(); } return c -> orderSpecifiers.forEach(orderSpecifier -> orderSpecifier.accept(c)); } diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index caa72eb..a4e6e10 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -1,5 +1,6 @@ package org.seasar.doma.boot; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -25,8 +26,11 @@ public class UnifiedQueryPageableTest { @Test public void testOffsetAndLimit() { Pageable pageable = PageRequest.of(0, 10); - Integer offset = UnifiedQueryPageable.offset(pageable); - Integer limit = UnifiedQueryPageable.limit(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + + Integer offset = p.offset(); + Integer limit = p.limit(); + assertThat(offset, is(0)); assertThat(limit, is(10)); } @@ -34,8 +38,11 @@ public void testOffsetAndLimit() { @Test public void testOffsetAndLimit2() { Pageable pageable = PageRequest.of(2, 10); - Integer offset = UnifiedQueryPageable.offset(pageable); - Integer limit = UnifiedQueryPageable.limit(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + + Integer offset = p.offset(); + Integer limit = p.limit(); + assertThat(offset, is(20)); assertThat(limit, is(10)); } @@ -43,8 +50,11 @@ public void testOffsetAndLimit2() { @Test public void testOffsetAndLimit3() { Pageable pageable = PageRequest.of(2, 5); - Integer offset = UnifiedQueryPageable.offset(pageable); - Integer limit = UnifiedQueryPageable.limit(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + + Integer offset = p.offset(); + Integer limit = p.limit(); + assertThat(offset, is(10)); assertThat(limit, is(5)); } @@ -52,8 +62,11 @@ public void testOffsetAndLimit3() { @Test public void testOffsetAndLimit4() { Pageable pageable = Pageable.unpaged(); - Integer offset = UnifiedQueryPageable.offset(pageable); - Integer limit = UnifiedQueryPageable.limit(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + + Integer offset = p.offset(); + Integer limit = p.limit(); + assertThat(offset, nullValue()); assertThat(limit, nullValue()); } @@ -61,15 +74,16 @@ public void testOffsetAndLimit4() { @Test public void testOrderBy() { Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending()); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - - Consumer consumer = UnifiedQueryPageable.orderBy( + UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> switch (propertyName) { case "name" -> Optional.of(nameProp); default -> Optional.empty(); }); + + Consumer consumer = p.orderBy(); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); verify(orderByNameDeclaration, times(1)).asc(nameProp); @@ -79,17 +93,18 @@ public void testOrderBy() { public void testOrderBy2() { Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); PropertyMetamodel ageProp = mock(PropertyMetamodel.class); - - Consumer consumer = UnifiedQueryPageable.orderBy( + UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> switch (propertyName) { case "name" -> Optional.of(nameProp); case "age" -> Optional.of(ageProp); default -> Optional.empty(); }); + + Consumer consumer = p.orderBy(); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); verify(orderByNameDeclaration, times(1)).desc(nameProp); @@ -99,10 +114,12 @@ public void testOrderBy2() { @Test public void testOrderByWhenNonSort() { Pageable pageable = PageRequest.of(0, 10); - - Consumer consumer = UnifiedQueryPageable.orderBy( + UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> Optional.empty()); + + Consumer consumer = p.orderBy(); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); verifyNoMoreInteractions(orderByNameDeclaration); @@ -111,13 +128,14 @@ public void testOrderByWhenNonSort() { @Test public void testOrderByWhenNonSortAndSetDefault() { Pageable pageable = PageRequest.of(0, 10); - PropertyMetamodel idProp = mock(PropertyMetamodel.class); - - Consumer consumer = UnifiedQueryPageable.orderBy( + UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> Optional.empty(), t -> t.asc(idProp)); + + Consumer consumer = p.orderBy(); + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); verify(orderByNameDeclaration, times(1)).asc(idProp); @@ -127,19 +145,30 @@ public void testOrderByWhenNonSortAndSetDefault() { public void testOrderBySingleEntity() { Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); when(nameProp.getName()).thenReturn("name"); PropertyMetamodel ageProp = mock(PropertyMetamodel.class); when(ageProp.getName()).thenReturn("age"); EntityMetamodel entity = mock(EntityMetamodel.class); when(entity.allPropertyMetamodels()).thenReturn(List.of(nameProp, ageProp)); + UnifiedQueryPageable p = UnifiedQueryPageable.from(pageable, entity); + + Consumer consumer = p.orderBy(); - Consumer consumer = UnifiedQueryPageable - .orderBySingleEntity(pageable, entity); OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); verify(orderByNameDeclaration, times(1)).desc(nameProp); verify(orderByNameDeclaration, times(1)).asc(ageProp); } + + @Test + public void testOrderByWhenMissingSortConfig() { + Pageable pageable = PageRequest.of(0, 10, + Sort.by("name").descending().and(Sort.by("age").ascending())); + UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + + assertThatThrownBy(() -> p.orderBy()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Sort configuration is required but not present."); + } } From 152ad5fbe45246a92d66878b1faae56e1031485a Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 15:44:27 +0900 Subject: [PATCH 09/20] Create getters --- .../java/org/seasar/doma/boot/UnifiedQueryPageable.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index cb444b4..122ec7d 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -58,6 +58,14 @@ private UnifiedQueryPageable( this.sortConfig = sortConfig; } + public Pageable getPageable() { + return pageable; + } + + public Optional getSortConfig() { + return sortConfig; + } + /** * Creates a {@link UnifiedQueryPageable}, resolving sort properties based on the entity's property names. * From 74ed41c1907f89ab908bee8bf803b188f5f37087 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 20:22:06 +0900 Subject: [PATCH 10/20] Make the sort configuration required instead of optional --- .../doma/boot/UnifiedQueryPageable.java | 25 +++++-------------- .../doma/boot/UnifiedQueryPageableTest.java | 20 +++------------ 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index 122ec7d..dab5407 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -39,7 +39,7 @@ */ public class UnifiedQueryPageable { private final Pageable pageable; - private final Optional sortConfig; + private final SortConfig sortConfig; /** * A configuration holder for sort resolution. @@ -53,7 +53,7 @@ public record SortConfig( private UnifiedQueryPageable( Pageable pageable, - Optional sortConfig) { + SortConfig sortConfig) { this.pageable = pageable; this.sortConfig = sortConfig; } @@ -62,7 +62,7 @@ public Pageable getPageable() { return pageable; } - public Optional getSortConfig() { + public SortConfig getSortConfig() { return sortConfig; } @@ -101,7 +101,7 @@ public static UnifiedQueryPageable from( defaultOrder); return new UnifiedQueryPageable( pageable, - Optional.of(sortConfig)); + sortConfig); } /** @@ -112,7 +112,7 @@ public static UnifiedQueryPageable from( * @return the {@link UnifiedQueryPageable} */ public static UnifiedQueryPageable of(Pageable pageable, SortConfig sortConfig) { - return new UnifiedQueryPageable(pageable, Optional.of(sortConfig)); + return new UnifiedQueryPageable(pageable, sortConfig); } /** @@ -144,18 +144,7 @@ public static UnifiedQueryPageable of( final var sortConfig = new SortConfig( propertyMetamodelResolver, defaultOrder); - return new UnifiedQueryPageable(pageable, Optional.of(sortConfig)); - } - - /** - * Creates a {@link UnifiedQueryPageable} without sort configuration. - * Only limit and offset will be available. - * - * @param pageable {@link Pageable} object to convert - * @return the {@link UnifiedQueryPageable} without sort support - */ - public static UnifiedQueryPageable ofNonSort(Pageable pageable) { - return new UnifiedQueryPageable(pageable, Optional.empty()); + return new UnifiedQueryPageable(pageable, sortConfig); } /** @@ -191,8 +180,6 @@ public Integer offset() { * @throws IllegalStateException if the sort configuration is missing */ public Consumer orderBy() { - final var sortConfig = this.sortConfig.orElseThrow( - () -> new IllegalStateException("Sort configuration is required but not present.")); if (pageable.getSort().isUnsorted()) { return sortConfig.defaultOrder(); } diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index a4e6e10..d131ea4 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -1,6 +1,5 @@ package org.seasar.doma.boot; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -26,7 +25,7 @@ public class UnifiedQueryPageableTest { @Test public void testOffsetAndLimit() { Pageable pageable = PageRequest.of(0, 10); - UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); Integer offset = p.offset(); Integer limit = p.limit(); @@ -38,7 +37,7 @@ public void testOffsetAndLimit() { @Test public void testOffsetAndLimit2() { Pageable pageable = PageRequest.of(2, 10); - UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); Integer offset = p.offset(); Integer limit = p.limit(); @@ -50,7 +49,7 @@ public void testOffsetAndLimit2() { @Test public void testOffsetAndLimit3() { Pageable pageable = PageRequest.of(2, 5); - UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); Integer offset = p.offset(); Integer limit = p.limit(); @@ -62,7 +61,7 @@ public void testOffsetAndLimit3() { @Test public void testOffsetAndLimit4() { Pageable pageable = Pageable.unpaged(); - UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); + UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); Integer offset = p.offset(); Integer limit = p.limit(); @@ -160,15 +159,4 @@ public void testOrderBySingleEntity() { verify(orderByNameDeclaration, times(1)).desc(nameProp); verify(orderByNameDeclaration, times(1)).asc(ageProp); } - - @Test - public void testOrderByWhenMissingSortConfig() { - Pageable pageable = PageRequest.of(0, 10, - Sort.by("name").descending().and(Sort.by("age").ascending())); - UnifiedQueryPageable p = UnifiedQueryPageable.ofNonSort(pageable); - - assertThatThrownBy(() -> p.orderBy()) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Sort configuration is required but not present."); - } } From e7a08ffd32ff0a0d359dcff75575cc984990e428 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 20:26:29 +0900 Subject: [PATCH 11/20] Fix test name --- .../java/org/seasar/doma/boot/UnifiedQueryPageableTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index d131ea4..9bf46d1 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -59,7 +59,7 @@ public void testOffsetAndLimit3() { } @Test - public void testOffsetAndLimit4() { + public void testOffsetAndLimitWhenUnpaged() { Pageable pageable = Pageable.unpaged(); UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); From 9c0694e811c70cadd5568e017b964530ad6a17e7 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 20:54:44 +0900 Subject: [PATCH 12/20] Use @ParameterizedTest --- .../doma/boot/UnifiedQueryPageableTest.java | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index 9bf46d1..38a0bd8 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -14,6 +14,8 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; @@ -22,40 +24,22 @@ import org.springframework.data.domain.Sort; public class UnifiedQueryPageableTest { - @Test - public void testOffsetAndLimit() { - Pageable pageable = PageRequest.of(0, 10); - UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); - - Integer offset = p.offset(); - Integer limit = p.limit(); - - assertThat(offset, is(0)); - assertThat(limit, is(10)); - } - - @Test - public void testOffsetAndLimit2() { - Pageable pageable = PageRequest.of(2, 10); - UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); - - Integer offset = p.offset(); - Integer limit = p.limit(); - - assertThat(offset, is(20)); - assertThat(limit, is(10)); - } - - @Test - public void testOffsetAndLimit3() { - Pageable pageable = PageRequest.of(2, 5); + @ParameterizedTest + @CsvSource(value = { + "0 | 10 | 0 | 10", + "2 | 10 | 20 | 10", + "2 | 5 | 10 | 5", + }, delimiter = '|') + public void testOffsetAndLimit( + int pageNumber, int pageSize, int expectedOffset, int expectedLimit) { + Pageable pageable = PageRequest.of(pageNumber, pageSize); UnifiedQueryPageable p = UnifiedQueryPageable.of(pageable, c -> Optional.empty()); Integer offset = p.offset(); Integer limit = p.limit(); - assertThat(offset, is(10)); - assertThat(limit, is(5)); + assertThat(offset, is(expectedOffset)); + assertThat(limit, is(expectedLimit)); } @Test From a7159f42858d8978741b473227f251740fc7d4d2 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 23:27:36 +0900 Subject: [PATCH 13/20] Removed an unused @throws tag from the Javadoc of orderBy --- .../src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java | 1 - 1 file changed, 1 deletion(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index dab5407..213a37d 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -177,7 +177,6 @@ public Integer offset() { * a default ordering is applied. * * @return a consumer that configures ordering based on the resolved {@link PropertyMetamodel} instances - * @throws IllegalStateException if the sort configuration is missing */ public Consumer orderBy() { if (pageable.getSort().isUnsorted()) { From 275499ecacc392d5731f7988a6fad7d9645ed4fe Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sat, 29 Mar 2025 23:46:25 +0900 Subject: [PATCH 14/20] Injectable a handler for missing sort properties in orderBy --- .../doma/boot/UnifiedQueryPageable.java | 50 +++++++++++++++---- .../doma/boot/UnifiedQueryPageableTest.java | 22 ++++++++ 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index 213a37d..d3a7c4f 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -1,5 +1,7 @@ package org.seasar.doma.boot; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -179,21 +181,51 @@ public Integer offset() { * @return a consumer that configures ordering based on the resolved {@link PropertyMetamodel} instances */ public Consumer orderBy() { + return orderBy(missingProperties -> { + }); + } + + /** + * Creates an {@link OrderByNameDeclaration} consumer based on the + * {@link Pageable}'s sort information using the provided {@link PropertyMetamodelResolver}. + *

+ * If the {@link Pageable} is unsorted, or if no {@link PropertyMetamodel} can be resolved + * for a given sort property, a default ordering is applied. + *

+ * The provided {@code handleMissingProperties} consumer is called with a list of + * property names that could not be resolved. This can be used to throw an exception, + * log a warning, or handle the situation in a custom way. + * + * @param handleMissingProperties a callback that handles property names which could not be resolved + * @return a consumer that configures ordering based on the resolved {@link PropertyMetamodel} instances + */ + public Consumer orderBy( + Consumer> handleMissingProperties) { if (pageable.getSort().isUnsorted()) { return sortConfig.defaultOrder(); } + final var missingProperties = new ArrayList(); final var orderSpecifiers = pageable .getSort() - .flatMap(order -> sortConfig - .propertyMetamodelResolver() - .resolve(order.getProperty()) - .> map( - propertyMetamodel -> switch (order.getDirection()) { - case ASC -> c -> c.asc(propertyMetamodel); - case DESC -> c -> c.desc(propertyMetamodel); - }) - .stream()) + .flatMap(order -> { + final var resolvedProperty = sortConfig + .propertyMetamodelResolver() + .resolve(order.getProperty()); + if (resolvedProperty.isEmpty()) { + missingProperties.add(order.getProperty()); + } + return resolvedProperty + .> map( + propertyMetamodel -> switch (order.getDirection()) { + case ASC -> c -> c.asc(propertyMetamodel); + case DESC -> c -> c.desc(propertyMetamodel); + }) + .stream(); + }) .toList(); + if (!missingProperties.isEmpty()) { + handleMissingProperties.accept(missingProperties); + } if (orderSpecifiers.isEmpty()) { return sortConfig.defaultOrder(); } diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index 38a0bd8..44acbd3 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -1,5 +1,6 @@ package org.seasar.doma.boot; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -12,6 +13,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -143,4 +145,24 @@ public void testOrderBySingleEntity() { verify(orderByNameDeclaration, times(1)).desc(nameProp); verify(orderByNameDeclaration, times(1)).asc(ageProp); } + + @Test + public void testOrderByWhenMissingPropertiesHandle() { + Pageable pageable = PageRequest.of(0, 10, + Sort.by("dog").and(Sort.by("name")).and(Sort.by("cat"))); + PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + UnifiedQueryPageable p = UnifiedQueryPageable.of( + pageable, + propertyName -> switch (propertyName) { + case "name" -> Optional.of(nameProp); + default -> Optional.empty(); + }); + + assertThatThrownBy(() -> p.orderBy(missingProperties -> { + throw new IllegalArgumentException( + missingProperties.stream().collect(Collectors.joining(","))); + })) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("dog,cat"); + } } From 18c5122ce6073907eb53447114bcf932946293c3 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sun, 30 Mar 2025 00:38:01 +0900 Subject: [PATCH 15/20] Use the actual metamodel instead of a mock in the test --- doma-spring-boot-core/pom.xml | 13 +++++ .../doma/boot/UnifiedQueryPageableTest.java | 54 ++++++++----------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/doma-spring-boot-core/pom.xml b/doma-spring-boot-core/pom.xml index b0577d6..9c0d738 100644 --- a/doma-spring-boot-core/pom.xml +++ b/doma-spring-boot-core/pom.xml @@ -79,6 +79,19 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.seasar.doma + doma-processor + ${doma.version} + + + + diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index 44acbd3..991dbf0 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -8,9 +8,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -18,9 +16,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.seasar.doma.Entity; +import org.seasar.doma.Id; +import org.seasar.doma.Metamodel; import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration; -import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel; -import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -59,11 +58,11 @@ public void testOffsetAndLimitWhenUnpaged() { @Test public void testOrderBy() { Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending()); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); + Person_ entity = new Person_(); UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> switch (propertyName) { - case "name" -> Optional.of(nameProp); + case "name" -> Optional.of(entity.name); default -> Optional.empty(); }); @@ -71,20 +70,19 @@ public void testOrderBy() { OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).asc(nameProp); + verify(orderByNameDeclaration, times(1)).asc(entity.name); } @Test public void testOrderBy2() { Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - PropertyMetamodel ageProp = mock(PropertyMetamodel.class); + Person_ entity = new Person_(); UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> switch (propertyName) { - case "name" -> Optional.of(nameProp); - case "age" -> Optional.of(ageProp); + case "name" -> Optional.of(entity.name); + case "age" -> Optional.of(entity.age); default -> Optional.empty(); }); @@ -92,8 +90,8 @@ public void testOrderBy2() { OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).desc(nameProp); - verify(orderByNameDeclaration, times(1)).asc(ageProp); + verify(orderByNameDeclaration, times(1)).desc(entity.name); + verify(orderByNameDeclaration, times(1)).asc(entity.age); } @Test @@ -113,50 +111,40 @@ public void testOrderByWhenNonSort() { @Test public void testOrderByWhenNonSortAndSetDefault() { Pageable pageable = PageRequest.of(0, 10); - PropertyMetamodel idProp = mock(PropertyMetamodel.class); + Person_ entity = new Person_(); UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> Optional.empty(), - t -> t.asc(idProp)); + t -> t.asc(entity.id)); Consumer consumer = p.orderBy(); OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).asc(idProp); + verify(orderByNameDeclaration, times(1)).asc(entity.id); } @Test public void testOrderBySingleEntity() { Pageable pageable = PageRequest.of(0, 10, Sort.by("name").descending().and(Sort.by("age").ascending())); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - when(nameProp.getName()).thenReturn("name"); - PropertyMetamodel ageProp = mock(PropertyMetamodel.class); - when(ageProp.getName()).thenReturn("age"); - EntityMetamodel entity = mock(EntityMetamodel.class); - when(entity.allPropertyMetamodels()).thenReturn(List.of(nameProp, ageProp)); + Person_ entity = new Person_(); UnifiedQueryPageable p = UnifiedQueryPageable.from(pageable, entity); Consumer consumer = p.orderBy(); OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).desc(nameProp); - verify(orderByNameDeclaration, times(1)).asc(ageProp); + verify(orderByNameDeclaration, times(1)).desc(entity.name); + verify(orderByNameDeclaration, times(1)).asc(entity.age); } @Test public void testOrderByWhenMissingPropertiesHandle() { Pageable pageable = PageRequest.of(0, 10, Sort.by("dog").and(Sort.by("name")).and(Sort.by("cat"))); - PropertyMetamodel nameProp = mock(PropertyMetamodel.class); - UnifiedQueryPageable p = UnifiedQueryPageable.of( - pageable, - propertyName -> switch (propertyName) { - case "name" -> Optional.of(nameProp); - default -> Optional.empty(); - }); + Person_ entity = new Person_(); + UnifiedQueryPageable p = UnifiedQueryPageable.from(pageable, entity); assertThatThrownBy(() -> p.orderBy(missingProperties -> { throw new IllegalArgumentException( @@ -166,3 +154,7 @@ public void testOrderByWhenMissingPropertiesHandle() { .hasMessage("dog,cat"); } } + +@Entity(metamodel = @Metamodel) +record Person(@Id String id, String name, Integer age) { +} \ No newline at end of file From dd97640557bed41432cd62a55ee8c6fec051eb9a Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sun, 30 Mar 2025 00:38:44 +0900 Subject: [PATCH 16/20] Fixed an issue where the call order of sort specifications was not being tested --- .../seasar/doma/boot/UnifiedQueryPageableTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index 991dbf0..616f9a3 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -90,8 +91,9 @@ public void testOrderBy2() { OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).desc(entity.name); - verify(orderByNameDeclaration, times(1)).asc(entity.age); + var sortOrderVerifier = inOrder(orderByNameDeclaration); + sortOrderVerifier.verify(orderByNameDeclaration, times(1)).desc(entity.name); + sortOrderVerifier.verify(orderByNameDeclaration, times(1)).asc(entity.age); } @Test @@ -135,8 +137,9 @@ public void testOrderBySingleEntity() { OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).desc(entity.name); - verify(orderByNameDeclaration, times(1)).asc(entity.age); + var sortOrderVerifier = inOrder(orderByNameDeclaration); + sortOrderVerifier.verify(orderByNameDeclaration, times(1)).desc(entity.name); + sortOrderVerifier.verify(orderByNameDeclaration, times(1)).asc(entity.age); } @Test From 3d37c73a891890d33cfa9c533af674db242aec64 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sun, 30 Mar 2025 13:16:50 +0900 Subject: [PATCH 17/20] To better testOrderByWhenNonSort --- .../org/seasar/doma/boot/UnifiedQueryPageableTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index 616f9a3..b37e301 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -114,16 +115,15 @@ public void testOrderByWhenNonSort() { public void testOrderByWhenNonSortAndSetDefault() { Pageable pageable = PageRequest.of(0, 10); Person_ entity = new Person_(); + Consumer defaultOrder = c -> c.asc(entity.id); UnifiedQueryPageable p = UnifiedQueryPageable.of( pageable, propertyName -> Optional.empty(), - t -> t.asc(entity.id)); + defaultOrder); Consumer consumer = p.orderBy(); - OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); - consumer.accept(orderByNameDeclaration); - verify(orderByNameDeclaration, times(1)).asc(entity.id); + assertThat(consumer, sameInstance(defaultOrder)); } @Test From 0522fdf434f5124fa968f6180aa60ee4603a6176 Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sun, 30 Mar 2025 13:32:04 +0900 Subject: [PATCH 18/20] Add missing tests for `missing properties` --- .../doma/boot/UnifiedQueryPageableTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java index b37e301..dacaef0 100644 --- a/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java +++ b/doma-spring-boot-core/src/test/java/org/seasar/doma/boot/UnifiedQueryPageableTest.java @@ -142,6 +142,33 @@ public void testOrderBySingleEntity() { sortOrderVerifier.verify(orderByNameDeclaration, times(1)).asc(entity.age); } + @Test + public void testOrderByWhenMissingProperties() { + Pageable pageable = PageRequest.of(0, 10, + Sort.by("dog").and(Sort.by("name")).and(Sort.by("cat"))); + Person_ entity = new Person_(); + UnifiedQueryPageable p = UnifiedQueryPageable.from(pageable, entity); + + Consumer consumer = p.orderBy(); + + OrderByNameDeclaration orderByNameDeclaration = mock(OrderByNameDeclaration.class); + consumer.accept(orderByNameDeclaration); + verify(orderByNameDeclaration, times(1)).asc(entity.name); + } + + @Test + public void testOrderByWhenMissingAllProperties() { + Pageable pageable = PageRequest.of(0, 10, + Sort.by("dog").and(Sort.by("cat"))); + Person_ entity = new Person_(); + Consumer defaultOrder = c -> c.desc(entity.age); + UnifiedQueryPageable p = UnifiedQueryPageable.from(pageable, entity, defaultOrder); + + Consumer consumer = p.orderBy(); + + assertThat(consumer, sameInstance(defaultOrder)); + } + @Test public void testOrderByWhenMissingPropertiesHandle() { Pageable pageable = PageRequest.of(0, 10, From cb4e97eb8715fe9b76068121c9a9ab1db7bd8b8b Mon Sep 17 00:00:00 2001 From: mazeneko Date: Sun, 30 Mar 2025 14:25:24 +0900 Subject: [PATCH 19/20] Remove stream side effects --- .../doma/boot/UnifiedQueryPageable.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index d3a7c4f..cb67de1 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -204,25 +204,20 @@ public Consumer orderBy( if (pageable.getSort().isUnsorted()) { return sortConfig.defaultOrder(); } + final var orderSpecifiers = new ArrayList>(); final var missingProperties = new ArrayList(); - final var orderSpecifiers = pageable - .getSort() - .flatMap(order -> { - final var resolvedProperty = sortConfig - .propertyMetamodelResolver() - .resolve(order.getProperty()); - if (resolvedProperty.isEmpty()) { - missingProperties.add(order.getProperty()); - } - return resolvedProperty - .> map( - propertyMetamodel -> switch (order.getDirection()) { + for (final var order : pageable.getSort()) { + sortConfig + .propertyMetamodelResolver() + .resolve(order.getProperty()) + .ifPresentOrElse( + propertyMetamodel -> orderSpecifiers.add( + switch (order.getDirection()) { case ASC -> c -> c.asc(propertyMetamodel); case DESC -> c -> c.desc(propertyMetamodel); - }) - .stream(); - }) - .toList(); + }), + () -> missingProperties.add(order.getProperty())); + } if (!missingProperties.isEmpty()) { handleMissingProperties.accept(missingProperties); } From 1ed99cc63cdb98313e0c7245b22e046f0bbd7916 Mon Sep 17 00:00:00 2001 From: mazeneko <46620955+mazeneko@users.noreply.github.com> Date: Sun, 30 Mar 2025 14:42:31 +0900 Subject: [PATCH 20/20] Fix missing closing bracket in Javadoc Co-authored-by: Uragami Taichi --- .../src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java | 1 + 1 file changed, 1 insertion(+) diff --git a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java index cb67de1..67882d7 100644 --- a/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java +++ b/doma-spring-boot-core/src/main/java/org/seasar/doma/boot/UnifiedQueryPageable.java @@ -35,6 +35,7 @@ * .select(Expressions.count()) * .fetchOne(); * return new PageImpl<>(content, pageable, total); + * } * } * * @author mazeneko