DATAJPA-64 - Refactored query execution to use ParameterExpression.

The JpaQueryCreator now creates a CriteriaQuery using ParameterExpressions that have to be bound later on. Refactored the RepositoryQuery implementation hierarchy and JpaQueryExecution as binding has to be done by the query classes now. This required the introduction of a special CriteraQueryParameterBinder as well. It uses the ParameterExpressions of the CriteriaQuery to bind the actual query values later on.

We have to convert arrays passed into query method into collections as none of the major persistence providers support binding arrays to IN parameters currently.
This commit is contained in:
Oliver Gierke
2011-06-15 21:28:03 +02:00
parent 762e57ca2f
commit 405fccb04f
16 changed files with 514 additions and 307 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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<ParameterExpression<?>> expressions;
/**
* Creates a new {@link CriteriaQueryParameterBinder} for the given
* {@link Parameters}, values and some {@link ParameterExpression}.
*
* @param parameters
*/
CriteriaQueryParameterBinder(Parameters parameters, Object[] values,
Iterable<ParameterExpression<?>> 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<Object> expression =
(ParameterExpression<Object>) 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);
}
}

View File

@@ -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);
}

View File

@@ -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<Object> 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<ParameterExpression<?>> getParameterExpressions() {
return provider.getExpressions();
}
@@ -82,7 +99,7 @@ public class JpaQueryCreator extends
@Override
protected Predicate create(Part part, Iterator<Object> 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<Object> 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<Object> iterator) {
private Predicate toPredicate(Part part, Root<?> root) {
Expression<Object> path =
toExpressionRecursively(root, part.getProperty());
Property property = part.getProperty();
Expression<Object> path = toExpressionRecursively(root, property);
switch (part.getType()) {
case BETWEEN:
ParameterExpression<Comparable> first = provider.next();
ParameterExpression<Comparable> second = provider.next();
return builder.between(
root.<Comparable> 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.<String> get(part.getProperty()
.toDotPath()), iterator.next().toString());
return builder.like(
root.<String> get(part.getProperty().toDotPath()),
provider.next(String.class));
case NOT_LIKE:
return builder.not(builder.like(root.<String> get(part
.getProperty().toDotPath()), iterator.next().toString()));
return builder.like(
root.<String> 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<Object> 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<Parameter> parameters;
private final List<ParameterExpression<?>> expressions;
private Collection<?> nextAsCollection(Iterator<Object> 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<ParameterExpression<?>>();
}
return Arrays.asList(next);
/**
* Returns all {@link ParameterExpression}s built.
*
* @return the expressions
*/
public List<ParameterExpression<?>> getExpressions() {
return Collections.unmodifiableList(expressions);
}
/**
* Builds a new {@link ParameterExpression} for the next
* {@link Parameter}.
*
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public <T> ParameterExpression<T> next() {
Parameter parameter = parameters.next();
return (ParameterExpression<T>) next(parameter.getType(),
parameter.getName());
}
/**
* Builds a new {@link ParameterExpression} of the given type. Forwards
* the underlying {@link Parameters} as well.
*
* @param <T>
* @param type must not be {@literal null}.
* @return
*/
public <T> ParameterExpression<T> next(Class<T> type) {
parameters.next();
return next(type, null);
}
/**
* Builds a new {@link ParameterExpression} for the given type and name.
*
* @param <T>
* @param type must not be {@literal null}.
* @param name
* @return
*/
@SuppressWarnings("unchecked")
private <T> ParameterExpression<T> next(Class<T> type, String name) {
Assert.notNull(type);
ParameterExpression<?> expression =
name == null ? builder.parameter(type) : builder.parameter(
type, name);
expressions.add(expression);
return (ParameterExpression<T>) expression;
}
}
}

View File

@@ -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<Object>(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<Object>(jpaQuery.getResultList(),
return new PageImpl<Object>(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();
}
}
}

View File

@@ -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)));
}
}

View File

@@ -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).

View File

@@ -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<Object> 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<Object> 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<ParameterExpression<?>> expressions) {
return execution.execute(this, parameters);
return new CriteriaQueryParameterBinder(parameters, values, expressions);
}
}

View File

@@ -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<QueryHint> 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)));
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jpa.domain.sample;
/**
* Sample domain class representing roles. Mapped with XML.
*

View File

@@ -1,8 +1,12 @@
package org.springframework.data.jpa.domain.sample;
import javax.persistence.Entity;
/**
* @author Oliver Gierke
*/
@Entity
public class SpecialUser extends User {
}

View File

@@ -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 {
}

View File

@@ -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<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
ParameterExpression<String[]> parameter =
builder.parameter(String[].class);
criteria.where(root.get("firstname").in(parameter));
TypedQuery<User> query = em.createQuery(criteria);
query.setParameter(parameter, new String[] { "Dave", "Carter" });
List<User> 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<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
ParameterExpression<Collection> parameter =
builder.parameter(Collection.class);
criteria.where(root.get("firstname").in(parameter));
TypedQuery<User> query = em.createQuery(criteria);
query.setParameter(parameter, Arrays.asList("Dave"));
List<User> result = query.getResultList();
assertThat(result.isEmpty(), is(false));
assertThat(result.get(0), is(user));
}
}

View File

@@ -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;
}

View File

@@ -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));
}
}