Skip to content

Commit bd44036

Browse files
committed
Since now getFetches returns an immutable Set it is not possible to just clear it so the queries need to be rebuilt, it doesn't seem to incure a performance penalty so for now I am just repeating the query creation
1 parent 1d29414 commit bd44036

File tree

5 files changed

+106
-76
lines changed

5 files changed

+106
-76
lines changed

nrich-registry/src/main/java/net/croz/nrich/registry/data/service/DefaultRegistryDataService.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import net.croz.nrich.registry.core.support.ManagedTypeWrapper;
2828
import net.croz.nrich.search.api.converter.StringToEntityPropertyMapConverter;
2929
import net.croz.nrich.search.api.model.SearchConfiguration;
30+
import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration;
3031
import net.croz.nrich.search.api.model.sort.SortDirection;
3132
import net.croz.nrich.search.api.model.sort.SortProperty;
3233
import net.croz.nrich.search.api.util.PageableUtil;
@@ -173,25 +174,30 @@ private <T, P> Page<P> registryListInternal(ListRegistryRequest request) {
173174

174175
Pageable pageable = PageableUtil.convertToPageable(request.getPageNumber(), request.getPageSize(), new SortProperty(idAttributeName, SortDirection.ASC), request.getSortPropertyList());
175176

177+
Map<String, Object> searchRequestMap = resolveSearchRequestMap(managedTypeWrapper, request, searchConfiguration.getSearchPropertyConfiguration());
178+
CriteriaQuery<P> query = queryBuilder.buildQuery(searchRequestMap, searchConfiguration, pageable.getSort());
179+
180+
TypedQuery<P> typedQuery = entityManager.createQuery(query);
181+
182+
typedQuery.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize());
183+
184+
return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(queryBuilder, searchRequestMap, searchConfiguration));
185+
}
186+
187+
private Map<String, Object> resolveSearchRequestMap(ManagedTypeWrapper managedTypeWrapper, ListRegistryRequest request, SearchPropertyConfiguration searchPropertyConfiguration) {
176188
Map<String, Object> searchRequestMap = Collections.emptyMap();
177189
if (request.getSearchParameter() != null) {
178190
searchRequestMap = stringToEntityPropertyMapConverter.convert(
179191
request.getSearchParameter().getQuery(), request.getSearchParameter().getPropertyNameList(), managedTypeWrapper.getIdentifiableType(),
180-
searchConfiguration.getSearchPropertyConfiguration()
192+
searchPropertyConfiguration
181193
);
182194
}
183195

184-
CriteriaQuery<P> query = queryBuilder.buildQuery(searchRequestMap, searchConfiguration, pageable.getSort());
185-
186-
TypedQuery<P> typedQuery = entityManager.createQuery(query);
187-
188-
typedQuery.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize());
189-
190-
return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(queryBuilder, query));
196+
return searchRequestMap;
191197
}
192198

193-
private long executeCountQuery(JpaQueryBuilder<?> queryBuilder, CriteriaQuery<?> query) {
194-
CriteriaQuery<Long> countQuery = queryBuilder.convertToCountQuery(query);
199+
private <T, P> long executeCountQuery(JpaQueryBuilder<T> queryBuilder, Map<String, Object> searchRequestMap, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
200+
CriteriaQuery<Long> countQuery = queryBuilder.buildCountQuery(searchRequestMap, searchConfiguration);
195201

196202
List<Long> totals = entityManager.createQuery(countQuery).getResultList();
197203

nrich-search/src/main/java/net/croz/nrich/search/repository/JpaSearchExecutor.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,28 +83,26 @@ public <R, P> Page<P> findAll(R request, SearchConfiguration<T, P, R> searchConf
8383
if (pageable.isPaged()) {
8484
typedQuery.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize());
8585

86-
return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(query));
86+
return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(request, searchConfiguration));
8787
}
8888

8989
return new PageImpl<>(typedQuery.getResultList());
9090
}
9191

9292
@Override
9393
public <R, P> long count(R request, SearchConfiguration<T, P, R> searchConfiguration) {
94-
return executeCountQuery(queryBuilder.buildQuery(request, searchConfiguration, Sort.unsorted()));
94+
return executeCountQuery(request, searchConfiguration);
9595
}
9696

