diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 64b495f9c..c4ac0a219 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -16,12 +16,12 @@ package org.springframework.data.jpa.repository.query; import javax.persistence.EntityManager; +import javax.persistence.Query; import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution; -import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.util.Assert; @@ -68,19 +68,10 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { } - /** - * @return the parameters - */ - public Parameters getParameters() { - - return method.getParameters(); - } - - /** * @return the em */ - public EntityManager getEntityManager() { + protected EntityManager getEntityManager() { return em; } @@ -99,6 +90,17 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { } + /** + * @param execution + * @param values + * @return + */ + private Object doExecute(JpaQueryExecution execution, Object[] values) { + + return execution.execute(this, values); + } + + protected JpaQueryExecution getExecution() { switch (method.getType()) { @@ -106,7 +108,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { case COLLECTION: return new CollectionExecution(); case PAGING: - return new PagedExecution(getParameters()); + return new PagedExecution(method.getParameters()); case MODIFYING: return method.getClearAutomatically() ? new ModifyingExecution( method, em) : new ModifyingExecution(method, null); @@ -116,7 +118,14 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { } - protected abstract Object doExecute(JpaQueryExecution execution, - Object[] parameters); + protected ParameterBinder createBinder(Object[] values) { + return new ParameterBinder(getQueryMethod().getParameters(), values); + } + + + protected abstract Query createQuery(Object[] values); + + + protected abstract Query createCountQuery(Object[] values); } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 6eda449bd..208d22993 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -16,7 +16,6 @@ package org.springframework.data.jpa.repository.query; import javax.persistence.EntityManager; -import javax.persistence.Query; /** @@ -37,38 +36,4 @@ public abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { super(method, em); } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.AbstractJpaQuery#doExecute - * (org.springframework.data.jpa.repository.query.JpaQueryExecution, - * java.lang.Object[]) - */ - @Override - protected Object doExecute(JpaQueryExecution execution, Object[] parameters) { - - ParameterBinder binder = - new ParameterBinder(getParameters(), parameters); - return execution.execute(this, binder); - } - - - /** - * Create a {@link Query} with the given {@link ParameterBinder}. - * - * @param binder - * @return - */ - protected abstract Query createQuery(ParameterBinder binder); - - - /** - * Create a count {@link Query} with the given {@link ParameterBinder}. - * - * @param binder - * @return - */ - protected abstract Query createCountQuery(ParameterBinder binder); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java new file mode 100644 index 000000000..4a7300993 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java @@ -0,0 +1,108 @@ +/* + * Copyright 2011 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import javax.persistence.Query; +import javax.persistence.criteria.ParameterExpression; + +import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.Parameters; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + + +/** + * Special {@link ParameterBinder} that uses {@link ParameterExpression}s to + * bind query parameters. + * + * @author Oliver Gierke + */ +class CriteriaQueryParameterBinder extends ParameterBinder { + + private final Iterator> expressions; + + + /** + * Creates a new {@link CriteriaQueryParameterBinder} for the given + * {@link Parameters}, values and some {@link ParameterExpression}. + * + * @param parameters + */ + CriteriaQueryParameterBinder(Parameters parameters, Object[] values, + Iterable> expressions) { + + super(parameters, values); + Assert.notNull(expressions); + this.expressions = expressions.iterator(); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.jpa.repository.query.ParameterBinder#bind(javax + * .persistence.Query, org.springframework.data.repository.query.Parameter, + * java.lang.Object, int) + */ + @Override + @SuppressWarnings("unchecked") + protected void bind(Query query, Parameter parameter, Object value, + int position) { + + ParameterExpression expression = + (ParameterExpression) expressions.next(); + + Object valueToBind = + Collection.class.equals(expression.getJavaType()) ? toCollection(value) + : value; + + query.setParameter(expression, valueToBind); + } + + + /** + * Return sthe given argument as {@link Collection} which means it will + * return it as is if it's a {@link Collections}, turn an array into an + * {@link ArrayList} or simply wrap any other value into a single element + * {@link Collections}. + * + * @param value + * @return + */ + private static Collection toCollection(Object value) { + + if (value == null) { + return null; + } + + if (value instanceof Collection) { + return (Collection) value; + } + + if (ObjectUtils.isArray(value)) { + return Arrays.asList(ObjectUtils.toObjectArray(value)); + } + + return Collections.singleton(value); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java index e8fe1cd22..1c0254226 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java @@ -23,6 +23,7 @@ import javax.persistence.criteria.Root; import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.parser.PartTree; @@ -37,14 +38,14 @@ public class JpaCountQueryCreator extends JpaQueryCreator { * Creates a new {@link JpaCountQueryCreator}. * * @param tree - * @param parameters * @param domainClass + * @param accessor * @param em */ - public JpaCountQueryCreator(PartTree tree, ParameterAccessor parameters, - Class domainClass, EntityManager em) { + public JpaCountQueryCreator(PartTree tree, Class domainClass, + ParameterAccessor accessor, Parameters parameters, EntityManager em) { - super(tree, parameters, domainClass, em); + super(tree, domainClass, accessor, parameters, em); } 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 f4b4c3670..b407e889a 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 @@ -15,9 +15,11 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; @@ -25,18 +27,20 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; +import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.ParameterAccessor; +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.PartTree; import org.springframework.data.repository.query.parser.Property; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** @@ -50,24 +54,37 @@ public class JpaQueryCreator extends private final CriteriaBuilder builder; private final Root root; private final CriteriaQuery query; + private final ParameterExpressionProvider provider; /** * Create a new {@link JpaQueryCreator}. * * @param tree - * @param parameters * @param domainClass + * @param accessor * @param em */ - public JpaQueryCreator(PartTree tree, ParameterAccessor parameters, - Class domainClass, EntityManager em) { + public JpaQueryCreator(PartTree tree, Class domainClass, + ParameterAccessor accessor, Parameters parameters, EntityManager em) { - super(tree, parameters); + super(tree, accessor); this.builder = em.getCriteriaBuilder(); this.query = builder.createQuery().distinct(tree.isDistinct()); this.root = query.from(domainClass); + this.provider = + new ParameterExpressionProvider(builder, + parameters.getBindableParameters()); + } + + + /** + * @return the parameterExpressions + */ + public List> getParameterExpressions() { + + return provider.getExpressions(); } @@ -82,7 +99,7 @@ public class JpaQueryCreator extends @Override protected Predicate create(Part part, Iterator iterator) { - return toPredicate(part, root, iterator); + return toPredicate(part, root); } @@ -97,7 +114,7 @@ public class JpaQueryCreator extends @Override protected Predicate and(Part part, Predicate base, Iterator iterator) { - return builder.and(base, toPredicate(part, root, iterator)); + return builder.and(base, toPredicate(part, root)); } @@ -156,42 +173,45 @@ public class JpaQueryCreator extends * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) - private Predicate toPredicate(Part part, Root root, - Iterator iterator) { + private Predicate toPredicate(Part part, Root root) { - Expression path = - toExpressionRecursively(root, part.getProperty()); + 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()), - nextAsComparable(iterator), nextAsComparable(iterator)); + first, second); case GREATER_THAN: return builder.greaterThan(getComparablePath(root, part), - nextAsComparable(iterator)); + provider.next(Comparable.class)); case LESS_THAN: return builder.lessThan(getComparablePath(root, part), - nextAsComparable(iterator)); + provider.next(Comparable.class)); case IS_NULL: return path.isNull(); case IS_NOT_NULL: return path.isNotNull(); case NOT_IN: - return builder.not(path.in(nextAsCollection(iterator))); + return path.in(provider.next(Collection.class)).not(); case IN: - return path.in(nextAsCollection(iterator)); + return path.in(provider.next(Collection.class)); case LIKE: - return builder.like(root. get(part.getProperty() - .toDotPath()), iterator.next().toString()); + return builder.like( + root. get(part.getProperty().toDotPath()), + provider.next(String.class)); case NOT_LIKE: - return builder.not(builder.like(root. get(part - .getProperty().toDotPath()), iterator.next().toString())); + return builder.like( + root. get(part.getProperty().toDotPath()), + provider.next(String.class)).not(); case SIMPLE_PROPERTY: - return builder.equal(path, iterator.next()); + return builder.equal(path, provider.next()); case NEGATING_SIMPLE_PROPERTY: - return builder.notEqual(path, iterator.next()); + return builder.notEqual(path, provider.next()); default: throw new IllegalArgumentException("Unsupported keyword + " + part.getType()); @@ -238,35 +258,97 @@ public class JpaQueryCreator extends return toExpressionRecursively(root, part.getProperty()); } - /** - * Returns the next parameter from the given {@link Iterator} and expects it - * to be a {@link Comparable}. + * Helper class to allow easy creation of {@link ParameterExpression}s. * - * @param iterator - * @return + * @author Oliver Gierke */ - @SuppressWarnings("rawtypes") - private Comparable nextAsComparable(Iterator iterator) { + private static class ParameterExpressionProvider { - Object next = iterator.next(); - Assert.isInstanceOf(Comparable.class, next, - "Parameter has to implement Comparable to be bound correctly!"); - - return (Comparable) next; - } + private final CriteriaBuilder builder; + private final Iterator parameters; + private final List> expressions; - private Collection nextAsCollection(Iterator iterator) { + /** + * Creates a new {@link ParameterExpressionProvider} from the given + * {@link CriteriaBuilder} and {@link Parameters}. + * + * @param builder + * @param parameters + */ + public ParameterExpressionProvider(CriteriaBuilder builder, + Parameters parameters) { - Object next = iterator.next(); + Assert.notNull(builder); + Assert.notNull(parameters); - if (next instanceof Collection) { - return (Collection) next; - } else if (next.getClass().isArray()) { - return CollectionUtils.arrayToList(next); + this.builder = builder; + this.parameters = parameters.iterator(); + this.expressions = new ArrayList>(); } - return Arrays.asList(next); + + /** + * Returns all {@link ParameterExpression}s built. + * + * @return the expressions + */ + public List> getExpressions() { + + return Collections.unmodifiableList(expressions); + } + + + /** + * Builds a new {@link ParameterExpression} for the next + * {@link Parameter}. + * + * @param + * @return + */ + @SuppressWarnings("unchecked") + public ParameterExpression next() { + + Parameter parameter = parameters.next(); + return (ParameterExpression) next(parameter.getType(), + parameter.getName()); + } + + + /** + * Builds a new {@link ParameterExpression} of the given type. Forwards + * the underlying {@link Parameters} as well. + * + * @param + * @param type must not be {@literal null}. + * @return + */ + public ParameterExpression next(Class type) { + + parameters.next(); + return next(type, null); + } + + + /** + * Builds a new {@link ParameterExpression} for the given type and name. + * + * @param + * @param type must not be {@literal null}. + * @param name + * @return + */ + @SuppressWarnings("unchecked") + private ParameterExpression next(Class type, String name) { + + Assert.notNull(type); + + ParameterExpression expression = + name == null ? builder.parameter(type) : builder.parameter( + type, name); + expressions.add(expression); + return (ParameterExpression) expression; + } } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 947345d6f..981348926 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -46,27 +46,13 @@ public abstract class JpaQueryExecution { * @return */ - public Object execute(AbstractStringBasedJpaQuery query, - ParameterBinder binder) { + public Object execute(AbstractJpaQuery query, Object[] values) { Assert.notNull(query); - Assert.notNull(binder); + Assert.notNull(values); try { - return doExecute(query, binder); - } catch (NoResultException e) { - return null; - } - } - - - public Object execute(PartTreeJpaQuery query, Object[] parameters) { - - Assert.notNull(query); - Assert.notNull(parameters); - - try { - return doExecute(query, parameters); + return doExecute(query, values); } catch (NoResultException e) { return null; } @@ -81,12 +67,7 @@ public abstract class JpaQueryExecution { * @param binder * @return */ - protected abstract Object doExecute(AbstractStringBasedJpaQuery query, - ParameterBinder parameters); - - - protected abstract Object doExecute(PartTreeJpaQuery query, - Object[] parameters); + protected abstract Object doExecute(AbstractJpaQuery query, Object[] values); /** * Executes the {@link AbstractStringBasedJpaQuery} to return a simple @@ -95,25 +76,9 @@ public abstract class JpaQueryExecution { static class CollectionExecution extends JpaQueryExecution { @Override - protected Object doExecute(AbstractStringBasedJpaQuery query, - ParameterBinder binder) { + protected Object doExecute(AbstractJpaQuery query, Object[] values) { - return binder.bindAndPrepare(query.createQuery(binder)) - .getResultList(); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute - * (org.springframework.data.jpa.repository.query.DerivedJpaQuery) - */ - @Override - protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) { - - return query.createQuery(parameters).getResultList(); + return query.createQuery(values).getResultList(); } } @@ -134,42 +99,19 @@ public abstract class JpaQueryExecution { @Override @SuppressWarnings("unchecked") - protected Object doExecute(AbstractStringBasedJpaQuery repositoryQuery, - ParameterBinder binder) { + protected Object doExecute(AbstractJpaQuery repositoryQuery, + Object[] values) { // Execute query to compute total - Query projection = - binder.bind(repositoryQuery.createCountQuery(binder)); + Query projection = repositoryQuery.createCountQuery(values); Long total = (Long) projection.getSingleResult(); - Query query = - binder.bindAndPrepare(repositoryQuery.createQuery(binder)); - - return new PageImpl(query.getResultList(), - binder.getPageable(), total); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute - * (org.springframework.data.jpa.repository.query.DerivedJpaQuery, - * java.lang.Object[]) - */ - @Override - @SuppressWarnings("unchecked") - protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) { + Query query = repositoryQuery.createQuery(values); ParameterAccessor accessor = - new ParametersParameterAccessor(this.parameters, parameters); + new ParametersParameterAccessor(parameters, values); - Query countQuery = query.createCountQuery(parameters); - Long total = (Long) countQuery.getSingleResult(); - - Query jpaQuery = query.createQuery(parameters); - return new PageImpl(jpaQuery.getResultList(), + return new PageImpl(query.getResultList(), accessor.getPageable(), total); } } @@ -180,25 +122,9 @@ public abstract class JpaQueryExecution { static class SingleEntityExecution extends JpaQueryExecution { @Override - protected Object doExecute(AbstractStringBasedJpaQuery query, - ParameterBinder binder) { + protected Object doExecute(AbstractJpaQuery query, Object[] values) { - return binder.bind(query.createQuery(binder)).getSingleResult(); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute - * (org.springframework.data.jpa.repository.query.DerivedJpaQuery, - * java.lang.Object[]) - */ - @Override - protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) { - - return query.createQuery(parameters).getSingleResult(); + return query.createQuery(values).getSingleResult(); } } @@ -235,19 +161,10 @@ public abstract class JpaQueryExecution { } - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.query.QueryExecution#doExecute - * (org.springframework.data.repository.query.AbstractRepositoryQuery, - * org.springframework.data.repository.query.ParameterBinder) - */ @Override - protected Object doExecute(AbstractStringBasedJpaQuery query, - ParameterBinder binder) { + protected Object doExecute(AbstractJpaQuery query, Object[] values) { - int result = binder.bind(query.createQuery(binder)).executeUpdate(); + int result = query.createQuery(values).executeUpdate(); if (em != null) { em.clear(); @@ -255,12 +172,5 @@ public abstract class JpaQueryExecution { return result; } - - - @Override - protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) { - - throw new UnsupportedOperationException(); - } } } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index d808fdc2c..be8966b60 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -32,7 +32,7 @@ import org.springframework.data.repository.query.RepositoryQuery; * * @author Oliver Gierke */ -final class NamedQuery extends AbstractStringBasedJpaQuery { +final class NamedQuery extends AbstractJpaQuery { private static final String CANNOT_EXTRACT_QUERY = "Your persistence provider does not support extracting the JPQL query from a " @@ -121,9 +121,10 @@ final class NamedQuery extends AbstractStringBasedJpaQuery { * ) */ @Override - protected Query createQuery(ParameterBinder binder) { + protected Query createQuery(Object[] values) { - return getEntityManager().createNamedQuery(queryName); + Query query = getEntityManager().createNamedQuery(queryName); + return createBinder(values).bindAndPrepare(query); } @@ -136,12 +137,13 @@ final class NamedQuery extends AbstractStringBasedJpaQuery { * ParameterBinder) */ @Override - protected Query createCountQuery(ParameterBinder binder) { + protected Query createCountQuery(Object[] values) { - Query query = createQuery(binder); + Query query = createQuery(values); String queryString = extractor.extractQueryString(query); - return getEntityManager().createQuery( - QueryUtils.createCountQueryFor(queryString)); + return createBinder(values).bind( + getEntityManager().createQuery( + QueryUtils.createCountQueryFor(queryString))); } } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java index 33503d5f3..c1f10b87b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinder.java @@ -115,12 +115,7 @@ public class ParameterBinder { if (parameter.isBindable()) { Object value = values[methodParameterPosition]; - - if (hasNamedParameter(query) && parameter.isNamedParameter()) { - query.setParameter(parameter.getName(), value); - } else { - query.setParameter(queryParameterPosition++, value); - } + bind(query, parameter, value, queryParameterPosition++); } methodParameterPosition++; @@ -130,6 +125,17 @@ public class ParameterBinder { } + protected void bind(Query query, Parameter parameter, Object value, + int position) { + + if (hasNamedParameter(query) && parameter.isNamedParameter()) { + query.setParameter(parameter.getName(), value); + } else { + query.setParameter(position, value); + } + } + + /** * Binds the parameters to the given query and applies special parameter * types (e.g. pagination). diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 16ba11098..ac6872b20 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -15,16 +15,17 @@ */ package org.springframework.data.jpa.repository.query; +import java.util.List; + import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; -import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.core.EntityMetadata; import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.parser.PartTree; @@ -35,8 +36,9 @@ import org.springframework.data.repository.query.parser.PartTree; */ public class PartTreeJpaQuery extends AbstractJpaQuery { + private final Class domainClass; private final PartTree tree; - private final QueryMethod method; + private final Parameters parameters; /** @@ -48,10 +50,10 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) { super(method, em); - this.tree = - new PartTree(method.getName(), method.getEntityInformation() - .getJavaType()); - this.method = method; + + this.domainClass = method.getEntityInformation().getJavaType(); + this.tree = new PartTree(method.getName(), domainClass); + this.parameters = method.getParameters(); } @@ -63,26 +65,21 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { * (javax.persistence.EntityManager, * org.springframework.data.jpa.repository.query.ParameterBinder) */ - public Query createQuery(Object[] parameters) { + @Override + public Query createQuery(Object[] values) { ParameterAccessor accessor = - new ParametersParameterAccessor(getParameters(), parameters); - - EntityMetadata metadata = method.getEntityInformation(); - JpaQueryCreator jpaQueryCreator = - new JpaQueryCreator(tree, accessor, metadata.getJavaType(), + new ParametersParameterAccessor(parameters, values); + JpaQueryCreator creator = + new JpaQueryCreator(tree, domainClass, accessor, parameters, getEntityManager()); + CriteriaQuery source = creator.createQuery(); - TypedQuery query = - getEntityManager().createQuery(jpaQueryCreator.createQuery()); + TypedQuery jpaQuery = getEntityManager().createQuery(source); + getBinder(values, creator.getParameterExpressions()).bindAndPrepare( + jpaQuery); - if (getParameters().hasPageableParameter()) { - Pageable pageable = accessor.getPageable(); - query.setFirstResult(pageable.getOffset()); - query.setMaxResults(pageable.getPageSize()); - } - - return query; + return jpaQuery; } @@ -92,28 +89,27 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery# * createCountQuery(javax.persistence.EntityManager) */ - public Query createCountQuery(Object[] parameters) { + @Override + public Query createCountQuery(Object[] values) { - CriteriaQuery query = - new JpaCountQueryCreator(tree, new ParametersParameterAccessor( - getParameters(), parameters), method - .getEntityInformation().getJavaType(), - getEntityManager()).createQuery(); - return getEntityManager().createQuery(query); + ParameterAccessor accessor = + new ParametersParameterAccessor(parameters, values); + + JpaCountQueryCreator creator = + new JpaCountQueryCreator(tree, domainClass, accessor, + parameters, getEntityManager()); + CriteriaQuery source = creator.createQuery(); + + TypedQuery jpaQuery = getEntityManager().createQuery(source); + getBinder(values, creator.getParameterExpressions()).bind(jpaQuery); + + return jpaQuery; } - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.AbstractJpaQuery#doExecute - * (org.springframework.data.jpa.repository.query.JpaQueryExecution, - * java.lang.Object[]) - */ - @Override - protected Object doExecute(JpaQueryExecution execution, Object[] parameters) { + private ParameterBinder getBinder(Object[] values, + List> expressions) { - return execution.execute(this, parameters); + return new CriteriaQueryParameterBinder(parameters, values, expressions); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java index aa1ab05c8..dabbf5a97 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java @@ -23,6 +23,9 @@ import javax.persistence.QueryHint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; @@ -34,7 +37,7 @@ import org.springframework.data.repository.query.RepositoryQuery; * * @author Oliver Gierke */ -final class SimpleJpaQuery extends AbstractStringBasedJpaQuery { +final class SimpleJpaQuery extends AbstractJpaQuery { private static final Logger LOG = LoggerFactory .getLogger(SimpleJpaQuery.class); @@ -43,6 +46,7 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery { private final String countQuery; private final String alias; private final List hints; + private final Parameters parameters; /** @@ -55,6 +59,7 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery { this.queryString = queryString; this.alias = QueryUtils.detectAlias(queryString); this.hints = method.getHints(); + this.parameters = method.getParameters(); this.countQuery = method.getCountQuery() == null ? QueryUtils .createCountQueryFor(queryString) : method @@ -75,27 +80,29 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery { * ) */ @Override - public Query createQuery(ParameterBinder binder) { + public Query createQuery(Object[] values) { - String query = - QueryUtils.applySorting(queryString, binder.getSort(), alias); + ParameterAccessor accessor = + new ParametersParameterAccessor(parameters, values); + String sortedQueryString = + QueryUtils.applySorting(queryString, accessor.getSort(), alias); - return applyHints(getEntityManager().createQuery(query)); + Query query = getEntityManager().createQuery(sortedQueryString); + return createBinder(values).bindAndPrepare(applyHints(query)); } /* * (non-Javadoc) * - * @see - * org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery - * #createCountQuery(org.springframework.data.jpa.repository.query. - * ParameterBinder) + * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery# + * createCountQuery(java.lang.Object[]) */ @Override - protected Query createCountQuery(ParameterBinder binder) { + protected Query createCountQuery(Object[] values) { - return applyHints(getEntityManager().createQuery(countQuery)); + return createBinder(values).bindAndPrepare( + applyHints(getEntityManager().createQuery(countQuery))); } diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/Role.java b/src/test/java/org/springframework/data/jpa/domain/sample/Role.java index 1b8f3f0e2..e71d0a7ed 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/Role.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/Role.java @@ -15,6 +15,8 @@ */ package org.springframework.data.jpa.domain.sample; + + /** * Sample domain class representing roles. Mapped with XML. * diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/SpecialUser.java b/src/test/java/org/springframework/data/jpa/domain/sample/SpecialUser.java index c0e9a9772..40b400e4f 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/SpecialUser.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/SpecialUser.java @@ -1,8 +1,12 @@ package org.springframework.data.jpa.domain.sample; +import javax.persistence.Entity; + + /** * @author Oliver Gierke */ +@Entity public class SpecialUser extends User { } diff --git a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java new file mode 100644 index 000000000..86577e6dd --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkUserRepositoryFinderTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011 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; + +import org.junit.Ignore; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; + + +/** + * Ignores some test cases using IN queries as long as we wait for fix for + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477. + * + * @author Oliver Gierke + */ +@Ignore +@DirtiesContext +@ContextConfiguration(value = "classpath:eclipselink.xml", inheritLocations = true) +public class EclipseLinkUserRepositoryFinderTests extends + UserRepositoryFinderTests { +} diff --git a/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java b/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java new file mode 100644 index 000000000..dd96ce301 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/SimpleJpaParameterBindingTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2011 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; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Root; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.jpa.domain.sample.User; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + + +/** + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({ "classpath:application-context.xml" +// , "classpath:eclipselink.xml" +// , "classpath:openjpa.xml" +}) +@Transactional +public class SimpleJpaParameterBindingTests { + + @PersistenceContext + EntityManager em; + + + @Test + @Ignore + public void bindArray() { + + User user = new User("Dave", "Matthews", "foo@bar.de"); + em.persist(user); + em.flush(); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + + CriteriaQuery criteria = builder.createQuery(User.class); + Root root = criteria.from(User.class); + ParameterExpression parameter = + builder.parameter(String[].class); + criteria.where(root.get("firstname").in(parameter)); + + TypedQuery query = em.createQuery(criteria); + query.setParameter(parameter, new String[] { "Dave", "Carter" }); + + List result = query.getResultList(); + assertThat(result.isEmpty(), is(false)); + } + + + @Test + @SuppressWarnings("rawtypes") + public void bindCollection() { + + User user = new User("Dave", "Matthews", "foo@bar.de"); + em.persist(user); + em.flush(); + + CriteriaBuilder builder = em.getCriteriaBuilder(); + + CriteriaQuery criteria = builder.createQuery(User.class); + Root root = criteria.from(User.class); + ParameterExpression parameter = + builder.parameter(Collection.class); + criteria.where(root.get("firstname").in(parameter)); + + TypedQuery query = em.createQuery(criteria); + + query.setParameter(parameter, Arrays.asList("Dave")); + + List result = query.getResultList(); + assertThat(result.isEmpty(), is(false)); + assertThat(result.get(0), is(user)); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java index 9f2fd1fd3..96ba0e380 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java @@ -17,16 +17,15 @@ package org.springframework.data.jpa.repository.query; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import javax.persistence.EntityManager; -import javax.persistence.NoResultException; import javax.persistence.Query; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution; @@ -44,8 +43,6 @@ public class JpaQueryExecutionUnitTests { @Mock AbstractStringBasedJpaQuery jpaQuery; @Mock - ParameterBinder binder; - @Mock Query query; @Mock JpaQueryMethod method; @@ -54,7 +51,7 @@ public class JpaQueryExecutionUnitTests { @Test(expected = IllegalArgumentException.class) public void rejectsNullQuery() { - new StubQueryExecution().execute(null, binder); + new StubQueryExecution().execute(null, new Object[] {}); } @@ -71,29 +68,11 @@ public class JpaQueryExecutionUnitTests { assertThat(new JpaQueryExecution() { @Override - protected Object doExecute(AbstractStringBasedJpaQuery query, - ParameterBinder binder) { - - throw new NoResultException(); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.jpa.repository.query.JpaQueryExecution - * #doExecute - * (org.springframework.data.jpa.repository.query.PartTreeJpaQuery, - * java.lang.Object[]) - */ - @Override - protected Object doExecute(PartTreeJpaQuery query, - Object[] parameters) { + protected Object doExecute(AbstractJpaQuery query, Object[] values) { return null; } - }.execute(jpaQuery, binder), is(nullValue())); + }.execute(jpaQuery, new Object[] {}), is(nullValue())); } @@ -101,13 +80,13 @@ public class JpaQueryExecutionUnitTests { @SuppressWarnings({ "unchecked", "rawtypes" }) public void modifyingExecutionClearsEntityManagerIfSet() { - Query param = any(); - when(binder.bind(param)).thenReturn(query); when(query.executeUpdate()).thenReturn(0); when(method.getReturnType()).thenReturn((Class) void.class); + when(jpaQuery.createQuery(Mockito.any(Object[].class))).thenReturn( + query); ModifyingExecution execution = new ModifyingExecution(method, em); - execution.execute(jpaQuery, binder); + execution.execute(jpaQuery, new Object[] {}); verify(em, times(1)).clear(); } @@ -138,15 +117,7 @@ public class JpaQueryExecutionUnitTests { static class StubQueryExecution extends JpaQueryExecution { @Override - protected Object doExecute(AbstractStringBasedJpaQuery query, - ParameterBinder binder) { - - return null; - } - - - @Override - protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) { + protected Object doExecute(AbstractJpaQuery query, Object[] values) { return null; } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java index e44a3cabb..c6b30b69b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java @@ -34,6 +34,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.Parameters; /** @@ -72,8 +73,7 @@ public class SimpleJpaQueryUnitTests { public void appliesHintsCorrectly() throws Exception { SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "foobar"); - jpaQuery.createQuery(new ParameterBinder(method.getParameters(), - new Object[] { "gierke" })); + jpaQuery.createQuery(new Object[] { "gierke" }); verify(query).setHint("foo", "bar"); } @@ -84,11 +84,16 @@ public class SimpleJpaQueryUnitTests { method = mock(JpaQueryMethod.class); when(method.getCountQuery()).thenReturn("foo"); + when(method.getParameters()) + .thenReturn( + new Parameters( + SimpleJpaQueryUnitTests.class + .getMethod("prefersDeclaredCountQueryOverCreatingOne"))); when(em.createQuery("foo")).thenReturn(query); SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u"); - assertThat(jpaQuery.createCountQuery(null), is(query)); + assertThat(jpaQuery.createCountQuery(new Object[] {}), is(query)); } }