62
62
63
63
/**
64
64
* Simple utility class to create JPA queries.
65
- *
65
+ *
66
66
* @author Oliver Gierke
67
67
* @author Kevin Raymond
68
68
* @author Thomas Darimont
72
72
* @author Sébastien Péralta
73
73
* @author Jens Schauder
74
74
* @author Florian Lüdiger
75
+ * @author Grégoire Druant
75
76
*/
76
77
public abstract class QueryUtils {
77
78
@@ -109,6 +110,7 @@ public abstract class QueryUtils {
109
110
110
111
private static final Pattern PUNCTATION_PATTERN = Pattern .compile (".*((?![\\ ._])[\\ p{Punct}|\\ s])" );
111
112
private static final Pattern FUNCTION_PATTERN ;
113
+ private static final Pattern FIELD_ALIAS_PATTERN ;
112
114
113
115
private static final String UNSAFE_PROPERTY_REFERENCE = "Sort expression '%s' must only contain property references or "
114
116
+ "aliases used in the select clause. If you really want to use something other than that for sorting, please use "
@@ -165,6 +167,14 @@ public abstract class QueryUtils {
165
167
builder .append ("\\ s+[as|AS]+\\ s+(([\\ w\\ .]+))" );
166
168
167
169
FUNCTION_PATTERN = compile (builder .toString ());
170
+
171
+ builder = new StringBuilder ();
172
+ builder .append ("\\ s+" ); // at least one space
173
+ builder .append ("[^\\ s\\ (\\ )]+" ); // No white char no bracket
174
+ builder .append ("\\ s+[as|AS]+\\ s+(([\\ w\\ .]+))" ); // the potential alias
175
+
176
+ FIELD_ALIAS_PATTERN = compile (builder .toString ());
177
+
168
178
}
169
179
170
180
/**
@@ -176,7 +186,7 @@ private QueryUtils() {
176
186
177
187
/**
178
188
* Returns the query string to execute an exists query for the given id attributes.
179
- *
189
+ *
180
190
* @param entityName the name of the entity to create the query for, must not be {@literal null}.
181
191
* @param countQueryPlaceHolder the placeholder for the count clause, must not be {@literal null}.
182
192
* @param idAttributes the id attributes for the entity, must not be {@literal null}.
@@ -201,7 +211,7 @@ public static String getExistsQueryString(String entityName, String countQueryPl
201
211
202
212
/**
203
213
* Returns the query string for the given class name.
204
- *
214
+ *
205
215
* @param template
206
216
* @param entityName
207
217
* @return
@@ -215,7 +225,7 @@ public static String getQueryString(String template, String entityName) {
215
225
216
226
/**
217
227
* Adds {@literal order by} clause to the JPQL query. Uses the first alias to bind the sorting property to.
218
- *
228
+ *
219
229
* @param query the query string to which sorting is applied
220
230
* @param sort the sort specification to apply.
221
231
* @return the modified query string.
@@ -226,7 +236,7 @@ public static String applySorting(String query, Sort sort) {
226
236
227
237
/**
228
238
* Adds {@literal order by} clause to the JPQL query.
229
- *
239
+ *
230
240
* @param query the query string to which sorting is applied. Must not be {@literal null} or empty.
231
241
* @param sort the sort specification to apply.
232
242
* @param alias the alias to be used in the order by clause. Must not be {@literal null} or empty.
@@ -249,10 +259,11 @@ public static String applySorting(String query, Sort sort, String alias) {
249
259
}
250
260
251
261
Set <String > aliases = getOuterJoinAliases (query );
252
- Set <String > functionAliases = getFunctionAliases (query );
262
+ Set <String > fieldAliases = getFunctionAliases (query );
263
+ fieldAliases .addAll (getFieldAliases (query ));
253
264
254
265
for (Order order : sort ) {
255
- builder .append (getOrderClause (aliases , functionAliases , alias , order )).append (", " );
266
+ builder .append (getOrderClause (aliases , fieldAliases , alias , order )).append (", " );
256
267
}
257
268
258
269
builder .delete (builder .length () - 2 , builder .length ());
@@ -263,19 +274,19 @@ public static String applySorting(String query, Sort sort, String alias) {
263
274
/**
264
275
* Returns the order clause for the given {@link Order}. Will prefix the clause with the given alias if the referenced
265
276
* property refers to a join alias, i.e. starts with {@code $alias.}.
266
- *
277
+ *
267
278
* @param joinAliases the join aliases of the original query.
268
279
* @param alias the alias for the root entity.
269
280
* @param order the order object to build the clause for.
270
281
* @return
271
282
*/
272
- private static String getOrderClause (Set <String > joinAliases , Set <String > functionAlias , String alias , Order order ) {
283
+ private static String getOrderClause (Set <String > joinAliases , Set <String > fieldAlias , String alias , Order order ) {
273
284
274
285
String property = order .getProperty ();
275
286
276
287
checkSortExpression (order );
277
288
278
- if (functionAlias .contains (property )) {
289
+ if (fieldAlias .contains (property )) {
279
290
return String .format ("%s %s" , property , toJpaDirection (order ));
280
291
}
281
292
@@ -297,7 +308,7 @@ private static String getOrderClause(Set<String> joinAliases, Set<String> functi
297
308
298
309
/**
299
310
* Returns the aliases used for {@code left (outer) join}s.
300
- *
311
+ *
301
312
* @param query
302
313
* @return
303
314
*/
@@ -317,6 +328,26 @@ static Set<String> getOuterJoinAliases(String query) {
317
328
return result ;
318
329
}
319
330
331
+ /**
332
+ * Returns the aliases used for fields in the query.
333
+ *
334
+ * @param query a {@literal String} containing a query. Must not be {@literal null}.
335
+ * @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}.
336
+ */
337
+ private static Set <String > getFieldAliases (String query ) {
338
+ Set <String > result = new HashSet <String >();
339
+ Matcher matcher = FIELD_ALIAS_PATTERN .matcher (query );
340
+
341
+ while (matcher .find ()) {
342
+ String alias = matcher .group (1 );
343
+
344
+ if (StringUtils .hasText (alias )) {
345
+ result .add (alias );
346
+ }
347
+ }
348
+ return result ;
349
+ }
350
+
320
351
/**
321
352
* Returns the aliases used for aggregate functions like {@code SUM, COUNT, ...}.
322
353
*
@@ -346,7 +377,7 @@ private static String toJpaDirection(Order order) {
346
377
347
378
/**
348
379
* Resolves the alias for the entity to be retrieved from the given JPA query.
349
- *
380
+ *
350
381
* @param query
351
382
* @return
352
383
*/
@@ -360,7 +391,7 @@ public static String detectAlias(String query) {
360
391
/**
361
392
* Creates a where-clause referencing the given entities and appends it to the given query string. Binds the given
362
393
* entities to the query.
363
- *
394
+ *
364
395
* @param <T>
365
396
* @param queryString must not be {@literal null}.
366
397
* @param entities must not be {@literal null}.
@@ -410,7 +441,7 @@ public static <T> Query applyAndBind(String queryString, Iterable<T> entities, E
410
441
411
442
/**
412
443
* Creates a count projected query from the given original query.
413
- *
444
+ *
414
445
* @param originalQuery must not be {@literal null} or empty.
415
446
* @return
416
447
*/
@@ -420,7 +451,7 @@ public static String createCountQueryFor(String originalQuery) {
420
451
421
452
/**
422
453
* Creates a count projected query from the given original query.
423
- *
454
+ *
424
455
* @param originalQuery must not be {@literal null}.
425
456
* @param countProjection may be {@literal null}.
426
457
* @return
@@ -450,7 +481,7 @@ public static String createCountQueryFor(String originalQuery, String countProje
450
481
451
482
/**
452
483
* Returns whether the given {@link Query} contains named parameters.
453
- *
484
+ *
454
485
* @param query
455
486
* @return
456
487
*/
@@ -471,7 +502,7 @@ public static boolean hasNamedParameter(Query query) {
471
502
472
503
/**
473
504
* Returns whether the given query contains named parameters.
474
- *
505
+ *
475
506
* @param query can be {@literal null} or empty.
476
507
* @return
477
508
*/
@@ -481,7 +512,7 @@ public static boolean hasNamedParameter(String query) {
481
512
482
513
/**
483
514
* Turns the given {@link Sort} into {@link javax.persistence.criteria.Order}s.
484
- *
515
+ *
485
516
* @param sort the {@link Sort} instance to be transformed into JPA {@link javax.persistence.criteria.Order}s.
486
517
* @param from must not be {@literal null}.
487
518
* @param cb must not be {@literal null}.
@@ -507,7 +538,7 @@ public static List<javax.persistence.criteria.Order> toOrders(Sort sort, From<?,
507
538
508
539
/**
509
540
* Returns whether the given JPQL query contains a constructor expression.
510
- *
541
+ *
511
542
* @param query must not be {@literal null} or empty.
512
543
* @return
513
544
* @since 1.10
@@ -521,7 +552,7 @@ public static boolean hasConstructorExpression(String query) {
521
552
522
553
/**
523
554
* Returns the projection part of the query, i.e. everything between {@code select} and {@code from}.
524
- *
555
+ *
525
556
* @param query must not be {@literal null} or empty.
526
557
* @return
527
558
* @since 1.10.2
@@ -536,7 +567,7 @@ public static String getProjection(String query) {
536
567
537
568
/**
538
569
* Creates a criteria API {@link javax.persistence.criteria.Order} from the given {@link Order}.
539
- *
570
+ *
540
571
* @param order the order to transform into a JPA {@link javax.persistence.criteria.Order}
541
572
* @param from the {@link From} the {@link Order} expression is based on
542
573
* @param cb the {@link CriteriaBuilder} to build the {@link javax.persistence.criteria.Order} with
@@ -587,7 +618,7 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
587
618
/**
588
619
* Returns whether the given {@code propertyPathModel} requires the creation of a join. This is the case if we find a
589
620
* optional association.
590
- *
621
+ *
591
622
* @param propertyPathModel must not be {@literal null}.
592
623
* @param isPluralAttribute is the attribute of Collection type?
593
624
* @param isLeafProperty is this the final property navigated by a {@link PropertyPath}?
@@ -638,7 +669,7 @@ static Expression<Object> toExpressionRecursively(Path<Object> path, PropertyPat
638
669
639
670
/**
640
671
* Returns an existing join for the given attribute if one already exists or creates a new one if not.
641
- *
672
+ *
642
673
* @param from the {@link From} to get the current joins from.
643
674
* @param attribute the {@link Attribute} to look for in the current joins.
644
675
* @return will never be {@literal null}.
@@ -659,7 +690,7 @@ static Expression<Object> toExpressionRecursively(Path<Object> path, PropertyPat
659
690
660
691
/**
661
692
* Return whether the given {@link From} contains a fetch declaration for the attribute with the given name.
662
- *
693
+ *
663
694
* @param from the {@link From} to check for fetches.
664
695
* @param attribute the attribute name to check.
665
696
* @return
0 commit comments