9797
@Override
9898
public <R, P> boolean exists(R request, SearchConfiguration<T, P, R> searchConfiguration) {
99-
CriteriaQuery<?> query = queryBuilder.buildQuery(request, searchConfiguration, Sort.unsorted());
99+
CriteriaQuery<Integer> query = queryBuilder.buildExistsQuery(request, searchConfiguration);
100100

101-
CriteriaQuery<Integer> existsQuery = queryBuilder.convertToExistsQuery(query);
102-
103-
return entityManager.createQuery(existsQuery).setMaxResults(1).getResultList().size() == 1;
101+
return entityManager.createQuery(query).setMaxResults(1).getResultList().size() == 1;
104102
}
105103

106-
private long executeCountQuery(CriteriaQuery<?> query) {
107-
CriteriaQuery<Long> countQuery = queryBuilder.convertToCountQuery(query);
104+
private <R, P> long executeCountQuery(R request, SearchConfiguration<T, P, R> searchConfiguration) {
105+
CriteriaQuery<Long> countQuery = queryBuilder.buildCountQuery(request, searchConfiguration);
108106

109107
List<Long> totals = entityManager.createQuery(countQuery).getResultList();
110108

nrich-search/src/main/java/net/croz/nrich/search/repository/JpaStringSearchExecutor.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public <P> Page<P> findAll(String searchTerm, List<String> propertyToSearchList,
9999
if (pageable.isPaged()) {
100100
typedQuery.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize());
101101

102-
return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(query));
102+
return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(searchMap, searchConfiguration));
103103
}
104104

105105
return new PageImpl<>(typedQuery.getResultList());
@@ -109,26 +109,24 @@ public <P> Page<P> findAll(String searchTerm, List<String> propertyToSearchList,
109109
public <P> long count(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
110110
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);
111111

112-
return executeCountQuery(queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted()));
112+
return executeCountQuery(searchMap, searchConfiguration);
113113
}
114114

115115
@Override
116116
public <P> boolean exists(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
117117
Map<String, Object> searchMap = convertToMap(searchTerm, propertyToSearchList, searchConfiguration);
118118

119-
CriteriaQuery<?> query = queryBuilder.buildQuery(searchMap, searchConfiguration, Sort.unsorted());
119+
CriteriaQuery<Integer> query = queryBuilder.buildExistsQuery(searchMap, searchConfiguration);
120120

121-
CriteriaQuery<Integer> existsQuery = queryBuilder.convertToExistsQuery(query);
122-
123-
return entityManager.createQuery(existsQuery).setMaxResults(1).getResultList().size() == 1;
121+
return entityManager.createQuery(query).setMaxResults(1).getResultList().size() == 1;
124122
}
125123

126124
private Map<String, Object> convertToMap(String searchTerm, List<String> propertyToSearchList, SearchConfiguration<T, ?, Map<String, Object>> searchConfiguration) {
127125
return stringToEntityPropertyMapConverter.convert(searchTerm, propertyToSearchList, managedType, searchConfiguration.getSearchPropertyConfiguration());
128126
}
129127

