From 5b9e959b3e23123e221a6d1937448debb5bed016 Mon Sep 17 00:00:00 2001 From: Phil Webb Date: Wed, 24 Aug 2011 17:19:15 +0100 Subject: [PATCH] DATAJPA-92 - Added support for IgnoreCase keyword. The query creation subsystem now supports using IgnoreCase when referencing String parameters, e.g.: findByUsernameIgnoreCase(String username); Both 'IgnoreCase' and 'IgnoringCase' are supported. If you'd like to entirely ignore cases for all String property references add 'AllIgnoreCase' or 'AllIgnoringCase' to the query method. --- .../jpa/repository/query/JpaQueryCreator.java | 146 +++++++++++++----- .../repository/UserRepositoryFinderTests.java | 22 +++ .../jpa/repository/sample/UserRepository.java | 11 ++ 3 files changed, 137 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 04395e7ac..c887ab5ed 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -37,6 +37,7 @@ import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.query.parser.Property; import org.springframework.util.Assert; @@ -173,50 +174,9 @@ public class JpaQueryCreator extends * @param iterator * @return */ - @SuppressWarnings({ "unchecked", "rawtypes" }) private Predicate toPredicate(Part part, Root root) { - Property property = part.getProperty(); - Expression path = toExpressionRecursively(root, property); - - switch (part.getType()) { - - case BETWEEN: - ParameterExpression first = provider.next(); - ParameterExpression second = provider.next(); - return builder.between( - root. get(part.getProperty().toDotPath()), - first, second); - case GREATER_THAN: - return builder.greaterThan(getComparablePath(root, part), - provider.next(Comparable.class)); - case LESS_THAN: - return builder.lessThan(getComparablePath(root, part), - provider.next(Comparable.class)); - case IS_NULL: - return path.isNull(); - case IS_NOT_NULL: - return path.isNotNull(); - case NOT_IN: - return path.in(provider.next(Collection.class)).not(); - case IN: - return path.in(provider.next(Collection.class)); - case LIKE: - return builder.like( - root. get(part.getProperty().toDotPath()), - provider.next(String.class)); - case NOT_LIKE: - return builder.like( - root. get(part.getProperty().toDotPath()), - provider.next(String.class)).not(); - case SIMPLE_PROPERTY: - return builder.equal(path, provider.next()); - case NEGATING_SIMPLE_PROPERTY: - return builder.notEqual(path, provider.next()); - default: - throw new IllegalArgumentException("Unsupported keyword + " - + part.getType()); - } + return new PredicateBuilder(part, root).build(); } @@ -352,4 +312,106 @@ public class JpaQueryCreator extends return (ParameterExpression) expression; } } + + /** + * Simple builder to contain logic to create JPA {@link Predicate}s from + * {@link Part}s. + * + * @author Phil Webb + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private class PredicateBuilder { + + private final Part part; + private final Root root; + + + /** + * Creates a new {@link PredicateBuilder} for the given {@link Part} and + * {@link Root}. + * + * @param part must not be {@literal null}. + * @param root must not be {@literal null}. + */ + public PredicateBuilder(Part part, Root root) { + + Assert.notNull(part); + Assert.notNull(root); + this.part = part; + this.root = root; + } + + + /** + * Builds a JPA {@link Predicate} from the underlying {@link Part}. + * + * @return + */ + public Predicate build() { + + Property property = part.getProperty(); + Expression path = toExpressionRecursively(root, property); + + switch (part.getType()) { + case BETWEEN: + ParameterExpression first = provider.next(); + ParameterExpression second = provider.next(); + return builder.between( + root. get(part.getProperty().toDotPath()), + first, second); + case GREATER_THAN: + return builder.greaterThan(getComparablePath(root, part), + provider.next(Comparable.class)); + case LESS_THAN: + return builder.lessThan(getComparablePath(root, part), + provider.next(Comparable.class)); + case IS_NULL: + return path.isNull(); + case IS_NOT_NULL: + return path.isNotNull(); + case NOT_IN: + return path.in(provider.next(Collection.class)).not(); + case IN: + return path.in(provider.next(Collection.class)); + case LIKE: + case NOT_LIKE: + Expression propertyExpression = + upperIfIgnoreCase(root. get(part.getProperty() + .toDotPath())); + Expression parameterExpression = + upperIfIgnoreCase(provider.next(String.class)); + Predicate like = + builder.like(propertyExpression, parameterExpression); + return part.getType() == Type.LIKE ? like : like.not(); + case SIMPLE_PROPERTY: + return builder.equal(upperIfIgnoreCase(path), + upperIfIgnoreCase(provider.next())); + case NEGATING_SIMPLE_PROPERTY: + return builder.notEqual(upperIfIgnoreCase(path), + upperIfIgnoreCase(provider.next())); + default: + throw new IllegalArgumentException("Unsupported keyword + " + + part.getType()); + } + } + + + /** + * Applies an {@code UPPERCASE} conversion to the given + * {@link Expression} in case the underlying {@link Part} requires + * ignoring case. + * + * @param expression must not be {@literal null}. + * @return + */ + private Expression upperIfIgnoreCase(Expression expression) { + + if (part.shouldIgnoreCase() + && String.class.equals(expression.getJavaType())) { + return (Expression) builder + .upper((Expression) expression); + } + return expression; + } + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java index f9373fb73..b72049ae7 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java @@ -155,4 +155,26 @@ public class UserRepositoryFinderTests { assertThat(result.size(), is(1)); assertThat(result.get(0), is(oliver)); } + + @Test + public void findsByLastnameIgnoringCase() throws Exception { + List result = userRepository.findByLastnameIgnoringCase("BeAUfoRd"); + assertThat(result.size(), is(1)); + assertThat(result.get(0), is(carter)); + } + + @Test + public void findsByLastnameIgnoringCaseLike() throws Exception { + List result = userRepository.findByLastnameIgnoringCaseLike("BeAUfo%"); + assertThat(result.size(), is(1)); + assertThat(result.get(0), is(carter)); + } + + @Test + public void findByLastnameAndFirstnameAllIgnoringCase() throws Exception { + List result = userRepository.findByLastnameAndFirstnameAllIgnoringCase("MaTTheWs","DaVe"); + assertThat(result.size(), is(1)); + assertThat(result.get(0), is(dave)); + } + } diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 15533b8a4..a29ce6ad9 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -215,4 +215,15 @@ public interface UserRepository extends JpaRepository, List findBySpringDataNamedQuery(String lastname); + + + List findByLastnameIgnoringCase(String lastname); + + + List findByLastnameIgnoringCaseLike(String lastname); + + + List findByLastnameAndFirstnameAllIgnoringCase(String lastname, + String firstname); + }