diff --git a/pom.xml b/pom.xml index a230dab8b..069cad460 100644 --- a/pom.xml +++ b/pom.xml @@ -24,11 +24,11 @@ 3.0.2 5.6.0.Final + 4.3 8.0.23 42.2.19 3.0.0-SNAPSHOT 0.10.3 - 4.3 org.hibernate diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index e4f17e4ac..ba9220cc2 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -229,7 +229,7 @@ com.github.jsqlparser jsqlparser - ${jsqlparser.version} + ${jsqlparser} provided true diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java index 7504d8135..7dd4f9ce2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java @@ -15,14 +15,16 @@ */ package org.springframework.data.jpa.repository.query; -import org.springframework.data.domain.Sort; - import java.util.Set; +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; + /** * The implementation of {@link QueryEnhancer} using {@link QueryUtils}. * * @author Diego Krupitza + * @since 2.7.0 */ public class DefaultQueryEnhancer implements QueryEnhancer { @@ -33,17 +35,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer { } @Override - public String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable idAttributes) { - return QueryUtils.getExistsQueryString(entityName, countQueryPlaceHolder, idAttributes); - } - - @Override - public String getQueryString(String template, String entityName) { - return QueryUtils.getQueryString(template, entityName); - } - - @Override - public String applySorting(Sort sort, String alias) { + public String applySorting(Sort sort, @Nullable String alias) { return QueryUtils.applySorting(this.query.getQueryString(), sort, alias); } @@ -53,7 +45,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer { } @Override - public String createCountQueryFor(String countProjection) { + public String createCountQueryFor(@Nullable String countProjection) { return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java index 74fffaa86..ce399d321 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java @@ -69,19 +69,18 @@ class ExpressionBasedStringQuery extends StringQuery { * @param query the original query. Must not be {@literal null}. * @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}. * @param parser Parser for resolving SpEL expressions. Must not be {@literal null}. - * @param nativeQuery + * @param nativeQuery is a given query native or not * @return A query supporting SpEL expressions. */ - static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata, SpelExpressionParser parser, - boolean nativeQuery) { + static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata, + SpelExpressionParser parser, boolean nativeQuery) { return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery); } /** - * @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}.} + * @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}. * @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}. * @param parser Must not be {@literal null}. - * @return */ private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata metadata, SpelExpressionParser parser) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index 220ddc868..3c4d038c0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -15,38 +15,44 @@ */ package org.springframework.data.jpa.repository.query; +import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*; +import static org.springframework.data.jpa.repository.query.QueryUtils.*; + import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Function; -import net.sf.jsqlparser.expression.operators.conditional.AndExpression; -import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; -import net.sf.jsqlparser.schema.Table; -import net.sf.jsqlparser.statement.select.*; -import net.sf.jsqlparser.util.SelectUtils; +import net.sf.jsqlparser.statement.select.OrderByElement; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.SelectItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + import org.springframework.data.domain.Sort; -import org.springframework.data.util.Streamable; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import java.util.*; -import java.util.stream.Collectors; - -import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*; -import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression; - /** * The implementation of {@link QueryEnhancer} using JSqlParser. * * @author Diego Krupitza + * @author Greg Turnquist + * @since 2.7.0 */ public class JSqlParserQueryEnhancer implements QueryEnhancer { - private static final String DEFAULT_TABLE_ALIAS = "x"; - private final DeclaredQuery query; /** @@ -57,40 +63,8 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { } @Override - public String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable idAttributes) { - final Table tableNameWithAlias = getTableWithAlias(entityName, DEFAULT_TABLE_ALIAS); - Function jSqlCount = getJSqlCount(Collections.singletonList(countQueryPlaceHolder), false); + public String applySorting(Sort sort, @Nullable String alias) { - Select select = SelectUtils.buildSelectFromTableAndSelectItems(tableNameWithAlias, - new SelectExpressionItem(jSqlCount)); - - PlainSelect selectBody = (PlainSelect) select.getSelectBody(); - - List equalityExpressions = Streamable.of(idAttributes).stream() // - .map(field -> { - Expression tableNameField = new Column().withTable(tableNameWithAlias).withColumnName(field); - Expression inputField = new Column(":".concat(field)); - return new EqualsTo(tableNameField, inputField); - }).collect(Collectors.toList()); - - if (equalityExpressions.size() > 1) { - AndExpression rootOfWhereClause = concatenateWithAndExpression(equalityExpressions); - selectBody.setWhere(rootOfWhereClause); - } else if (equalityExpressions.size() == 1) { - selectBody.setWhere(equalityExpressions.get(0)); - } - - return selectBody.toString(); - } - - @Override - public String getQueryString(String template, String entityName) { - Assert.hasText(entityName, "Entity name must not be null or empty!"); - return String.format(template, entityName); - } - - @Override - public String applySorting(Sort sort, String alias) { String queryString = query.getQueryString(); Assert.hasText(queryString, "Query must not be null or empty!"); @@ -145,6 +119,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { * @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}. */ Set getSelectionAliases() { + Select selectStatement = parseSelectStatement(this.query.getQueryString()); PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody(); return this.getSelectionAliases(selectBody); @@ -189,7 +164,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { * @return a {@link OrderByElement} containing an order clause. Guaranteed to be not {@literal null}. */ private OrderByElement getOrderClause(final Set joinAliases, final Set selectionAliases, - final String alias, final Sort.Order order) { + @Nullable final String alias, final Sort.Order order) { final OrderByElement orderByElement = new OrderByElement(); orderByElement.setAsc(order.getDirection().isAscending()); @@ -233,7 +208,9 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { * @param query must not be {@literal null}. * @return Might return {@literal null}. */ + @Nullable private String detectAlias(String query) { + Select selectStatement = parseSelectStatement(query); PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody(); return detectAlias(selectBody); @@ -246,13 +223,15 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { * @param selectBody must not be {@literal null}. * @return Might return {@literal null}. */ + @Nullable private static String detectAlias(PlainSelect selectBody) { + Alias alias = selectBody.getFromItem().getAlias(); return alias == null ? null : alias.getName(); } @Override - public String createCountQueryFor(String countProjection) { + public String createCountQueryFor(@Nullable String countProjection) { Assert.hasText(this.query.getQueryString(), "OriginalQuery must not be null or empty!"); @@ -298,6 +277,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { @Override public String getProjection() { + Assert.hasText(query.getQueryString(), "Query must not be null or empty!"); Select selectStatement = parseSelectStatement(query.getQueryString()); @@ -321,6 +301,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { * @return the parsed query */ private static Select parseSelectStatement(String query) { + try { return (Select) CCJSqlParserUtil.parse(query); } catch (JSQLParserException e) { @@ -329,12 +310,13 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { } /** - * Checks whether a given projection only contains a single column definition (aka without functions, etc) + * Checks whether a given projection only contains a single column definition (aka without functions, etc.) * * @param projection the projection to analyse * @return true when the projection only contains a single column definition otherwise false */ private boolean onlyASingleColumnProjection(List projection) { + // this is unfortunately the only way to check without any hacky & hard string regex magic return projection.size() == 1 && projection.get(0) instanceof SelectExpressionItem && (((SelectExpressionItem) projection.get(0)).getExpression()) instanceof Column; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java index 1cc4eb1d6..4fbf0bf9c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java @@ -15,15 +15,10 @@ */ package org.springframework.data.jpa.repository.query; -import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Function; -import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.schema.Column; -import net.sf.jsqlparser.schema.Table; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import java.util.Collections; import java.util.List; @@ -33,58 +28,12 @@ import java.util.stream.Collectors; * A utility class for JSqlParser. * * @author Diego Krupitza + * @author Greg Turnquist + * @since 2.7.0 */ public final class JSqlParserUtils { - private JSqlParserUtils() { - } - - /** - * Generates a JSqlParser table from an entity name and an optional alias name - * - * @param entityName the name of the table - * @param alias the optional alias. Might be {@literal null} or empty - * @return the newly generated table - */ - public static Table getTableWithAlias(String entityName, String alias) { - Table table = new Table(entityName); - return StringUtils.hasText(alias) ? table.withAlias(new Alias(alias)) : table; - } - - /** - * Concatenates a list of expression with AND. - * - * @param expressions the list of expressions to concatenate. Has to be non empty and with size >= 2 - * @return the root of the concatenated expression - */ - public static AndExpression concatenateWithAndExpression(List expressions) { - - if (CollectionUtils.isEmpty(expressions) || expressions.size() == 1) { - throw new IllegalArgumentException( - "The list of expression has to be at least of length 2! Otherwise it is not possible to concatinate with an"); - } - - AndExpression rootAndExpression = new AndExpression(); - AndExpression currentLocation = rootAndExpression; - - // traverse the list with looking 1 element ahead - for (int i = 0; i < expressions.size(); i++) { - Expression currentExpression = expressions.get(i); - if (currentLocation.getLeftExpression() == null) { - currentLocation.setLeftExpression(currentExpression); - } else if (currentLocation.getRightExpression() == null && i == expressions.size() - 1) { - currentLocation.setRightExpression(currentExpression); - } else { - AndExpression nextAndExpression = new AndExpression(); - nextAndExpression.setLeftExpression(currentExpression); - - currentLocation.setRightExpression(nextAndExpression); - currentLocation = (AndExpression) currentLocation.getRightExpression(); - } - } - - return rootAndExpression; - } + private JSqlParserUtils() {} /** * Generates a count function call, based on the {@code countFields}. @@ -94,12 +43,14 @@ public final class JSqlParserUtils { * @return the generated count function call */ public static Function getJSqlCount(final List countFields, final boolean distinct) { + List countColumns = countFields // .stream() // .map(Column::new) // .collect(Collectors.toList()); ExpressionList countExpression = new ExpressionList(countColumns); + return new Function() // .withName("count") // .withParameters(countExpression) // @@ -113,11 +64,12 @@ public final class JSqlParserUtils { * @return the generated lower function call */ public static Function getJSqlLower(String column) { + List expressions = Collections.singletonList(new Column(column)); ExpressionList lowerParamExpression = new ExpressionList(expressions); + return new Function() // .withName("lower") // .withParameters(lowerParamExpression); } - } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 4afd0a79a..c23eef900 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -55,6 +55,7 @@ import org.springframework.util.Assert; * @author Reda.Housni-Alaoui * @author Moritz Becker * @author Andrey Kovalev + * @author Greg Turnquist */ public class JpaQueryCreator extends AbstractQueryCreator, Predicate> { @@ -166,8 +167,10 @@ public class JpaQueryCreator extends AbstractQueryCreator typeToRead = returnedType.getReturnedType(); - query = typeToRead.isInterface() ? query.multiselect(selections) - : query.select((Selection) builder.construct(typeToRead, selections.toArray(new Selection[0]))); + query = typeToRead.isInterface() // + ? query.multiselect(selections) // + : query.select((Selection) builder.construct(typeToRead, // + selections.toArray(new Selection[0]))); } else if (tree.isExistsProjection()) { @@ -239,83 +242,84 @@ public class JpaQueryCreator extends AbstractQueryCreator first = provider.next(part); - ParameterMetadata second = provider.next(part); - return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression()); - case AFTER: - case GREATER_THAN: - return builder.greaterThan(getComparablePath(root, part), - provider.next(part, Comparable.class).getExpression()); - case GREATER_THAN_EQUAL: - return builder.greaterThanOrEqualTo(getComparablePath(root, part), - provider.next(part, Comparable.class).getExpression()); - case BEFORE: - case LESS_THAN: - return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression()); - case LESS_THAN_EQUAL: - return builder.lessThanOrEqualTo(getComparablePath(root, part), - provider.next(part, Comparable.class).getExpression()); - case IS_NULL: - return getTypedPath(root, part).isNull(); - case IS_NOT_NULL: - return getTypedPath(root, part).isNotNull(); - case NOT_IN: - // cast required for eclipselink workaround, see DATAJPA-433 - return upperIfIgnoreCase(getTypedPath(root, part)) - .in((Expression>) provider.next(part, Collection.class).getExpression()).not(); - case IN: - // cast required for eclipselink workaround, see DATAJPA-433 - return upperIfIgnoreCase(getTypedPath(root, part)) - .in((Expression>) provider.next(part, Collection.class).getExpression()); - case STARTING_WITH: - case ENDING_WITH: - case CONTAINING: - case NOT_CONTAINING: + case BETWEEN: + ParameterMetadata first = provider.next(part); + ParameterMetadata second = provider.next(part); + return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression()); + case AFTER: + case GREATER_THAN: + return builder.greaterThan(getComparablePath(root, part), + provider.next(part, Comparable.class).getExpression()); + case GREATER_THAN_EQUAL: + return builder.greaterThanOrEqualTo(getComparablePath(root, part), + provider.next(part, Comparable.class).getExpression()); + case BEFORE: + case LESS_THAN: + return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression()); + case LESS_THAN_EQUAL: + return builder.lessThanOrEqualTo(getComparablePath(root, part), + provider.next(part, Comparable.class).getExpression()); + case IS_NULL: + return getTypedPath(root, part).isNull(); + case IS_NOT_NULL: + return getTypedPath(root, part).isNotNull(); + case NOT_IN: + // cast required for eclipselink workaround, see DATAJPA-433 + return upperIfIgnoreCase(getTypedPath(root, part)) + .in((Expression>) provider.next(part, Collection.class).getExpression()).not(); + case IN: + // cast required for eclipselink workaround, see DATAJPA-433 + return upperIfIgnoreCase(getTypedPath(root, part)) + .in((Expression>) provider.next(part, Collection.class).getExpression()); + case STARTING_WITH: + case ENDING_WITH: + case CONTAINING: + case NOT_CONTAINING: - if (property.getLeafProperty().isCollection()) { + if (property.getLeafProperty().isCollection()) { - Expression> propertyExpression = traversePath(root, property); - ParameterExpression parameterExpression = provider.next(part).getExpression(); + Expression> propertyExpression = traversePath(root, property); + ParameterExpression parameterExpression = provider.next(part).getExpression(); - // Can't just call .not() in case of negation as EclipseLink chokes on that. - return type.equals(NOT_CONTAINING) ? isNotMember(builder, parameterExpression, propertyExpression) - : isMember(builder, parameterExpression, propertyExpression); - } + // Can't just call .not() in case of negation as EclipseLink chokes on that. + return type.equals(NOT_CONTAINING) // + ? isNotMember(builder, parameterExpression, propertyExpression) // + : isMember(builder, parameterExpression, propertyExpression); + } - case LIKE: - case NOT_LIKE: - Expression stringPath = getTypedPath(root, part); - Expression propertyExpression = upperIfIgnoreCase(stringPath); - Expression parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression()); - Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter()); - return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like; - case TRUE: - Expression truePath = getTypedPath(root, part); - return builder.isTrue(truePath); - case FALSE: - Expression falsePath = getTypedPath(root, part); - return builder.isFalse(falsePath); - case SIMPLE_PROPERTY: - ParameterMetadata expression = provider.next(part); - Expression path = getTypedPath(root, part); - return expression.isIsNullParameter() ? path.isNull() - : builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression())); - case NEGATING_SIMPLE_PROPERTY: - return builder.notEqual(upperIfIgnoreCase(getTypedPath(root, part)), - upperIfIgnoreCase(provider.next(part).getExpression())); - case IS_EMPTY: - case IS_NOT_EMPTY: + case LIKE: + case NOT_LIKE: + Expression stringPath = getTypedPath(root, part); + Expression propertyExpression = upperIfIgnoreCase(stringPath); + Expression parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression()); + Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter()); + return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like; + case TRUE: + Expression truePath = getTypedPath(root, part); + return builder.isTrue(truePath); + case FALSE: + Expression falsePath = getTypedPath(root, part); + return builder.isFalse(falsePath); + case SIMPLE_PROPERTY: + ParameterMetadata expression = provider.next(part); + Expression path = getTypedPath(root, part); + return expression.isIsNullParameter() ? path.isNull() + : builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression())); + case NEGATING_SIMPLE_PROPERTY: + return builder.notEqual(upperIfIgnoreCase(getTypedPath(root, part)), + upperIfIgnoreCase(provider.next(part).getExpression())); + case IS_EMPTY: + case IS_NOT_EMPTY: - if (!property.getLeafProperty().isCollection()) { - throw new IllegalArgumentException("IsEmpty / IsNotEmpty can only be used on collection properties!"); - } + if (!property.getLeafProperty().isCollection()) { + throw new IllegalArgumentException("IsEmpty / IsNotEmpty can only be used on collection properties!"); + } - Expression> collectionPath = traversePath(root, property); - return type.equals(IS_NOT_EMPTY) ? builder.isNotEmpty(collectionPath) : builder.isEmpty(collectionPath); + Expression> collectionPath = traversePath(root, property); + return type.equals(IS_NOT_EMPTY) ? builder.isNotEmpty(collectionPath) : builder.isEmpty(collectionPath); - default: - throw new IllegalArgumentException("Unsupported keyword " + type); + default: + throw new IllegalArgumentException("Unsupported keyword " + type); } } @@ -340,22 +344,22 @@ public class JpaQueryCreator extends AbstractQueryCreator) builder.upper((Expression) expression); - - case WHEN_POSSIBLE: - - if (canUpperCase(expression)) { + Assert.state(canUpperCase(expression), "Unable to ignore case of " + expression.getJavaType().getName() + + " types, the property '" + part.getProperty().getSegment() + "' must reference a String"); return (Expression) builder.upper((Expression) expression); - } - case NEVER: - default: + case WHEN_POSSIBLE: - return (Expression) expression; + if (canUpperCase(expression)) { + return (Expression) builder.upper((Expression) expression); + } + + case NEVER: + default: + + return (Expression) expression; } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java index b8fba6e99..1c470ae67 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java @@ -24,27 +24,11 @@ import org.springframework.lang.Nullable; * This interface describes the API for enhancing a given Query. * * @author Diego Krupitza + * @author Greg Turnquist + * @since 2.7.0 */ public interface QueryEnhancer { - /** - * Returns the query string to execute an exists query for the given id attributes. - * - * @param entityName the name of the entity to create the query for, must not be {@literal null}. - * @param countQueryPlaceHolder the placeholder for the count clause, must not be {@literal null}. - * @param idAttributes the id attributes for the entity, must not be {@literal null}. - */ - String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable idAttributes); - - /** - * Returns the query string for the given class name. - * - * @param template must not be {@literal null}. - * @param entityName must not be {@literal null}. - * @return the template with placeholders replaced by the {@literal entityName}. Guaranteed to be not {@literal null}. - */ - String getQueryString(String template, String entityName); - /** * Adds {@literal order by} clause to the JPQL query. Uses the first alias to bind the sorting property to. * @@ -110,7 +94,7 @@ public interface QueryEnhancer { /** * Gets the query we want to use for enhancements. * - * @return non null {@link DeclaredQuery} that wraps the query + * @return non-null {@link DeclaredQuery} that wraps the query */ DeclaredQuery getQuery(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java index d6123ddee..156a49aee 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java @@ -22,6 +22,8 @@ import org.apache.commons.logging.LogFactory; * Encapsulates different strategies for the creation of a {@link QueryEnhancer} from a {@link DeclaredQuery}. * * @author Diego Krupitza + * @author Greg Turnquist + * @since 2.7.0 */ public final class QueryEnhancerFactory { @@ -29,8 +31,7 @@ public final class QueryEnhancerFactory { private static final boolean JSQLPARSER_IN_CLASSPATH = isJSqlParserInClassPath(); - private QueryEnhancerFactory() { - } + private QueryEnhancerFactory() {} /** * Creates a new {@link QueryEnhancer} for the given {@link DeclaredQuery}. @@ -39,6 +40,7 @@ public final class QueryEnhancerFactory { * @return an implementation of {@link QueryEnhancer} that suits the query the most */ public static QueryEnhancer forQuery(DeclaredQuery query) { + if (qualifiesForJSqlParserUsage(query)) { return new JSqlParserQueryEnhancer(query); } else { @@ -63,6 +65,7 @@ public final class QueryEnhancerFactory { * @return true when in classpath otherwise false */ private static boolean isJSqlParserInClassPath() { + try { Class.forName("net.sf.jsqlparser.parser.JSqlParser", false, QueryEnhancerFactory.class.getClassLoader()); LOG.info("JSqlParser is in classpath. If applicable JSqlParser will be used."); @@ -71,5 +74,4 @@ public final class QueryEnhancerFactory { return false; } } - } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java index d14fea67e..8bc189e31 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java @@ -32,7 +32,6 @@ import jakarta.persistence.criteria.ParameterExpression; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -49,8 +48,7 @@ interface QueryParameterSetter { void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling); /** Noop implementation */ - QueryParameterSetter NOOP = (query, values, errorHandling) -> { - }; + QueryParameterSetter NOOP = (query, values, errorHandling) -> {}; /** * {@link QueryParameterSetter} for named or indexed parameters that might have a {@link TemporalType} specified. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index dc20aad83..d68068082 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -666,8 +666,8 @@ public abstract class QueryUtils { } /** - * Checks if this attribute requires an outer join. This is the case eg. if it hadn't already been fetched with an - * inner join and if it's an a optional association, and if previous paths has already required outer joins. It also + * Checks if this attribute requires an outer join. This is the case e.g. if it hadn't already been fetched with an + * inner join and if it's an optional association, and if previous paths has already required outer joins. It also * ensures outer joins are used even when Hibernate defaults to inner joins (HHH-12712 and HHH-12999). * * @param from the {@link From} to check for fetches. @@ -741,6 +741,7 @@ public abstract class QueryUtils { return hasRequiredOuterJoin || getAnnotationProperty(attribute, "optional", true); } + @Nullable private static T getAnnotationProperty(Attribute attribute, String propertyName, T defaultValue) { Class associationAnnotation = ASSOCIATION_TYPES.get(attribute.getPersistentAttributeType()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 37f3af731..60ca72af3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -101,10 +101,9 @@ class StringQuery implements DeclaredQuery { } @Override - @SuppressWarnings("deprecation") public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable String countQueryProjection) { - return DeclaredQuery.of( + return DeclaredQuery.of( // countQuery != null ? countQuery : this.queryEnhancer.createCountQueryFor(countQueryProjection), // this.isNative); } @@ -259,36 +258,37 @@ class StringQuery implements DeclaredQuery { switch (ParameterBindingType.of(typeSource)) { - case LIKE: + case LIKE: - Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2)); - 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); - } else { - checkAndRegister(new LikeParameterBinding(parameterName, likeType, expression), bindings); + if (parameterIndex != null) { + checkAndRegister(new LikeParameterBinding(parameterIndex, likeType, expression), bindings); + } else { + checkAndRegister(new LikeParameterBinding(parameterName, likeType, expression), bindings); - replacement = ":" + parameterName; - } + replacement = ":" + parameterName; + } - break; + break; - case IN: + case IN: - if (parameterIndex != null) { - checkAndRegister(new InParameterBinding(parameterIndex, expression), bindings); - } else { - checkAndRegister(new InParameterBinding(parameterName, expression), bindings); - } + if (parameterIndex != null) { + checkAndRegister(new InParameterBinding(parameterIndex, expression), bindings); + } else { + checkAndRegister(new InParameterBinding(parameterName, expression), bindings); + } - break; + break; - case AS_IS: // fall-through we don't need a special parameter binding for the given parameter. - default: + case AS_IS: // fall-through we don't need a special parameter binding for the given parameter. + default: - bindings.add(parameterIndex != null ? new ParameterBinding(null, parameterIndex, expression) - : new ParameterBinding(parameterName, null, expression)); + bindings.add(parameterIndex != null // + ? new ParameterBinding(null, parameterIndex, expression) // + : new ParameterBinding(parameterName, null, expression)); } if (replacement != null) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index b9b7e1cf2..52dc7c730 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -57,8 +57,6 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; -import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; -import org.springframework.data.domain.ExampleMatcher.StringMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -103,12 +101,10 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class UserRepositoryTests { - @PersistenceContext - EntityManager em; + @PersistenceContext EntityManager em; // CUT - @Autowired - UserRepository repository; + @Autowired UserRepository repository; // Test fixture private User firstUser; @@ -147,7 +143,7 @@ public class UserRepositoryTests { } @Test - void testRead() throws Exception { + void testRead() { flushTestUsers(); @@ -172,7 +168,7 @@ public class UserRepositoryTests { } @Test - void savesCollectionCorrectly() throws Exception { + void savesCollectionCorrectly() { assertThat(repository.saveAll(asList(firstUser, secondUser, thirdUser))) // .containsExactlyInAnyOrder(firstUser, secondUser, thirdUser); @@ -186,7 +182,7 @@ public class UserRepositoryTests { } @Test - void savingEmptyCollectionIsNoOp() throws Exception { + void savingEmptyCollectionIsNoOp() { assertThat(repository.saveAll(new ArrayList<>())).isEmpty(); } @@ -207,7 +203,7 @@ public class UserRepositoryTests { } @Test - void existReturnsWhetherAnEntityCanBeLoaded() throws Exception { + void existReturnsWhetherAnEntityCanBeLoaded() { flushTestUsers(); assertThat(repository.existsById(id)).isTrue(); @@ -237,7 +233,7 @@ public class UserRepositoryTests { } @Test - void returnsAllSortedCorrectly() throws Exception { + void returnsAllSortedCorrectly() { flushTestUsers(); @@ -246,7 +242,7 @@ public class UserRepositoryTests { } @Test // DATAJPA-296 - void returnsAllIgnoreCaseSortedCorrectly() throws Exception { + void returnsAllIgnoreCaseSortedCorrectly() { flushTestUsers(); @@ -306,7 +302,7 @@ public class UserRepositoryTests { } @Test - void executesManipulatingQuery() throws Exception { + void executesManipulatingQuery() { flushTestUsers(); repository.renameAllUsersTo("newLastname"); @@ -320,11 +316,11 @@ public class UserRepositoryTests { flushTestUsers(); - repository.findByLastname((String) null); + repository.findByLastname(null); } @Test - void testFindByLastname() throws Exception { + void testFindByLastname() { flushTestUsers(); @@ -335,7 +331,7 @@ public class UserRepositoryTests { * Tests, that searching by the email address of the reference user returns exactly that instance. */ @Test - void testFindByEmailAddress() throws Exception { + void testFindByEmailAddress() { flushTestUsers(); @@ -358,7 +354,7 @@ public class UserRepositoryTests { * Tests that all users get deleted by triggering {@link UserRepository#deleteAll()}. */ @Test - void deleteAll() throws Exception { + void deleteAll() { flushTestUsers(); @@ -472,14 +468,14 @@ public class UserRepositoryTests { } @Test - void executesSingleEntitySpecificationCorrectly() throws Exception { + void executesSingleEntitySpecificationCorrectly() { flushTestUsers(); assertThat(repository.findOne(userHasFirstname("Oliver"))).contains(firstUser); } @Test - void returnsNullIfNoEntityFoundForSingleEntitySpecification() throws Exception { + void returnsNullIfNoEntityFoundForSingleEntitySpecification() { flushTestUsers(); assertThat(repository.findOne(userHasLastname("Beauford"))).isNotPresent(); @@ -524,7 +520,7 @@ public class UserRepositoryTests { } @Test - void executesMethodWithAnnotatedNamedParametersCorrectly() throws Exception { + void executesMethodWithAnnotatedNamedParametersCorrectly() { firstUser = repository.save(firstUser); secondUser = repository.save(secondUser); @@ -533,7 +529,7 @@ public class UserRepositoryTests { } @Test - void executesMethodWithNamedParametersCorrectlyOnMethodsWithQueryCreation() throws Exception { + void executesMethodWithNamedParametersCorrectlyOnMethodsWithQueryCreation() { firstUser = repository.save(firstUser); secondUser = repository.save(secondUser); @@ -542,7 +538,7 @@ public class UserRepositoryTests { } @Test - void executesLikeAndOrderByCorrectly() throws Exception { + void executesLikeAndOrderByCorrectly() { flushTestUsers(); @@ -551,7 +547,7 @@ public class UserRepositoryTests { } @Test - void executesNotLikeCorrectly() throws Exception { + void executesNotLikeCorrectly() { flushTestUsers(); @@ -559,7 +555,7 @@ public class UserRepositoryTests { } @Test - void executesSimpleNotCorrectly() throws Exception { + void executesSimpleNotCorrectly() { flushTestUsers(); @@ -567,21 +563,21 @@ public class UserRepositoryTests { } @Test - void returnsSameListIfNoSpecGiven() throws Exception { + void returnsSameListIfNoSpecGiven() { flushTestUsers(); assertSameElements(repository.findAll(), repository.findAll((Specification) null)); } @Test - void returnsSameListIfNoSortIsGiven() throws Exception { + void returnsSameListIfNoSortIsGiven() { flushTestUsers(); assertSameElements(repository.findAll(Sort.unsorted()), repository.findAll()); } @Test - void returnsSamePageIfNoSpecGiven() throws Exception { + void returnsSamePageIfNoSpecGiven() { Pageable pageable = PageRequest.of(0, 1); @@ -590,14 +586,14 @@ public class UserRepositoryTests { } @Test - void returnsAllAsPageIfNoPageableIsGiven() throws Exception { + void returnsAllAsPageIfNoPageableIsGiven() { flushTestUsers(); assertThat(repository.findAll(Pageable.unpaged())).isEqualTo(new PageImpl<>(repository.findAll())); } @Test - void removeDetachedObject() throws Exception { + void removeDetachedObject() { flushTestUsers(); @@ -608,14 +604,14 @@ public class UserRepositoryTests { } @Test - void executesPagedSpecificationsCorrectly() throws Exception { + void executesPagedSpecificationsCorrectly() { Page result = executeSpecWithSort(Sort.unsorted()); assertThat(result.getContent()).isSubsetOf(firstUser, thirdUser); } @Test - void executesPagedSpecificationsWithSortCorrectly() throws Exception { + void executesPagedSpecificationsWithSortCorrectly() { Page result = executeSpecWithSort(Sort.by(Direction.ASC, "lastname")); @@ -623,7 +619,7 @@ public class UserRepositoryTests { } @Test - void executesPagedSpecificationWithSortCorrectly2() throws Exception { + void executesPagedSpecificationWithSortCorrectly2() { Page result = executeSpecWithSort(Sort.by(Direction.DESC, "lastname")); @@ -631,7 +627,7 @@ public class UserRepositoryTests { } @Test - void executesQueryMethodWithDeepTraversalCorrectly() throws Exception { + void executesQueryMethodWithDeepTraversalCorrectly() { flushTestUsers(); @@ -644,7 +640,7 @@ public class UserRepositoryTests { } @Test - void executesFindByColleaguesLastnameCorrectly() throws Exception { + void executesFindByColleaguesLastnameCorrectly() { flushTestUsers(); @@ -658,7 +654,7 @@ public class UserRepositoryTests { } @Test - void executesFindByNotNullLastnameCorrectly() throws Exception { + void executesFindByNotNullLastnameCorrectly() { flushTestUsers(); @@ -666,7 +662,7 @@ public class UserRepositoryTests { } @Test - void executesFindByNullLastnameCorrectly() throws Exception { + void executesFindByNullLastnameCorrectly() { flushTestUsers(); User forthUser = repository.save(new User("Foo", null, "email@address.com")); @@ -675,7 +671,7 @@ public class UserRepositoryTests { } @Test - void findsSortedByLastname() throws Exception { + void findsSortedByLastname() { flushTestUsers(); @@ -1676,7 +1672,7 @@ public class UserRepositoryTests { } @Test // DATAJPA-606 - void findByEmptyCollectionOfStrings() throws Exception { + void findByEmptyCollectionOfStrings() { flushTestUsers(); @@ -1685,7 +1681,7 @@ public class UserRepositoryTests { } @Test // DATAJPA-606 - void findByEmptyCollectionOfIntegers() throws Exception { + void findByEmptyCollectionOfIntegers() { flushTestUsers(); @@ -1694,7 +1690,7 @@ public class UserRepositoryTests { } @Test // DATAJPA-606 - void findByEmptyArrayOfIntegers() throws Exception { + void findByEmptyArrayOfIntegers() { flushTestUsers(); @@ -1736,7 +1732,7 @@ public class UserRepositoryTests { flushTestUsers(); - try (Stream stream = repository.streamAllPaged(PageRequest.of(0, 2));) { + try (Stream stream = repository.streamAllPaged(PageRequest.of(0, 2))) { assertThat(stream).hasSize(2); } } @@ -2088,13 +2084,11 @@ public class UserRepositoryTests { User prototype = new User(); prototype.setFirstname("v"); - assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> { - repository.findBy( - of(prototype, - matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname", - GenericPropertyMatcher::contains)), // - q -> q.sortBy(Sort.by("firstname")).oneValue()); - }); + assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> repository.findBy( + of(prototype, + matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname", + GenericPropertyMatcher::contains)), // + q -> q.sortBy(Sort.by("firstname")).oneValue())); } @Test // GH-2294 @@ -2552,7 +2546,7 @@ public class UserRepositoryTests { } @Test // DATAJPA-1307 - void testFindByEmailAddressJdbcStyleParameter() throws Exception { + void testFindByEmailAddressJdbcStyleParameter() { flushTestUsers(); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java index ac8148850..677cbc4f2 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java @@ -24,7 +24,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - import org.springframework.expression.spel.standard.SpelExpressionParser; /** @@ -42,8 +41,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; class ExpressionBasedStringQueryUnitTests { private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); - @Mock - JpaEntityMetadata metadata; + @Mock JpaEntityMetadata metadata; @Test // DATAJPA-170 void shouldReturnQueryWithDomainTypeExpressionReplacedWithSimpleDomainTypeName() { @@ -93,6 +91,7 @@ class ExpressionBasedStringQueryUnitTests { @Test void shouldDetectComplexNativeQueriesWithSpelAsNonNative() { + StringQuery query = new ExpressionBasedStringQuery( "select n from #{#entityName} n where (LOWER(n.name) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.name},'%')), '')) OR ?#{#networkRequest.name} IS NULL )\"\n" + "+ \"AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)\"\n" @@ -105,16 +104,17 @@ class ExpressionBasedStringQueryUnitTests { @Test void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() { + StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, SPEL_PARSER, true); assertThat(query.isNativeQuery()).isFalse(); } @Test - void shouldDetectSimpleNativeQueriesWithoutSpelAsNonNative() { + void shouldDetectSimpleNativeQueriesWithoutSpelAsNative() { + StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, SPEL_PARSER, true); assertThat(query.isNativeQuery()).isTrue(); } - } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java index 348821256..f6964d9b9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java @@ -35,7 +35,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - import org.springframework.core.annotation.AliasFor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -74,10 +73,8 @@ public class JpaQueryMethodUnitTests { private static final String METHOD_NAME = "findByFirstname"; - @Mock - QueryExtractor extractor; - @Mock - RepositoryMetadata metadata; + @Mock QueryExtractor extractor; + @Mock RepositoryMetadata metadata; private ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); private Method invalidReturnType; @@ -520,7 +517,7 @@ public class JpaQueryMethodUnitTests { interface ValidRepository extends Repository { - @Query(value = "Select u from User u where u.lastname = ?1", nativeQuery = true) + @Query(value = "select u from User u where u.lastname = ?1", nativeQuery = true) List findByLastname(String lastname); @Query(name = "HateoasAwareSpringDataWebConfiguration.bar") diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java index 1eb04239e..a6248e5a6 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java @@ -15,9 +15,9 @@ */ package org.springframework.data.jpa.repository.query; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; /** * Unit tests for {@link QueryEnhancerFactory}. @@ -28,18 +28,22 @@ class QueryEnhancerFactoryUnitTests { @Test void createsDefaultImplementationForNonNativeQuery() { - StringQuery query = new StringQuery("Select new User(u.firstname) from User u", false); + + StringQuery query = new StringQuery("select new User(u.firstname) from User u", false); QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query); + assertThat(queryEnhancer) // .isInstanceOf(DefaultQueryEnhancer.class); } @Test void createsJSqlImplementationForNativeQuery() { - StringQuery query = new StringQuery("Select * from User", true); + + StringQuery query = new StringQuery("select * from User", true); QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query); + assertThat(queryEnhancer) // .isInstanceOf(JSqlParserQueryEnhancer.class); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index 7ba11d74b..e40535c1e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -15,6 +15,14 @@ */ package org.springframework.data.jpa.repository.query; +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -24,16 +32,8 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.JpaSort; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.*; - /** - * Unit test for {@link QueryEnhancer}. + * Unit tests for {@link QueryEnhancer}. * * @author Diego Krupitza */ @@ -114,6 +114,7 @@ class QueryEnhancerUnitTests { } public static Stream detectsAliasWithUCorrectlySource() { + return Stream.of( // Arguments.of(new StringQuery(QUERY, true), "u"), // Arguments.of(new StringQuery(SIMPLE_QUERY, false), "u"), // @@ -130,6 +131,7 @@ class QueryEnhancerUnitTests { void allowsFullyQualifiedEntityNamesInQuery() { StringQuery query = new StringQuery(FQ_QUERY, true); + assertThat(getEnhancer(query).detectAlias()).isEqualTo("u"); assertCountQuery(FQ_QUERY, "select count(u) from org.acme.domain.User$Foo_Bar u", true); } @@ -138,6 +140,7 @@ class QueryEnhancerUnitTests { void doesNotPrefixOrderReferenceIfOuterJoinAliasDetected() { StringQuery query = new StringQuery("select p from Person p left join p.address address", true); + endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("address.city")), "order by address.city asc"); endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("address.city", "lastname"), "p"), "order by address.city asc, p.lastname asc"); @@ -147,6 +150,7 @@ class QueryEnhancerUnitTests { void extendsExistingOrderByClausesCorrectly() { StringQuery query = new StringQuery("select p from Person p order by p.lastname asc", true); + endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("firstname"), "p"), "order by p.lastname asc, p.firstname asc"); } @@ -157,6 +161,7 @@ class QueryEnhancerUnitTests { Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase()); StringQuery query = new StringQuery("select p from Person p", true); + endsIgnoringCase(getEnhancer(query).applySorting(sort, "p"), "order by lower(p.firstname) asc"); } @@ -166,11 +171,12 @@ class QueryEnhancerUnitTests { Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase()); StringQuery query = new StringQuery("select p from Person p order by p.lastname asc", true); + endsIgnoringCase(getEnhancer(query).applySorting(sort, "p"), "order by p.lastname asc, lower(p.firstname) asc"); } @Test // DATAJPA-342 - void usesReturnedVariableInCOuntProjectionIfSet() { + void usesReturnedVariableInCountProjectionIfSet() { assertCountQuery("select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", "select count(distinct m.genre) from Media m where m.user = ?1", true); @@ -185,6 +191,7 @@ class QueryEnhancerUnitTests { @Test // DATAJPA-148 void doesNotPrefixSortsIfFunction() { + StringQuery query = new StringQuery("select p from Person p", true); Sort sort = Sort.by("sum(foo)"); @@ -207,6 +214,7 @@ class QueryEnhancerUnitTests { Sort sort = Sort.by("lastname"); StringQuery originalQuery = new StringQuery("select p from Person p ORDER BY p.firstname", true); String query = getEnhancer(originalQuery).applySorting(sort, "p"); + endsIgnoringCase(query, "ORDER BY p.firstname, p.lastname asc"); } @@ -222,15 +230,17 @@ class QueryEnhancerUnitTests { @Test // DATAJPA-456 void createCountQueryFromTheGivenCountProjection() { + StringQuery query = new StringQuery("select p.lastname,p.firstname from Person p", true); + assertThat(getEnhancer(query).createCountQueryFor("p.lastname")) .isEqualToIgnoringCase("select count(p.lastname) from Person p"); } @Test // DATAJPA-726 - void detectsAliassesInPlainJoins() { + void detectsAliasesInPlainJoins() { - StringQuery query = new StringQuery("select p from Customer c join c.productOrder p where p.delaye = true", true); + StringQuery query = new StringQuery("select p from Customer c join c.productOrder p where p.delay = true", true); Sort sort = Sort.by("p.lineItems"); endsIgnoringCase(getEnhancer(query).applySorting(sort, "c"), "order by p.lineItems asc"); @@ -238,13 +248,17 @@ class QueryEnhancerUnitTests { @Test // DATAJPA-736 void supportsNonAsciiCharactersInEntityNames() { + StringQuery query = new StringQuery("select u from Usèr u", true); + assertThat(getEnhancer(query).createCountQueryFor()).isEqualToIgnoringCase("select count(u) from Usèr u"); } @Test // DATAJPA-798 void detectsAliasInQueryContainingLineBreaks() { + StringQuery query = new StringQuery("select \n u \n from \n User \nu", true); + assertThat(getEnhancer(query).detectAlias()).isEqualTo("u"); } @@ -259,6 +273,7 @@ class QueryEnhancerUnitTests { @Test // DATAJPA-815 void doesPrefixPropertyWithNative() { + StringQuery query = new StringQuery("Select * from Cat c join Dog d", true); Sort sort = Sort.by("dPropertyStartingWithJoinAlias"); @@ -267,7 +282,9 @@ class QueryEnhancerUnitTests { @Test // DATAJPA-938 void detectsConstructorExpressionInDistinctQuery() { + StringQuery query = new StringQuery("select distinct new Foo() from Bar b", false); + assertThat(getEnhancer(query).hasConstructorExpression()).isTrue(); } @@ -286,19 +303,25 @@ class QueryEnhancerUnitTests { @Test // DATAJPA-938 void detectsConstructorExpressionWithLineBreaks() { + StringQuery query = new StringQuery("select new foo.bar.FooBar(\na.id) from DtoA a ", false); + assertThat(getEnhancer(query).hasConstructorExpression()).isTrue(); } @Test // DATAJPA-960 void doesNotQualifySortIfNoAliasDetectedNonNative() { + StringQuery query = new StringQuery("from mytable where ?1 is null", false); + assertThat(getEnhancer(query).applySorting(Sort.by("firstname"))).endsWith("order by firstname asc"); } @Test // DATAJPA-960 void doesNotQualifySortIfNoAliasDetectedNative() { + StringQuery query = new StringQuery("Select * from mytable where ?1 is null", true); + endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("firstname")), "order by firstname asc"); } @@ -308,15 +331,17 @@ class QueryEnhancerUnitTests { StringQuery query = new StringQuery("select p from Person p", true); Sort sort = Sort.by("case when foo then bar"); + assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) .isThrownBy(() -> getEnhancer(query).applySorting(sort, "p")); } @Test // DATAJPA-965, DATAJPA-970 - void doesNotPrefixUnsageJpaSortFunctionCalls() { + void doesNotPrefixUnsafeJpaSortFunctionCalls() { JpaSort sort = JpaSort.unsafe("sum(foo)"); StringQuery query = new StringQuery("select p from Person p", true); + endsIgnoringCase(getEnhancer(query).applySorting(sort, "p"), "order by sum(foo) asc"); } @@ -349,7 +374,7 @@ class QueryEnhancerUnitTests { } @Test // DATAJPA-965, DATAJPA-970 - void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainesAliasedFunctionForDifferentProperty() { + void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainsAliasedFunctionForDifferentProperty() { StringQuery query = new StringQuery("SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m", true); Sort sort = Sort.by("name", "avgPrice"); @@ -464,11 +489,13 @@ class QueryEnhancerUnitTests { @Test void createCountQuerySupportsLineBreaksInSelectClause() { + StringQuery query = new StringQuery("select user.age,\n" + // " user.name\n" + // " from User user\n" + // " where user.age = 18\n" + // " order\nby\nuser.name\n ", true); + assertThat(getEnhancer(query).createCountQueryFor()) .isEqualToIgnoringCase("select count(user) from User user where user.age = 18"); } @@ -562,12 +589,11 @@ class QueryEnhancerUnitTests { @MethodSource("findProjectionClauseWithDistinctSource") void findProjectionClauseWithDistinct(DeclaredQuery query, String expected) { - SoftAssertions.assertSoftly(sofly -> { - sofly.assertThat(getEnhancer(query).getProjection()).isEqualTo(expected); - }); + SoftAssertions.assertSoftly(sofly -> sofly.assertThat(getEnhancer(query).getProjection()).isEqualTo(expected)); } public static Stream findProjectionClauseWithDistinctSource() { + return Stream.of( // Arguments.of(new StringQuery("select * from x", true), "*"), // Arguments.of(new StringQuery("select a, b, c from x", true), "a, b, c"), // @@ -591,17 +617,21 @@ class QueryEnhancerUnitTests { // This is a required behavior the testcase in #findProjectionClauseWithSubselect tells why String queryString = "select * from (select x from y)"; StringQuery query = new StringQuery(queryString, true); + assertThat(getEnhancer(query).getProjection()).isEqualTo("*"); } @Test // DATAJPA-1696 void findProjectionClauseWithIncludedFrom() { + StringQuery query = new StringQuery("select x, frommage, y from t", true); + assertThat(getEnhancer(query).getProjection()).isEqualTo("x, frommage, y"); } @Test - void countProjectionDistrinctQueryIncludesNewLineAfterFromAndBeforeJoin() { + void countProjectionDistinctQueryIncludesNewLineAfterFromAndBeforeJoin() { + StringQuery originalQuery = new StringQuery( "SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true); @@ -611,17 +641,21 @@ class QueryEnhancerUnitTests { @Test void countProjectionDistinctQueryIncludesNewLineAfterEntity() { + StringQuery originalQuery = new StringQuery( "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true); + assertCountQuery(originalQuery, "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key"); } @Test void countProjectionDistinctQueryIncludesNewLineAfterEntityAndBeforeWhere() { + StringQuery originalQuery = new StringQuery( "SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key\nwhere entity1.id = 1799", true); + assertCountQuery(originalQuery, "select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799"); } @@ -651,6 +685,7 @@ class QueryEnhancerUnitTests { @Test // GH-2441 void correctFunctionAliasWithComplexNestedFunctions() { + String queryString = "\nSELECT \nCAST(('{' || string_agg(distinct array_to_string(c.institutes_ids, ','), ',') || '}') AS bigint[]) as institutesIds\nFROM\ncity c"; StringQuery nativeQuery = new StringQuery(queryString, true); @@ -661,9 +696,11 @@ class QueryEnhancerUnitTests { @Test // GH-2441 void correctApplySortOnComplexNestedFunctionQuery() { - String queryString = "SELECT dd.institutesIds FROM (\n" + " SELECT\n" + String queryString = "SELECT dd.institutesIds FROM (\n" // + + " SELECT\n" // + " CAST(('{' || string_agg(distinct array_to_string(c.institutes_ids, ','), ',') || '}') AS bigint[]) as institutesIds\n" - + " FROM\n" + " city c\n" + + " FROM\n" // + + " city c\n" // + " ) dd"; StringQuery nativeQuery = new StringQuery(queryString, true); @@ -671,10 +708,12 @@ class QueryEnhancerUnitTests { QueryEnhancer queryEnhancer = getEnhancer(nativeQuery); String result = queryEnhancer.applySorting(Sort.by(new Sort.Order(Sort.Direction.ASC, "institutesIds"))); + assertThat(result).containsIgnoringCase("order by dd.institutesIds"); } public static Stream detectsJoinAliasesCorrectlySource() { + return Stream.of( // Arguments.of("select p from Person p left outer join x.foo b2_$ar", Collections.singletonList("b2_$ar")), // Arguments.of("select p from Person p left join x.foo b2_$ar", Collections.singletonList("b2_$ar")), // @@ -695,6 +734,7 @@ class QueryEnhancerUnitTests { } private static void endsIgnoringCase(String original, String endWithIgnoreCase) { + // https://github.com/assertj/assertj-core/pull/2451 // can be removed when upgrading to version 3.23.0 assertJ assertThat(original.toUpperCase()).endsWith(endWithIgnoreCase.toUpperCase()); @@ -703,5 +743,4 @@ class QueryEnhancerUnitTests { private static QueryEnhancer getEnhancer(DeclaredQuery query) { return QueryEnhancerFactory.forQuery(query); } - } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java index a0081a413..ee126b039 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactoryUnitTests.java @@ -34,6 +34,7 @@ import org.springframework.data.jpa.repository.query.StringQuery.ParameterBindin * * @author Jens Schauder * @author Mark Paluch + * @author Diego Krupitza */ class QueryParameterSetterFactoryUnitTests { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java index d048d70bb..11776da9f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java @@ -46,7 +46,6 @@ import jakarta.persistence.spi.PersistenceProviderResolverHolder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; - import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.jpa.domain.sample.Category; @@ -66,13 +65,13 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; * @author Sébastien Péralta * @author Jens Schauder * @author Patrice Blanchardie + * @author Diego Krupitza */ @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:infrastructure.xml") public class QueryUtilsIntegrationTests { - @PersistenceContext - EntityManager em; + @PersistenceContext EntityManager em; @Test // DATAJPA-403 void reusesExistingJoinForExpression() { @@ -183,7 +182,7 @@ public class QueryUtilsIntegrationTests { CriteriaQuery query = builder.createQuery(InvoiceItem.class); Root root = query.from(InvoiceItem.class); - // given an existing inner join an nested optional + // given an existing inner join a nested optional root.join("invoice").join("order"); QueryUtils.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class), @@ -237,7 +236,7 @@ public class QueryUtilsIntegrationTests { } @Test // DATAJPA-454 - void createsJoingToTraverseCollectionPath() { + void createsJoinToTraverseCollectionPath() { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(User.class); @@ -319,7 +318,7 @@ public class QueryUtilsIntegrationTests { * https://github.com/javaee/jpa-spec/issues/169 Compare to: {@link EclipseLinkQueryUtilsIntegrationTests} */ @Test // DATAJPA-1238 - void demonstrateDifferentBehavorOfGetJoin() { + void demonstrateDifferentBehaviorOfGetJoin() { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(User.class); @@ -348,40 +347,32 @@ public class QueryUtilsIntegrationTests { @SuppressWarnings("unused") static class Merchant { - @Id - String id; - @OneToMany - Set employees; + @Id String id; + @OneToMany Set employees; - @OneToOne - Address address; + @OneToOne Address address; } @Entity @SuppressWarnings("unused") static class Address { - @Id - String id; - @OneToOne(mappedBy = "address") - Merchant merchant; + @Id String id; + @OneToOne(mappedBy = "address") Merchant merchant; } @Entity @SuppressWarnings("unused") static class Employee { - @Id - String id; - @OneToMany - Set credentials; + @Id String id; + @OneToMany Set credentials; } @Entity @SuppressWarnings("unused") static class Credential { - @Id - String id; + @Id String id; String uid; } @@ -399,8 +390,7 @@ public class QueryUtilsIntegrationTests { } @Override - public void clearCachedProviders() { - } + public void clearCachedProviders() {} } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java index cf7cb8614..d7aaad168 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java @@ -51,7 +51,7 @@ class QueryUtilsUnitTests { private static final String QUERY_WITH_AS = "select u from User as u where u.username = ?"; @Test - void createsCountQueryCorrectly() throws Exception { + void createsCountQueryCorrectly() { assertCountQuery(QUERY, COUNT_QUERY); } @@ -64,47 +64,45 @@ class QueryUtilsUnitTests { } @Test - void createsCountQueryForDistinctQueries() throws Exception { + void createsCountQueryForDistinctQueries() { assertCountQuery("select distinct u from User u where u.foo = ?", "select count(distinct u) from User u where u.foo = ?"); } @Test - void createsCountQueryForConstructorQueries() throws Exception { + void createsCountQueryForConstructorQueries() { assertCountQuery("select distinct new User(u.name) from User u where u.foo = ?", "select count(distinct u) from User u where u.foo = ?"); } @Test - void createsCountQueryForJoins() throws Exception { + void createsCountQueryForJoins() { assertCountQuery("select distinct new User(u.name) from User u left outer join u.roles r WHERE r = ?", "select count(distinct u) from User u left outer join u.roles r WHERE r = ?"); } @Test - void createsCountQueryForQueriesWithSubSelects() throws Exception { + void createsCountQueryForQueriesWithSubSelects() { assertCountQuery("select u from User u left outer join u.roles r where r in (select r from Role)", "select count(u) from User u left outer join u.roles r where r in (select r from Role)"); } @Test - void createsCountQueryForAliasesCorrectly() throws Exception { - + void createsCountQueryForAliasesCorrectly() { assertCountQuery("select u from User as u", "select count(u) from User as u"); } @Test - void allowsShortJpaSyntax() throws Exception { - + void allowsShortJpaSyntax() { assertCountQuery(SIMPLE_QUERY, COUNT_QUERY); } @Test - void detectsAliasCorrectly() throws Exception { + void detectsAliasCorrectly() { assertThat(detectAlias(QUERY)).isEqualTo("u"); assertThat(detectAlias(SIMPLE_QUERY)).isEqualTo("u"); @@ -180,14 +178,14 @@ class QueryUtilsUnitTests { } @Test // DATAJPA-342 - void usesReturnedVariableInCOuntProjectionIfSet() { + void usesReturnedVariableInCountProjectionIfSet() { assertCountQuery("select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", "select count(distinct m.genre) from Media m where m.user = ?1"); } @Test // DATAJPA-343 - void projectsCOuntQueriesForQueriesWithSubselects() { + void projectsCountQueriesForQueriesWithSubselects() { assertCountQuery("select o from Foo o where cb.id in (select b from Bar b)", "select count(o) from Foo o where cb.id in (select b from Bar b)"); @@ -233,7 +231,7 @@ class QueryUtilsUnitTests { } @Test // DATAJPA-726 - void detectsAliassesInPlainJoins() { + void detectsAliasesInPlainJoins() { String query = "select p from Customer c join c.productOrder p where p.delayed = true"; Sort sort = Sort.by("p.lineItems"); @@ -294,7 +292,7 @@ class QueryUtilsUnitTests { } @Test // DATAJPA-965, DATAJPA-970 - void doesNotPrefixUnsageJpaSortFunctionCalls() { + void doesNotPrefixUnsafeJpaSortFunctionCalls() { JpaSort sort = JpaSort.unsafe("sum(foo)"); assertThat(applySorting("select p from Person p", sort, "p")).endsWith("order by sum(foo) asc"); @@ -328,7 +326,7 @@ class QueryUtilsUnitTests { } @Test // DATAJPA-965, DATAJPA-970 - void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainesAliasedFunctionForDifferentProperty() { + void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainsAliasedFunctionForDifferentProperty() { String query = "SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m"; Sort sort = Sort.by("name", "avgPrice"); @@ -400,8 +398,8 @@ class QueryUtilsUnitTests { @Test // DATAJPA-1363 void discoversAliasWithComplexFunction() { - assertThat(QueryUtils - .getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) // + assertThat( + QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) // .contains("myAlias"); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index 926047477..4746f2710 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -23,7 +23,6 @@ import java.util.List; import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; - import org.springframework.data.jpa.repository.query.StringQuery.InParameterBinding; import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding; import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;