130-
private long executeCountQuery(CriteriaQuery<?> query) {
131-
CriteriaQuery<Long> countQuery = queryBuilder.convertToCountQuery(query);
128+
private <P> long executeCountQuery(Map<String, Object> searchMap, SearchConfiguration<T, P, Map<String, Object>> searchConfiguration) {
129+
CriteriaQuery<Long> countQuery = queryBuilder.buildCountQuery(searchMap, searchConfiguration);
132130

133131
List<Long> totals = entityManager.createQuery(countQuery).getResultList();
134132

nrich-search/src/main/java/net/croz/nrich/search/support/JpaQueryBuilder.java

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,10 @@ public class JpaQueryBuilder<T> {
6969
private final Class<T> entityType;
7070

7171
public <R, P> CriteriaQuery<P> buildQuery(R request, SearchConfiguration<T, P, R> searchConfiguration, Sort sort) {
72-
Assert.notNull(request, "Search request is not defined!");
73-
Assert.notNull(searchConfiguration, "Search configuration is not defined!");
74-
Assert.notNull(sort, "Sort is not defined!");
75-
76-
Class<T> rootEntity;
77-
if (searchConfiguration.getRootEntityResolver() == null) {
78-
rootEntity = entityType;
79-
}
80-
else {
81-
rootEntity = searchConfiguration.getRootEntityResolver().apply(request);
82-
}
83-
84-
Assert.notNull(rootEntity, "Root entity returned by resolver is not defined!");
72+
validateArguments(request, searchConfiguration);
8573

8674
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
87-
75+
Class<T> rootEntity = resolveRootEntity(request, searchConfiguration);
8876
Class<P> resultClass = resolveResultClass(searchConfiguration, rootEntity);
8977

9078
Assert.isTrue(!joinFetchExists(searchConfiguration.getJoinList()) || entityType.isAssignableFrom(resultClass), "Join Fetch is ony possible when result class is not an projection!");
@@ -93,7 +81,7 @@ public <R, P> CriteriaQuery<P> buildQuery(R request, SearchConfiguration<T, P, R
9381

9482
Root<T> root = query.from(rootEntity);
9583

96-
applyJoinsOrFetchesToQuery(request, root, searchConfiguration.getJoinList());
84+
applyJoinsOrFetchesToQuery(true, request, root, searchConfiguration.getJoinList());
9785

9886
List<SearchProjection<R>> searchProjectionList = searchConfiguration.getProjectionList();
9987
if (!resultClass.equals(entityType) && CollectionUtils.isEmpty(searchProjectionList)) {
@@ -108,46 +96,79 @@ public <R, P> CriteriaQuery<P> buildQuery(R request, SearchConfiguration<T, P, R
10896

10997
query.distinct(searchConfiguration.isDistinct());
11098

111-
List<Predicate> requestPredicateList = resolveQueryPredicateList(request, searchConfiguration, criteriaBuilder, root, query);
112-
List<Predicate> interceptorPredicateList = resolveInterceptorPredicateList(request, searchConfiguration.getAdditionalRestrictionResolverList(), criteriaBuilder, root, query);
99+
resolveAndApplyPredicateList(request, searchConfiguration, criteriaBuilder, root, query);
113100

114-
applyPredicatesToQuery(criteriaBuilder, query, searchConfiguration.isAnyMatch(), requestPredicateList, interceptorPredicateList);
115-
116-
if (sort.isSorted()) {
101+
if (sort != null && sort.isSorted()) {
117102
query.orderBy(QueryUtils.toOrders(sort, root, criteriaBuilder));
118103
}
119104

120105
return query;
121106
}
122107

123-
public CriteriaQuery<Long> convertToCountQuery(CriteriaQuery<?> query) {
124-
@SuppressWarnings("unchecked")
125-
CriteriaQuery<Long> countQuery = (CriteriaQuery<Long>) query;
108+
public <R, P> CriteriaQuery<Long> buildCountQuery(R request, SearchConfiguration<T, P, R> searchConfiguration) {
109+
validateArguments(request, searchConfiguration);
126110

127-
clearSortAndFetchesFromQuery(countQuery);
111+
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
112+
Class<T> rootEntity = resolveRootEntity(request, searchConfiguration);
113+
CriteriaQuery<Long> query = criteriaBuilder.createQuery(Long.class);
128114

129-
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
130-
Root<?> root = query.getRoots().iterator().next();
115+
Root<T> root = query.from(rootEntity);
116+
117+
applyJoinsOrFetchesToQuery(false, request, root, searchConfiguration.getJoinList());
131118

132-
if (countQuery.isDistinct()) {
133-
countQuery.select(builder.countDistinct(root));
119+
if (searchConfiguration.isDistinct()) {
120+
query.select(criteriaBuilder.countDistinct(root));
134121
}
135122
else {
136-
countQuery.select(builder.count(root));
123+
query.select(criteriaBuilder.count(root));
137124
}
138125

139-
return countQuery;
126+
@SuppressWarnings("unchecked")
127+
CriteriaQuery<P> castedQuery = (CriteriaQuery<P>) query;
128+
129+
resolveAndApplyPredicateList(request, searchConfiguration, criteriaBuilder, root, castedQuery);
130+
131+
return query;
140132
}
141133

142-
public CriteriaQuery<Integer> convertToExistsQuery(CriteriaQuery<?> query) {
134+
public <R, P> CriteriaQuery<Integer> buildExistsQuery(R request, SearchConfiguration<T, P, R> searchConfiguration) {
135+
validateArguments(request, searchConfiguration);
136+
137+
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
138+
Class<T> rootEntity = resolveRootEntity(request, searchConfiguration);
139+
CriteriaQuery<Integer> query = criteriaBuilder.createQuery(Integer.class);
140+
141+
Root<T> root = query.from(rootEntity);
142+
143+
applyJoinsOrFetchesToQuery(false, request, root, searchConfiguration.getJoinList());
144+
145+
query.select(entityManager.getCriteriaBuilder().literal(1));
146+
143147
@SuppressWarnings("unchecked")
144-
CriteriaQuery<Integer> existsQuery = (CriteriaQuery<Integer>) query;
148+
CriteriaQuery<P> castedQuery = (CriteriaQuery<P>) query;
149+
150+
resolveAndApplyPredicateList(request, searchConfiguration, criteriaBuilder, root, castedQuery);
151+
152+
return query;
153+
}
145154

146-
clearSortAndFetchesFromQuery(existsQuery);
155+
private <R, P> void validateArguments(R request, SearchConfiguration<T, P, R> searchConfiguration) {
156+
Assert.notNull(request, "Search request is not defined!");
157+
Assert.notNull(searchConfiguration, "Search configuration is not defined!");
158+
}
159+
160+
private <R, P> Class<T> resolveRootEntity(R request, SearchConfiguration<T, P, R> searchConfiguration) {
161+
Class<T> rootEntity;
162+
if (searchConfiguration.getRootEntityResolver() == null) {
163+
rootEntity = entityType;
164+
}
165+
else {
166+
rootEntity = searchConfiguration.getRootEntityResolver().apply(request);
167+
}
147168

148-
existsQuery.select(entityManager.getCriteriaBuilder().literal(1));
169+
Assert.notNull(rootEntity, "Root entity returned by resolver is not defined!");
149170

150-
return existsQuery;
171+
return rootEntity;
151172
}
152173

153174
// TODO try to use result set mapper, jpa projections require constructors with all parameters
@@ -156,14 +177,14 @@ private <R, P> Class<P> resolveResultClass(SearchConfiguration<T, P, R> searchCo
156177
return searchConfiguration.getResultClass() == null ? (Class<P>) rootEntity : searchConfiguration.getResultClass();
157178
}
158179

159-
private <R> void applyJoinsOrFetchesToQuery(R request, Root<?> root, List<SearchJoin<R>> joinList) {
180+
private <R> void applyJoinsOrFetchesToQuery(boolean applyFetch, R request, Root<?> root, List<SearchJoin<R>> joinList) {
160181
if (CollectionUtils.isEmpty(joinList)) {
161182
return;
162183
}
163184

164185
joinList.stream()
165186
.filter(join -> shouldApplyJoinOrFetch(join, request))
166-
.forEach(searchJoin -> applyJoinOrJoinFetch(root, searchJoin));
187+
.forEach(searchJoin -> applyJoinOrJoinFetch(root, searchJoin, applyFetch));
167188
}
168189

169190
private <R> List<Selection<?>> resolveQueryProjectionList(Root<?> root, List<SearchProjection<R>> projectionList, R request) {
@@ -181,11 +202,11 @@ private <R> boolean shouldApplyJoinOrFetch(SearchJoin<R> join, R request) {
181202
return join.getCondition() == null || join.getCondition().test(request);
182203
}
183204

184-
private void applyJoinOrJoinFetch(Root<?> root, SearchJoin<?> searchJoin) {
205+
private void applyJoinOrJoinFetch(Root<?> root, SearchJoin<?> searchJoin, boolean applyFetch) {
185206
JoinType joinType = searchJoin.getJoinType() == null ? JoinType.INNER : searchJoin.getJoinType();
186207

187208
String[] pathList = PathResolvingUtil.convertToPathList(searchJoin.getPath());
188-
if (searchJoin.isFetch()) {
209+
if (applyFetch && searchJoin.isFetch()) {
189210
Fetch<?, ?> fetch = null;
190211
for (String path : pathList) {
191212
fetch = fetch == null ? root.fetch(path, joinType) : fetch.fetch(path, joinType);
@@ -207,6 +228,13 @@ private <R> boolean shouldApplyProjection(SearchProjection<R> projection, R requ
207228
return projection.getCondition() == null || projection.getCondition().test(request);
208229
}
209230

231+
private <P, R> void resolveAndApplyPredicateList(R request, SearchConfiguration<T, P, R> searchConfiguration, CriteriaBuilder criteriaBuilder, Root<T> root, CriteriaQuery<P> query) {
232+
List<Predicate> requestPredicateList = resolveQueryPredicateList(request, searchConfiguration, criteriaBuilder, root, query);
233+
List<Predicate> interceptorPredicateList = resolveInterceptorPredicateList(request, searchConfiguration.getAdditionalRestrictionResolverList(), criteriaBuilder, root, query);
234+
235+
applyPredicatesToQuery(criteriaBuilder, query, searchConfiguration.isAnyMatch(), requestPredicateList, interceptorPredicateList);
236+
}
237+
210238
private <P, R> List<Predicate> resolveQueryPredicateList(R request, SearchConfiguration<T, P, R> searchConfiguration, CriteriaBuilder criteriaBuilder, Root<?> root, CriteriaQuery<?> query) {
211239
Set<Restriction> restrictionList = new SearchDataParser(root.getModel(), request, SearchDataParserConfiguration.fromSearchConfiguration(searchConfiguration)).resolveRestrictionList();
212240

@@ -375,10 +403,4 @@ private SearchDataParserConfiguration searchDataParserConfiguration(SearchProper
375403
.resolvePropertyMappingUsingPrefix(resolvePropertyMappingUsingPrefix)
376404
.build();
377405
}
378-
379-
private void clearSortAndFetchesFromQuery(CriteriaQuery<?> query) {
380-
query.orderBy(Collections.emptyList());
381-
382-
query.getRoots().forEach(root -> root.getFetches().clear());
383-
}
384406
}

nrich-search/src/test/java/net/croz/nrich/search/repository/support/JpaQueryBuilderTest.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,13 +446,10 @@ void shouldRemoveFetchesWhenConvertingToCountQuery() {
446446
.build();
447447

448448
// when
449-
CriteriaQuery<TestEntity> query = jpaQueryBuilder.buildQuery(request, searchConfiguration, Sort.unsorted());
450-
451-
// and when
452-
CriteriaQuery<Long> countQuery = jpaQueryBuilder.convertToCountQuery(query);
449+
CriteriaQuery<Long> query = jpaQueryBuilder.buildCountQuery(request, searchConfiguration);
453450

454451
// then
455-
assertThat(entityManager.createQuery(countQuery).getSingleResult()).isEqualTo(1L);
452+
assertThat(entityManager.createQuery(query).getSingleResult()).isEqualTo(1L);
456453
}
457454

458455
@Test
@@ -749,6 +746,15 @@ void shouldSupportJoinsByDifferentIdProperty() {
749746
assertThat(results).hasSize(2);
750747
}
751748

749+
@Test
750+
void shouldNotFailOnNullSort() {
751+
// when
752+
Throwable thrown = catchThrowable(() -> executeQuery(Collections.emptyMap(), SearchConfiguration.emptyConfiguration(), null));
753+
754+
// then
755+
assertThat(thrown).isNull();
756+
}
757+
752758
private <P, R> List<P> executeQuery(R request, SearchConfiguration<TestEntity, P, R> searchConfiguration) {
753759
return executeQuery(request, searchConfiguration, Sort.unsorted());
754760
}

0 commit comments

Comments
 (0)