diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 9ab98f288..6ce1bda83 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -22,14 +22,14 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.springframework.data.domain.Range; import org.springframework.data.repository.query.parser.Part.Type; +import org.springframework.data.repository.query.parser.QuotationMap; +import org.springframework.data.repository.query.parser.SpelQueryContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -54,7 +54,6 @@ class StringQuery implements DeclaredQuery { private final @Nullable String alias; private final boolean hasConstructorExpression; private final boolean containsPageableInSpel; - private final boolean usesJdbcStyleParameters; /** * Creates a new {@link StringQuery} from the given JPQL query. @@ -68,11 +67,9 @@ class StringQuery implements DeclaredQuery { this.bindings = new ArrayList<>(); this.containsPageableInSpel = query.contains("#pageable"); - Metadata queryMeta = new Metadata(); this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, - this.bindings, queryMeta); + this.bindings); - this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters; this.alias = QueryUtils.detectAlias(query); this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query); } @@ -115,7 +112,7 @@ class StringQuery implements DeclaredQuery { */ @Override public boolean usesJdbcStyleParameters() { - return usesJdbcStyleParameters; + return false; } /* @@ -178,23 +175,18 @@ class StringQuery implements DeclaredQuery { * * @author Thomas Darimont */ - public enum ParameterBindingParser { + enum ParameterBindingParser { INSTANCE; - static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__"; - public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\?(\\d*+(?![#\\w]))"; - // .....................................................................^ not followed by a hash or a letter. - // .................................................................^ zero or more digits. - // .............................................................^ start with a question mark. - private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern.compile(POSITIONAL_OR_INDEXED_PARAMETER); + private static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__"; + private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern.compile("\\?(\\d+)"); private static final Pattern PARAMETER_BINDING_PATTERN; private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type! " + "Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same binding."; - public static final int INDEXED_PARAMETER_GROUP = 4; - public static final int NAMED_PARAMETER_GROUP = 6; - public static final int COMPARISION_TYPE_GROUP = 1; - public static final int EXPRESSION_GROUP = 9; + private static final int INDEXED_PARAMETER_GROUP = 4; + private static final int NAMED_PARAMETER_GROUP = 6; + private static final int COMPARISION_TYPE_GROUP = 1; static { @@ -213,13 +205,12 @@ class StringQuery implements DeclaredQuery { builder.append("(?: )?"); // some whitespace builder.append("\\(?"); // optional braces around parameters builder.append("("); - builder.append("%?(" + POSITIONAL_OR_INDEXED_PARAMETER + ")%?"); // position parameter and parameter index + builder.append("%?(\\?(\\d+))%?"); // position parameter and parameter index builder.append("|"); // or // named parameter and the parameter name builder.append("%?(" + QueryUtils.COLON_NO_DOUBLE_COLON + QueryUtils.IDENTIFIER_GROUP + ")%?"); - builder.append("|"); // or - builder.append("%?((:|\\?)#\\{([^}]+)\\})%?"); // expression parameter and expression + builder.append(")"); builder.append("\\)?"); // optional braces around parameters @@ -230,33 +221,17 @@ class StringQuery implements DeclaredQuery { * Parses {@link ParameterBinding} instances from the given query and adds them to the registered bindings. Returns * the cleaned up query. */ - String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query, List bindings, - Metadata queryMeta) { + private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query, + List bindings) { - String result = query; - Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(query); + SpelQueryContext.SpelExtractor spelExtractor = createSpelExtractor(query); - int greatestParameterIndex = tryFindGreatestParameterIndexIn(query); + String resultingQuery = spelExtractor.query(); - boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != -1; + Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(spelExtractor.query()); - /* - * Prefer indexed access over named parameters if only SpEL Expression parameters are present. - */ - if (!parametersShouldBeAccessedByIndex && query.contains("?#{")) { - parametersShouldBeAccessedByIndex = true; - greatestParameterIndex = 0; - } + QuotationMap quotationMap = new QuotationMap(spelExtractor.query()); - /* - * If parameters need to be bound by index, we bind the synthetic expression parameters starting from position of the greatest discovered index parameter in order to - * not mix-up with the actual parameter indices. - */ - int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0; - - QuotationMap quotationMap = new QuotationMap(query); - - boolean usesJpaStyleParameters = false; while (matcher.find()) { if (quotationMap.isQuoted(matcher.start())) { @@ -265,51 +240,20 @@ class StringQuery implements DeclaredQuery { String parameterIndexString = matcher.group(INDEXED_PARAMETER_GROUP); String parameterName = parameterIndexString != null ? null : matcher.group(NAMED_PARAMETER_GROUP); - Integer parameterIndex = getParameterIndex(parameterIndexString); - + Integer parameterIndex = parameterIndexString == null ? null : Integer.valueOf(parameterIndexString); String typeSource = matcher.group(COMPARISION_TYPE_GROUP); - String expression = null; + String expression = spelExtractor.parameterNameToSpelMap() + .get(parameterName == null ? parameterIndexString : parameterName); String replacement = null; - if (parameterName == null && parameterIndex == null) { + Assert.isTrue(parameterIndex != null || parameterName != null, "We need either a name or an index."); - expressionParameterIndex++; - - if ("".equals(parameterIndexString)) { - - parameterIndex = expressionParameterIndex; - queryMeta.usesJdbcStyleParameters = true; - } else { - - usesJpaStyleParameters = true; - - if (parametersShouldBeAccessedByIndex) { - - parameterIndex = expressionParameterIndex; - replacement = "?" + parameterIndex; - } else { - - parameterName = EXPRESSION_PARAMETER_PREFIX + expressionParameterIndex; - replacement = ":" + parameterName; - } - } - - expression = matcher.group(EXPRESSION_GROUP); - } else { - usesJpaStyleParameters = true; - } - - if (usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) { - throw new IllegalArgumentException("Mixing of ? parameters and other forms like ?1 is not supported"); - } - - String replacementTarget = matcher.group(2); switch (ParameterBindingType.of(typeSource)) { case LIKE: - Type likeType = LikeParameterBinding.getLikeTypeFrom(replacementTarget); - replacement = replacement != null ? replacement : matcher.group(3); + Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); + replacement = matcher.group(3); if (parameterIndex != null) { checkAndRegister(new LikeParameterBinding(parameterIndex, likeType, expression), bindings); @@ -336,28 +280,48 @@ class StringQuery implements DeclaredQuery { bindings.add(parameterIndex != null ? new ParameterBinding(null, parameterIndex, expression) : new ParameterBinding(parameterName, null, expression)); - } if (replacement != null) { - result = replaceFirst(result, replacementTarget, replacement); + resultingQuery = replaceFirst(resultingQuery, matcher.group(2), replacement); } } - return result; + return resultingQuery; } - @Nullable - private Integer getParameterIndex(@Nullable String parameterIndexString) { + private SpelQueryContext.SpelExtractor createSpelExtractor(String queryWithSpel) { + int greatestParameterIndex = tryFindGreatestParameterIndexIn(queryWithSpel); - if (parameterIndexString == null || parameterIndexString.isEmpty()) { - return null; + boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != -1; + + /* + * Prefer indexed access over named parameters if only SpEL Expression parameters are present. + */ + if (!parametersShouldBeAccessedByIndex && queryWithSpel.contains("?#{")) { + parametersShouldBeAccessedByIndex = true; + greatestParameterIndex = 0; } - return Integer.valueOf(parameterIndexString); + + /* + * If parameters need to be bound by index, we bind the synthetic expression parameters starting from position of the greatest discovered index parameter in order to + * not mix-up with the actual parameter indices. + */ + int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0; + + BiFunction indexToParameterName = parametersShouldBeAccessedByIndex + ? (index, expression) -> String.valueOf(index + expressionParameterIndex + 1) + : (index, expression) -> EXPRESSION_PARAMETER_PREFIX + (index + 1); + + String fixedPrefix = parametersShouldBeAccessedByIndex ? "?" : ":"; + + BiFunction parameterNameToReplacement = (prefix, name) -> fixedPrefix + name; + + return new SpelQueryContext(indexToParameterName, parameterNameToReplacement).parse(queryWithSpel); } - private static String replaceFirst(String text, String substring, String replacement) { + private String replaceFirst(String text, String substring, String replacement) { int index = text.indexOf(substring); if (index < 0) { @@ -373,18 +337,14 @@ class StringQuery implements DeclaredQuery { int greatestParameterIndex = -1; while (parameterIndexMatcher.find()) { - String parameterIndexString = parameterIndexMatcher.group(1); - Integer parameterIndex = getParameterIndex(parameterIndexString); - if (parameterIndex != null) { - greatestParameterIndex = Math.max(greatestParameterIndex, parameterIndex); - } + greatestParameterIndex = Math.max(greatestParameterIndex, Integer.parseInt(parameterIndexString)); } return greatestParameterIndex; } - private static void checkAndRegister(ParameterBinding binding, List bindings) { + private void checkAndRegister(ParameterBinding binding, List bindings) { for (ParameterBinding existing : bindings) { if (existing.hasName(binding.getName()) || existing.hasPosition(binding.getPosition())) { @@ -835,63 +795,4 @@ class StringQuery implements DeclaredQuery { } } - /** - * Value object to analyze a String to determine the parts of the String that are quoted and offers an API to query - * that information. - * - * @author Jens Schauder - * @since 3.0.3 - */ - static class QuotationMap { - - private static final Set QUOTING_CHARACTERS = new HashSet<>(Arrays.asList('"', '\'')); - - private List> quotedRanges = new ArrayList<>(); - - QuotationMap(@Nullable String query) { - - if (query == null) { - return; - } - - Character inQuotation = null; - int start = 0; - - for (int i = 0; i < query.length(); i++) { - - char currentChar = query.charAt(i); - - if (QUOTING_CHARACTERS.contains(currentChar)) { - - if (inQuotation == null) { - - inQuotation = currentChar; - start = i; - - } else if (currentChar == inQuotation) { - - inQuotation = null; - quotedRanges.add(Range.of(Range.Bound.inclusive(start), Range.Bound.inclusive(i))); - } - } - } - - if (inQuotation != null) { - throw new IllegalArgumentException( - String.format("The string <%s> starts a quoted range at %d, but never ends it.", query, start)); - } - } - - /** - * @param index to check if it is part of a quoted range. - * @return whether the query contains a quoted range at {@literal index}. - */ - public boolean isQuoted(int index) { - return quotedRanges.stream().anyMatch(r -> r.contains(index)); - } - } - - static class Metadata { - private boolean usesJdbcStyleParameters = false; - } } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java index 33015e5ed..f634d52b0 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterBindingParserUnitTests.java @@ -69,10 +69,9 @@ public class ParameterBindingParserUnitTests { public void checkHasParameter(SoftAssertions softly, String query, boolean containsParameter, String label) { - List bindings = new ArrayList<>(); - ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, bindings, - new StringQuery.Metadata()); - softly.assertThat(bindings.size()) // + StringQuery stringQuery = new StringQuery(query); + + softly.assertThat(stringQuery.getParameterBindings().size()) // .describedAs(String.format("<%s> (%s)", query, label)) // .isEqualTo(containsParameter ? 1 : 0); } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QuotationMapUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QuotationMapUnitTests.java deleted file mode 100644 index b71bb6f57..000000000 --- a/src/test/java/org/springframework/data/jpa/repository/query/QuotationMapUnitTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2018 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jpa.repository.query; - -import static org.assertj.core.api.Assertions.*; - -import org.assertj.core.api.SoftAssertions; -import org.junit.Test; -import org.springframework.data.jpa.repository.query.StringQuery.QuotationMap; - -/** - * Unit tests for {@link QuotationMap}. - * - * @author Jens Schauder - */ -public class QuotationMapUnitTests { - - SoftAssertions softly = new SoftAssertions(); - - @Test // DATAJPA-1235 - public void emptyStringDoesNotContainQuotes() { - isNotQuoted("", "empty String", -1, 0, 1); - } - - @Test // DATAJPA-1235 - public void nullStringDoesNotContainQuotes() { - isNotQuoted(null, "null String", -1, 0, 1); - } - - @Test // DATAJPA-1235 - public void simpleStringDoesNotContainQuotes() { - String query = "something"; - isNotQuoted(query, "simple String", -1, 0, query.length() - 1, query.length(), query.length() + 1); - } - - @Test // DATAJPA-1235 - public void fullySingleQuotedStringDoesContainQuotes() { - - String query = "'something'"; - isNotQuoted(query, "quoted String", -1, query.length()); - isQuoted(query, "quoted String", 0, 1, 5, query.length() - 1); - } - - @Test // DATAJPA-1235 - public void fullyDoubleQuotedStringDoesContainQuotes() { - - String query = "\"something\""; - isNotQuoted(query, "double quoted String", -1, query.length()); - isQuoted(query, "double quoted String", 0, 1, 5, query.length() - 1); - } - - @Test // DATAJPA-1235 - public void stringWithEmptyQuotes() { - - String query = "abc''def"; - isNotQuoted(query, "zero length quote", -1, 0, 1, 2, 5, 6, 7); - isQuoted(query, "zero length quote", 3, 4); - } - - @Test // DATAJPA-1235 - public void doubleInSingleQuotes() { - - String query = "abc'\"'def"; - isNotQuoted(query, "double inside single quote", -1, 0, 1, 2, 6, 7, 8); - isQuoted(query, "double inside single quote", 3, 4, 5); - } - - @Test // DATAJPA-1235 - public void singleQuotesInDoubleQuotes() { - - String query = "abc\"'\"def"; - isNotQuoted(query, "single inside double quote", -1, 0, 1, 2, 6, 7, 8); - isQuoted(query, "single inside double quote", 3, 4, 5); - } - - @Test // DATAJPA-1235 - public void escapedQuotes() { - - String query = "a'b''cd''e'f"; - isNotQuoted(query, "escaped quote", -1, 0, 11, 12); - isQuoted(query, "escaped quote", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - } - - @Test // DATAJPA-1235 - public void openEndedQuoteThrowsException() { - - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new QuotationMap("a'b")); - } - - public void isNotQuoted(String query, Object label, int... indexes) { - - QuotationMap quotationMap = new QuotationMap(query); - - for (int index : indexes) { - - assertThat(quotationMap.isQuoted(index)) - .describedAs(String.format("(%s) %s does not contain a quote at %s", label, query, index)) // - .isFalse(); - } - } - - public void isQuoted(String query, Object label, int... indexes) { - - QuotationMap quotationMap = new QuotationMap(query); - - for (int index : indexes) { - - assertThat(quotationMap.isQuoted(index)) - .describedAs(String.format("(%s) %s does contain a quote at %s", label, query, index)).isTrue(); - } - } -} diff --git a/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index e412bd91f..daca39991 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -58,7 +58,8 @@ public class StringQueryUnitTests { LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0); assertThat(binding.getType()).isEqualTo(Type.LIKE); - assertThat(binding.hasName("firstname")).isEqualTo(true); + + assertThat(binding.hasName("firstname")).isTrue(); } @Test // DATAJPA-292 @@ -114,6 +115,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertNamedBinding(InParameterBinding.class, "ids", bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-461 @@ -131,6 +134,8 @@ public class StringQueryUnitTests { assertNamedBinding(InParameterBinding.class, "ids", bindings.get(0)); assertNamedBinding(InParameterBinding.class, "names", bindings.get(1)); assertNamedBinding(ParameterBinding.class, "bar", bindings.get(2)); + + softly.assertAll(); } @Test // DATAJPA-461 @@ -146,6 +151,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertPositionalBinding(InParameterBinding.class, 1, bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-461 @@ -163,6 +170,8 @@ public class StringQueryUnitTests { assertPositionalBinding(InParameterBinding.class, 1, bindings.get(0)); assertPositionalBinding(InParameterBinding.class, 2, bindings.get(1)); assertPositionalBinding(ParameterBinding.class, 3, bindings.get(2)); + + softly.assertAll(); } @Test // DATAJPA-373 @@ -183,6 +192,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertPositionalBinding(ParameterBinding.class, 1, bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-473 @@ -196,8 +207,12 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(2); assertNamedBinding(LikeParameterBinding.class, "escapedWord", bindings.get(0)); assertNamedBinding(ParameterBinding.class, "word", bindings.get(1)); - assertThat(query.getQueryString()).isEqualTo("SELECT a FROM Article a WHERE a.overview LIKE :escapedWord ESCAPE '~'" - + " OR a.content LIKE :escapedWord ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC"); + + softly.assertThat(query.getQueryString()) + .isEqualTo("SELECT a FROM Article a WHERE a.overview LIKE :escapedWord ESCAPE '~'" + + " OR a.content LIKE :escapedWord ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC"); + + softly.assertAll(); } @Test // DATAJPA-483 @@ -209,6 +224,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertNamedBinding(InParameterBinding.class, "statuses", bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-545 @@ -220,6 +237,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertNamedBinding(InParameterBinding.class, "abonnés", bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-545 @@ -231,6 +250,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertNamedBinding(InParameterBinding.class, "øre", bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-545 @@ -242,6 +263,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertNamedBinding(InParameterBinding.class, "생일", bindings.get(0)); + + softly.assertAll(); } @Test // DATAJPA-545 @@ -253,6 +276,8 @@ public class StringQueryUnitTests { assertThat(bindings).hasSize(1); assertNamedBinding(InParameterBinding.class, "ab1babc생일233", bindings.get(0)); + + softly.assertAll(); } @Test(expected = IllegalArgumentException.class) // DATAJPA-362 @@ -275,15 +300,21 @@ public class StringQueryUnitTests { StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}"); String queryString = query.getQueryString(); - assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2"); + softly.assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2"); + softly.assertThat(query.getParameterBindings().get(0).getExpression()).isEqualTo("#bs"); + softly.assertThat(query.getParameterBindings().get(1).getExpression()).isEqualTo("#cs"); + + softly.assertAll(); } @Test // DATAJPA-864 public void detectsConstructorExpressions() { - assertThat(new StringQuery("select new Dto(a.foo, a.bar) from A a").hasConstructorExpression()).isTrue(); - assertThat(new StringQuery("select new Dto (a.foo, a.bar) from A a").hasConstructorExpression()).isTrue(); - assertThat(new StringQuery("select a from A a").hasConstructorExpression()).isFalse(); + softly.assertThat(new StringQuery("select new Dto(a.foo, a.bar) from A a").hasConstructorExpression()).isTrue(); + softly.assertThat(new StringQuery("select new Dto (a.foo, a.bar) from A a").hasConstructorExpression()).isTrue(); + softly.assertThat(new StringQuery("select a from A a").hasConstructorExpression()).isFalse(); + + softly.assertAll(); } /** @@ -294,8 +325,10 @@ public class StringQueryUnitTests { public void detectsConstructorExpressionForDefaultConstructor() { // Parentheses required - assertThat(new StringQuery("select new Dto() from A a").hasConstructorExpression()).isTrue(); - assertThat(new StringQuery("select new Dto from A a").hasConstructorExpression()).isFalse(); + softly.assertThat(new StringQuery("select new Dto() from A a").hasConstructorExpression()).isTrue(); + softly.assertThat(new StringQuery("select new Dto from A a").hasConstructorExpression()).isFalse(); + + softly.assertAll(); } @Test // DATAJPA-1179 @@ -304,12 +337,15 @@ public class StringQueryUnitTests { StringQuery query = new StringQuery("select a from A a where a.first = :#{#exp} or a.second = :#{#exp}"); List bindings = query.getParameterBindings(); - assertThat(bindings).isNotEmpty(); + softly.assertThat(bindings).isNotEmpty(); for (ParameterBinding binding : bindings) { - assertThat(binding.getName()).isNotNull(); - assertThat(query.getQueryString()).containsSequence(binding.getName()); + softly.assertThat(binding.getName()).isNotNull(); + softly.assertThat(query.getQueryString()).contains(binding.getName()); + softly.assertThat(binding.getExpression()).isEqualTo("#exp"); } + + softly.assertAll(); } @Test // DATAJPA-1235 @@ -526,16 +562,16 @@ public class StringQueryUnitTests { private void assertPositionalBinding(Class bindingType, Integer position, ParameterBinding expectedBinding) { - assertThat(bindingType.isInstance(expectedBinding)).isTrue(); - assertThat(expectedBinding).isNotNull(); - assertThat(expectedBinding.hasPosition(position)).isTrue(); + softly.assertThat(bindingType.isInstance(expectedBinding)).isTrue(); + softly.assertThat(expectedBinding).isNotNull(); + softly.assertThat(expectedBinding.hasPosition(position)).isTrue(); } private void assertNamedBinding(Class bindingType, String parameterName, ParameterBinding expectedBinding) { - assertThat(bindingType.isInstance(expectedBinding)).isTrue(); - assertThat(expectedBinding).isNotNull(); - assertThat(expectedBinding.hasName(parameterName)).isTrue(); + softly.assertThat(bindingType.isInstance(expectedBinding)).isTrue(); + softly.assertThat(expectedBinding).isNotNull(); + softly.assertThat(expectedBinding.hasName(parameterName)).isTrue(); } }