From 36a6f958f2033049ce10ca4b2a04147063db0d34 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Fri, 14 Nov 2014 12:44:05 +0100 Subject: [PATCH] DATAJPA-629 - Allow SpEL template expressions in combination with parameter expressions in @Query. Previously SpEL template expressions like #{#entityName} could not be used in conjunction with parameter expressions in @Query because the SpEL template parser tried to evaluate the dynamic parameter expressions as well. We now mask the parameters prior to evaluating the SpEL template expression. Original pull request: #122. --- .../query/ExpressionBasedStringQuery.java | 30 +++++++++++++++++-- .../jpa/repository/UserRepositoryTests.java | 14 +++++++++ .../jpa/repository/sample/UserRepository.java | 9 +++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java index 84bb1725d..8074788ef 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.data.jpa.repository.query; +import java.util.regex.Pattern; + import org.springframework.data.repository.core.EntityMetadata; import org.springframework.expression.Expression; import org.springframework.expression.ParserContext; @@ -35,6 +37,13 @@ import org.springframework.util.Assert; */ class ExpressionBasedStringQuery extends StringQuery { + private static final String EXPRESSION_PARAMETER = "?#{"; + private static final String QUOTED_EXPRESSION_PARAMETER = "?__HASH__{"; + + private static final Pattern EXPRESSION_PARAMETER_QUOTING = Pattern.compile(Pattern.quote(EXPRESSION_PARAMETER)); + private static final Pattern EXPRESSION_PARAMETER_UNQUOTING = Pattern.compile(Pattern + .quote(QUOTED_EXPRESSION_PARAMETER)); + private static final String ENTITY_NAME = "entityName"; private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME; private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE + "}"; @@ -70,10 +79,25 @@ class ExpressionBasedStringQuery extends StringQuery { StandardEvaluationContext evalContext = new StandardEvaluationContext(); evalContext.setVariable(ENTITY_NAME, metadata.getEntityName()); + query = potentiallyQuoteExpressionsParameter(query); + Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION); - Object result = expr.getValue(evalContext, String.class); - return result == null ? query : String.valueOf(result); + String result = expr.getValue(evalContext, String.class); + + if (result == null) { + return query; + } + + return potentiallyUnquoteParameterExpressions(result); + } + + private static String potentiallyUnquoteParameterExpressions(String result) { + return EXPRESSION_PARAMETER_UNQUOTING.matcher(result).replaceAll(EXPRESSION_PARAMETER); + } + + private static String potentiallyQuoteExpressionsParameter(String query) { + return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER); } private static boolean containsExpression(String query) { diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 7f1517a8d..a3caceda1 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1753,6 +1753,20 @@ public class UserRepositoryTests { assertThat(users.getContent().get(1), is(fourthUser)); } + /** + * @see DATAJPA-629 + */ + @Test + public void shouldfindUsersBySpELExpressionParametersWithSpelTemplateExpression() { + + flushTestUsers(); + List users = repository.findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntityExpression( + "Joachim", "Arrasz"); + + assertThat(users, hasSize(1)); + assertThat(users.get(0), is(secondUser)); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 3f7327ec6..9c6d02861 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -459,7 +459,7 @@ public interface UserRepository extends JpaRepository, JpaSpecifi */ @Query("select u from User u where u.emailAddress = ?1") Optional findOptionalByEmailAddress(String emailAddress); - + /** * @see DATAJPA-564 */ @@ -527,4 +527,11 @@ public interface UserRepository extends JpaRepository, JpaSpecifi value = "select * from (select rownum() as RN, u.* from User u) where RN between ?#{ #pageable.offset -1} and ?#{#pageable.offset + #pageable.pageSize}", countQuery = "select count(u.id) from User u", nativeQuery = true) Page findUsersInNativeQueryWithPagination(Pageable pageable); + + /** + * @see DATAJPA-629 + */ + @Query("select u from #{#entityName} u where u.firstname = ?#{[0]} and u.lastname = ?#{[1]}") + List findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntityExpression(String firstname, + String lastname); }