Migrated query-from-methodname algorithm to PartTree infrastructure.

Query creation is not String based anymore but rather leverages the PartTree infrastructure and builds a JPA criteria API query instead. Changed query lookup and execution accordingly.
This commit is contained in:
Oliver Gierke
2010-12-07 20:43:54 +01:00
parent 488e6979ab
commit 4d1188b751
28 changed files with 691 additions and 687 deletions

View File

@@ -19,7 +19,6 @@ import javax.persistence.EntityManager;
import javax.persistence.Query;
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;
@@ -57,65 +56,37 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
/**
* Creates a JPA {@link Query} with the given {@link ParameterBinder}.
*
* @param binder
* @return
* @return the parameters
*/
public Query createQuery(ParameterBinder binder) {
public Parameters getParameters() {
return createQuery(em, binder);
return parameters;
}
/**
* Creates a JPA {@link Query} to count the instances of the
* {@link HadesQuery} to be returned.
*
* @param binder
* @return
* @return the em
*/
public Query createCountQuery() {
public EntityManager getEntityManager() {
return createCountQuery(em);
return em;
}
/**
* Executes the {@link javax.persistence.Query} backing the
* {@link QueryMethod} with the given parameters.
/*
* (non-Javadoc)
*
* @param em
* @param parameters
* @return
* @see
* org.springframework.data.repository.query.RepositoryQuery#execute(java
* .lang.Object[])
*/
public Object execute(Object[] parameters) {
ParameterBinder binder =
new ParameterBinder(this.parameters, parameters);
return execution.execute(this, binder);
return doExecute(execution, parameters);
}
/**
* Returns the actual {@link Query} to be executed. Has to return a fresh
* instance on each call.
*
* @param em
* @param binder
* @return
*/
protected abstract Query createQuery(EntityManager em,
ParameterBinder binder);
protected abstract Object doExecute(JpaQueryExecution execution,
Object[] parameters);
/**
* Returns the projecting count {@link Query} to be executed. Has to return
* a fresh instance on each call.
*
* @param em
* @return
*/
protected abstract Query createCountQuery(EntityManager em);
}

View File

@@ -0,0 +1,59 @@
package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
import javax.persistence.Query;
/**
* Base class for {@link String} based JPA queries.
*
* @author Oliver Gierke
*/
public abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
/**
* Creates a new {@link AbstractStringBasedJpaQuery}.
*
* @param method
* @param em
*/
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em) {
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,56 @@
package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.SimpleParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
/**
* Special {@link JpaQueryCreator} that creates a count projecting query.
*
* @author Oliver Gierke
*/
public class JpaCountQueryCreator extends JpaQueryCreator {
private final Class<?> domainClass;
/**
* Creates a new {@link JpaCountQueryCreator}.
*
* @param tree
* @param parameters
* @param domainClass
* @param em
*/
public JpaCountQueryCreator(PartTree tree,
SimpleParameterAccessor parameters, Class<?> domainClass,
EntityManager em) {
super(tree, parameters, domainClass, em);
this.domainClass = domainClass;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.query.JpaQueryCreator#finalize
* (javax.persistence.criteria.Predicate,
* org.springframework.data.domain.Sort,
* javax.persistence.criteria.CriteriaQuery,
* javax.persistence.criteria.CriteriaBuilder)
*/
@Override
protected CriteriaQuery<Object> finalize(Predicate predicate, Sort sort,
CriteriaQuery<Object> query, CriteriaBuilder builder) {
return query.select(builder.count(query.from(domainClass)));
}
}

View File

@@ -0,0 +1,209 @@
package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.SimpleParameterAccessor;
import org.springframework.data.repository.query.SimpleParameterAccessor.BindableParameterIterator;
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.util.Assert;
/**
* Query creator to create a {@link CriteriaQuery} from a {@link PartTree}.
*
* @author Oliver Gierke
*/
public class JpaQueryCreator extends
AbstractQueryCreator<CriteriaQuery<Object>, Predicate> {
private final CriteriaBuilder builder;
private final Root<?> root;
private final CriteriaQuery<Object> query;
/**
* Create a new {@link JpaQueryCreator}.
*
* @param tree
* @param parameters
* @param domainClass
* @param em
*/
public JpaQueryCreator(PartTree tree, SimpleParameterAccessor parameters,
Class<?> domainClass, EntityManager em) {
super(tree, parameters);
this.builder = em.getCriteriaBuilder();
this.query = builder.createQuery();
this.root = query.from(domainClass);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.query.parser.AbstractQueryCreator
* #create(org.springframework.data.repository.query.parser.Part,
* org.springframework
* .data.repository.query.SimpleParameterAccessor.BindableParameterIterator)
*/
@Override
protected Predicate create(Part part, BindableParameterIterator iterator) {
return toPredicate(part, root, iterator);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.query.parser.AbstractQueryCreator
* #and(org.springframework.data.repository.query.parser.Part,
* java.lang.Object,
* org.springframework.data.repository.query.SimpleParameterAccessor
* .BindableParameterIterator)
*/
@Override
protected Predicate and(Part part, Predicate base,
BindableParameterIterator iterator) {
return builder.and(base, toPredicate(part, root, iterator));
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.query.parser.AbstractQueryCreator
* #or(java.lang.Object, java.lang.Object)
*/
@Override
protected Predicate or(Predicate base, Predicate predicate) {
return builder.or(base, predicate);
}
/**
* Finalizes the given {@link Predicate} and applies the given sort.
* Delegates to
* {@link #finalize(Predicate, Sort, CriteriaQuery, CriteriaBuilder)} and
* hands it the current {@link CriteriaQuery} and {@link CriteriaBuilder}.
*/
@Override
protected final CriteriaQuery<Object> finalize(Predicate predicate,
Sort sort) {
return finalize(predicate, sort, query, builder);
}
/**
* Template method to finalize the given {@link Predicate} using the given
* {@link CriteriaQuery} and {@link CriteriaBuilder}.
*
* @param predicate
* @param sort
* @param query
* @param builder
* @return
*/
protected CriteriaQuery<Object> finalize(Predicate predicate, Sort sort,
CriteriaQuery<Object> query, CriteriaBuilder builder) {
return this.query.select(root).where(predicate)
.orderBy(QueryUtils.toOrders(sort, root, builder));
}
/**
* Creates a {@link Predicate} from the given {@link Part}.
*
* @param part
* @param root
* @param iterator
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private Predicate toPredicate(Part part, Root<?> root,
BindableParameterIterator iterator) {
Expression<Object> path = root.get(part.getProperty());
switch (part.getType()) {
case BETWEEN:
return builder.between(root.<Comparable> get(part.getProperty()),
nextAsComparable(iterator), nextAsComparable(iterator));
case GREATER_THAN:
return builder.greaterThan(getComparablePath(root, part),
nextAsComparable(iterator));
case LESS_THAN:
return builder.lessThan(getComparablePath(root, part),
nextAsComparable(iterator));
case IS_NULL:
return root.isNull();
case IS_NOT_NULL:
return root.isNotNull();
case LIKE:
return builder.like(root.<String> get(part.getProperty()), iterator
.next().toString());
case NOT_LIKE:
return builder.not(builder.like(root.<String> get(part
.getProperty()), iterator.next().toString()));
case SIMPLE_PROPERTY:
return builder.equal(path, iterator.next());
case NEGATING_SIMPLE_PROPERTY:
return builder.notEqual(path, iterator.next());
default:
throw new IllegalArgumentException("Unsupported keyword + "
+ part.getType());
}
}
/**
* Returns a path to a {@link Comparable}.
*
* @param root
* @param part
* @return
*/
@SuppressWarnings("rawtypes")
private Expression<? extends Comparable> getComparablePath(Root<?> root,
Part part) {
return root.get(part.getProperty());
}
/**
* Returns the next parameter from the given
* {@link BindableParameterIterator} and expects it to be a
* {@link Comparable}.
*
* @param iterator
* @return
*/
@SuppressWarnings("rawtypes")
private Comparable nextAsComparable(BindableParameterIterator iterator) {
Object next = iterator.next();
Assert.isInstanceOf(Comparable.class, next,
"Parameter has to implement Comparable to be bound correctly!");
return (Comparable<?>) next;
}
}

View File

@@ -24,6 +24,7 @@ import javax.persistence.Query;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.SimpleParameterAccessor;
import org.springframework.util.Assert;
@@ -44,7 +45,8 @@ public abstract class JpaQueryExecution {
* @return
*/
public Object execute(AbstractJpaQuery query, ParameterBinder binder) {
public Object execute(AbstractStringBasedJpaQuery query,
ParameterBinder binder) {
Assert.notNull(query);
Assert.notNull(binder);
@@ -57,6 +59,19 @@ public abstract class JpaQueryExecution {
}
public Object execute(PartTreeJpaQuery query, Object[] parameters) {
Assert.notNull(query);
Assert.notNull(parameters);
try {
return doExecute(query, parameters);
} catch (NoResultException e) {
return null;
}
}
/**
* Method to implement {@link AbstractHadesQuery} executions by single enum
* values.
@@ -65,8 +80,12 @@ public abstract class JpaQueryExecution {
* @param binder
* @return
*/
protected abstract Object doExecute(AbstractJpaQuery query,
ParameterBinder binder);
protected abstract Object doExecute(AbstractStringBasedJpaQuery query,
ParameterBinder parameters);
protected abstract Object doExecute(PartTreeJpaQuery query,
Object[] parameters);
/**
* Executes the {@link HadesQuery} to return a simple collection of
@@ -75,12 +94,26 @@ public abstract class JpaQueryExecution {
static class CollectionExecution extends JpaQueryExecution {
@Override
protected Object doExecute(AbstractJpaQuery query,
protected Object doExecute(AbstractStringBasedJpaQuery query,
ParameterBinder binder) {
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();
}
}
/**
@@ -89,13 +122,23 @@ public abstract class JpaQueryExecution {
*/
static class PagedExecution extends JpaQueryExecution {
private final Parameters parameters;
public PagedExecution(Parameters parameters) {
this.parameters = parameters;
}
@Override
@SuppressWarnings("unchecked")
protected Object doExecute(AbstractJpaQuery repositoryQuery,
protected Object doExecute(AbstractStringBasedJpaQuery repositoryQuery,
ParameterBinder binder) {
// Execute query to compute total
Query projection = binder.bind(repositoryQuery.createCountQuery());
Query projection =
binder.bind(repositoryQuery.createCountQuery(binder));
Long total = (Long) projection.getSingleResult();
Query query =
@@ -104,6 +147,30 @@ public abstract class JpaQueryExecution {
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) {
SimpleParameterAccessor accessor =
new SimpleParameterAccessor(this.parameters, parameters);
Query countQuery = query.createCountQuery(parameters);
Long total = (Long) countQuery.getSingleResult();
Query jpaQuery = query.createQuery(parameters);
return new PageImpl<Object>(jpaQuery.getResultList(),
accessor.getPageable(), total);
}
}
/**
@@ -112,11 +179,26 @@ public abstract class JpaQueryExecution {
static class SingleEntityExecution extends JpaQueryExecution {
@Override
protected Object doExecute(AbstractJpaQuery query,
protected Object doExecute(AbstractStringBasedJpaQuery query,
ParameterBinder binder) {
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();
}
}
/**
@@ -158,7 +240,7 @@ public abstract class JpaQueryExecution {
* org.springframework.data.repository.query.ParameterBinder)
*/
@Override
protected Object doExecute(AbstractJpaQuery query,
protected Object doExecute(AbstractStringBasedJpaQuery query,
ParameterBinder binder) {
int result = binder.bind(query.createQuery(binder)).executeUpdate();
@@ -169,5 +251,12 @@ public abstract class JpaQueryExecution {
return result;
}
@Override
protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -90,7 +90,7 @@ public class JpaQueryLookupStrategy {
protected RepositoryQuery resolveQuery(JpaQueryMethod method,
EntityManager em) {
return SimpleJpaQuery.construct(method, em);
return new PartTreeJpaQuery(method, em);
}
}

View File

@@ -81,7 +81,7 @@ public class JpaQueryMethod extends QueryMethod {
}
if (isPageQuery()) {
return new PagedExecution();
return new PagedExecution(getParameters());
}
if (isModifyingQuery()) {

View File

@@ -32,7 +32,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
*
* @author Oliver Gierke
*/
final class NamedQuery extends AbstractJpaQuery {
final class NamedQuery extends AbstractStringBasedJpaQuery {
private static final Logger LOG = LoggerFactory.getLogger(NamedQuery.class);
@@ -121,9 +121,9 @@ final class NamedQuery extends AbstractJpaQuery {
* .EntityManager, org.synyx.hades.dao.query.ParameterBinder)
*/
@Override
protected Query createQuery(EntityManager em, ParameterBinder binder) {
protected Query createQuery(ParameterBinder binder) {
return em.createNamedQuery(queryName);
return getEntityManager().createNamedQuery(queryName);
}
@@ -134,11 +134,12 @@ final class NamedQuery extends AbstractJpaQuery {
* persistence.EntityManager)
*/
@Override
protected Query createCountQuery(EntityManager em) {
protected Query createCountQuery(ParameterBinder binder) {
Query query = createQuery(em, null);
Query query = createQuery(binder);
String queryString = extractor.extractQueryString(query);
return em.createQuery(QueryUtils.createCountQueryFor(queryString));
return getEntityManager().createQuery(
QueryUtils.createCountQueryFor(queryString));
}
}

View File

@@ -118,7 +118,7 @@ public class ParameterBinder {
if (hasNamedParameter(query) && parameter.isNamedParameter()) {
query.setParameter(parameter.getName(), value);
} else {
query.setParameter(parameter.getParameterPosition(), value);
query.setParameter(parameter.getIndex() + 1, value);
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.data.jpa.repository.query;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;
import org.springframework.data.repository.query.SimpleParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
/**
* A {@link AbstractJpaQuery} implementation based on a {@link PartTree}.
*
* @author Oliver Gierke
*/
public class PartTreeJpaQuery extends AbstractJpaQuery {
private final PartTree tree;
private final Class<?> domainClass;
/**
* Creates a new {@link PartTreeJpaQuery}.
*
* @param method
* @param em
*/
public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) {
super(method, em);
this.tree = new PartTree(method.getName(), method.getDomainClass());
this.domainClass = method.getDomainClass();
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.query.AbstractJpaQuery#createQuery
* (javax.persistence.EntityManager,
* org.springframework.data.jpa.repository.query.ParameterBinder)
*/
public Query createQuery(Object[] parameters) {
JpaQueryCreator jpaQueryCreator =
new JpaQueryCreator(tree, new SimpleParameterAccessor(
getParameters(), parameters), domainClass,
getEntityManager());
return getEntityManager().createQuery(jpaQueryCreator.createQuery());
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#
* createCountQuery(javax.persistence.EntityManager)
*/
public Query createCountQuery(Object[] parameters) {
CriteriaQuery<Object> createQuery =
new JpaCountQueryCreator(tree, new SimpleParameterAccessor(
getParameters(), parameters), domainClass,
getEntityManager()).createQuery();
return getEntityManager().createQuery(createQuery);
}
/*
* (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) {
return execution.execute(this, parameters);
}
}

View File

@@ -1,166 +0,0 @@
/*
* Copyright 2008-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.repository.utils.JpaClassUtils;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParameterOutOfBoundsException;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.parser.OrderBySource;
import org.springframework.data.repository.query.parser.PartSource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Class to encapsulate query creation logic for {@link QueryMethod}s.
*
* @author Oliver Gierke
*/
class QueryCreator {
private static final Logger LOG = LoggerFactory
.getLogger(QueryCreator.class);
private static final String INVALID_PARAMETER_SIZE =
"You have to provide method arguments for each query "
+ "criteria to construct the query correctly!";
private static final String AND = "And";
private static final String OR = "Or";
private JpaQueryMethod method;
/**
* Creates a new {@link QueryCreator} for the given {@link QueryMethod}.
*
* @param queryMethod
*/
public QueryCreator(JpaQueryMethod queryMethod) {
Assert.isTrue(!queryMethod.isModifyingQuery());
this.method = queryMethod;
}
/**
* Constructs a query from the underlying {@link QueryMethod}.
*
* @return the query string
* @throws QueryCreationException in case the query can't be created
*/
public String constructQuery() {
StringBuilder queryBuilder = new StringBuilder();
Parameters parameters = method.getParameters().getBindableParameters();
String methodName = method.getName();
Class<?> domainClass = method.getDomainClass();
try {
int parametersBound =
doCreateQuery(new PartSource(methodName), domainClass,
queryBuilder, parameters);
if (!method.isCorrectNumberOfParameters(parametersBound)) {
throw QueryCreationException.create(method,
INVALID_PARAMETER_SIZE);
}
} catch (ParameterOutOfBoundsException e) {
throw QueryCreationException.create(method, e);
}
String query = queryBuilder.toString();
LOG.debug("Created query '%s' from method %s", query, method.getName());
return query;
}
private int doCreateQuery(PartSource source, Class<?> domainClass,
StringBuilder builder, Parameters parameters) {
builder.append(getQueryString(READ_ALL_QUERY,
JpaClassUtils.getEntityName(domainClass)));
builder.append(" where ");
Iterator<PartSource> orParts = source.getParts(OR);
int parametersBound = 0;
while (orParts.hasNext()) {
Iterator<PartSource> andParts = orParts.next().getParts(AND);
while (andParts.hasNext()) {
PartSource andPart = andParts.next();
JpaQueryPart part =
new JpaQueryPart(andPart.cleanedUp(), domainClass);
Parameter parameter =
part.getParameterRequired() ? parameters
.getParameter(parametersBound) : null;
builder.append(part.getQueryPart(parameter));
if (andParts.hasNext()) {
builder.append(" and ");
}
parametersBound += part.getNumberOfArguments();
}
if (orParts.hasNext()) {
builder.append(" or ");
}
}
if (source.hasOrderByClause()) {
builder.append(" ").append(getClause(source.getOrderBySource()));
}
return parametersBound;
}
/**
* Returns the final JPA order by clause.
*
* @return
*/
public String getClause(OrderBySource source) {
List<String> parts = new ArrayList<String>();
for (Order order : source.toSort()) {
parts.add(String.format("x.%s %s", order.getProperty(),
QueryUtils.toJpaDirection(order)));
}
return "order by "
+ StringUtils.collectionToDelimitedString(parts, ", ");
}
}

View File

@@ -18,7 +18,9 @@ package org.springframework.data.jpa.repository.query;
import static java.util.regex.Pattern.*;
import static org.springframework.data.jpa.repository.utils.JpaClassUtils.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -26,6 +28,9 @@ import java.util.regex.Pattern;
import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Root;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
@@ -107,7 +112,7 @@ public abstract class QueryUtils {
* @param clazzName
* @return
*/
public static String getQueryString(String template, String clazzName) {
private static String getQueryString(String template, String clazzName) {
Assert.hasText(clazzName, "Classname must not be null or empty!");
@@ -265,4 +270,48 @@ public abstract class QueryUtils {
return false;
}
/**
* Turns the given {@link Sort} into
* {@link javax.persistence.criteria.Order}s.
*
* @param sort
* @param root
* @param cb
* @return
*/
public static List<javax.persistence.criteria.Order> toOrders(Sort sort,
Root<?> root, CriteriaBuilder cb) {
List<javax.persistence.criteria.Order> orders =
new ArrayList<javax.persistence.criteria.Order>();
if (sort == null) {
return orders;
}
for (org.springframework.data.domain.Sort.Order order : sort) {
orders.add(toJpaOrder(order, root, cb));
}
return orders;
}
/**
* Creates a criteria API {@link javax.persistence.criteria.Order} from the
* given {@link Order}.
*
* @param order
* @param root
* @param cb
* @return
*/
private static javax.persistence.criteria.Order toJpaOrder(Order order,
Root<?> root, CriteriaBuilder cb) {
Expression<?> expression = root.get(order.getProperty());
return order.isAscending() ? cb.asc(expression) : cb.desc(expression);
}
}

View File

@@ -23,7 +23,6 @@ import javax.persistence.QueryHint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
@@ -35,7 +34,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
*
* @author Oliver Gierke
*/
final class SimpleJpaQuery extends AbstractJpaQuery {
final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
private static final Logger LOG = LoggerFactory
.getLogger(SimpleJpaQuery.class);
@@ -63,19 +62,6 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
}
/**
* Creates a new {@link SimpleJpaQuery} that constructs the query from the
* given {@link QueryMethod}.
*
* @param method
* @param em
*/
SimpleJpaQuery(JpaQueryMethod method, EntityManager em) {
this(method, em, new QueryCreator(method).constructQuery());
}
/*
* (non-Javadoc)
*
@@ -84,25 +70,27 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
* .EntityManager, org.synyx.hades.dao.query.ParameterBinder)
*/
@Override
protected Query createQuery(EntityManager em, ParameterBinder binder) {
public Query createQuery(ParameterBinder binder) {
String query =
QueryUtils.applySorting(queryString, binder.getSort(), alias);
return applyHints(em.createQuery(query));
return applyHints(getEntityManager().createQuery(query));
}
/*
* (non-Javadoc)
*
* @see org.synyx.hades.dao.query.AbstractHadesQuery#createCountQuery(javax.
* persistence.EntityManager)
* @see
* org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery
* #createCountQuery(org.springframework.data.jpa.repository.query.
* ParameterBinder)
*/
@Override
protected Query createCountQuery(EntityManager em) {
protected Query createCountQuery(ParameterBinder binder) {
return applyHints(em.createQuery(countQuery));
return applyHints(getEntityManager().createQuery(countQuery));
}
@@ -138,30 +126,7 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
String query = queryMethod.getAnnotatedQuery();
return query == null ? null : new SimpleJpaQuery(queryMethod, em,
query);
}
/**
* Constructs a {@link HadesQuery} from the given {@link QueryMethod}.
*
* @param queryMethod
* @param em
* @return
*/
public static RepositoryQuery construct(JpaQueryMethod queryMethod,
EntityManager em) {
if (queryMethod.isModifyingQuery()) {
throw QueryCreationException
.create(queryMethod,
"Cannot create query from method name "
+ "for modifying query. Use @Query or @NamedQuery to "
+ "declare the query to execute. Do not use CREATE as "
+ "strategy to lookup queries!");
}
return new SimpleJpaQuery(queryMethod, em);
return query == null ? null
: new SimpleJpaQuery(queryMethod, em, query);
}
}

View File

@@ -26,8 +26,6 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@@ -464,28 +462,4 @@ public class SimpleJpaRepository<T, ID extends Serializable> extends
return root;
}
private List<Order> toOrders(Sort sort, Root<T> root, CriteriaBuilder cb) {
List<Order> orders = new ArrayList<Order>();
if (sort == null) {
return orders;
}
for (org.springframework.data.domain.Sort.Order order : sort) {
orders.add(toJpaOrder(order, root, cb));
}
return orders;
}
private Order toJpaOrder(org.springframework.data.domain.Sort.Order order,
Root<T> root, CriteriaBuilder cb) {
Expression<?> expression = root.get(order.getProperty());
return order.isAscending() ? cb.asc(expression) : cb.desc(expression);
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.test.context.ContextConfiguration;
* @author Oliver Gierke
*/
@ContextConfiguration(value = "classpath:openjpa.xml", inheritLocations = true)
public class OpenJpaNamespaceUserDaoTests extends
NamespaceUserRepositoryTests {
public class OpenJpaNamespaceUserDaoTests extends NamespaceUserRepositoryTests {
}

View File

@@ -255,8 +255,9 @@ public class UserRepositoryTests {
flushTestUsers();
repository.renameAllUsersTo("newLastname");
assertEquals(repository.count().intValue(),
repository.findByLastname("newLastname").size());
Integer expected = repository.count().intValue();
assertThat(repository.findByLastname("newLastname").size(),
is(expected));
}
@@ -546,8 +547,10 @@ public class UserRepositoryTests {
firstUser = repository.save(firstUser);
secondUser = repository.save(secondUser);
assertTrue(repository.findByFirstnameOrLastname("Oliver", "Arrasz")
.containsAll(Arrays.asList(firstUser, secondUser)));
List<User> result =
repository.findByFirstnameOrLastname("Oliver", "Arrasz");
assertThat(result.size(), is(2));
assertThat(result, hasItems(firstUser, secondUser));
}

View File

@@ -25,6 +25,5 @@ import org.springframework.test.context.ContextConfiguration;
* @author Oliver Gierke
*/
@ContextConfiguration(locations = "classpath:config/namespace-autoconfig-context.xml")
public class RepositoryAutoConfigTests extends
AbstractRepositoryConfigTests {
public class RepositoryAutoConfigTests extends AbstractRepositoryConfigTests {
}

View File

@@ -28,8 +28,7 @@ import org.springframework.test.context.ContextConfiguration;
* @author Oliver Gierke
*/
@ContextConfiguration(locations = "classpath:config/namespace-autoconfig-typefilter-context.xml")
public class TypeFilterConfigTest extends
AbstractRepositoryConfigTests {
public class TypeFilterConfigTest extends AbstractRepositoryConfigTests {
/*
* (non-Javadoc)

View File

@@ -25,13 +25,12 @@ import org.springframework.data.repository.support.RepositorySupport;
/**
* Sample implementation of a custom {@link JpaRepositoryFactory} to use
* a custom repository base class.
* Sample implementation of a custom {@link JpaRepositoryFactory} to use a
* custom repository base class.
*
* @author Oliver Gierke
*/
public class CustomGenericJpaRepositoryFactory extends
JpaRepositoryFactory {
public class CustomGenericJpaRepositoryFactory extends JpaRepositoryFactory {
/**
* @param entityManager

View File

@@ -24,10 +24,10 @@ import org.springframework.data.repository.NoRepositoryBean;
/**
* Extension of {@link Repository} to be added on a custom repository
* base class. This tests the facility to implement custom base class
* functionality for all repository instances derived from this interface and
* implementation base class.
* Extension of {@link Repository} to be added on a custom repository base
* class. This tests the facility to implement custom base class functionality
* for all repository instances derived from this interface and implementation
* base class.
*
* @author Oliver Gierke
*/

View File

@@ -19,13 +19,13 @@ package org.springframework.data.jpa.repository.custom;
import org.springframework.data.jpa.domain.sample.User;
/**
* Custom Extended DAO interface for a {@code User}. This relies on the custom
* intermediate DAO interface {@link CustomGenericRepository}.
*
* @author Oliver Gierke
*/
public interface UserCustomExtendedRepository extends CustomGenericRepository<User, Integer> {
public interface UserCustomExtendedRepository extends
CustomGenericRepository<User, Integer> {
}

View File

@@ -45,7 +45,7 @@ public class JpaQueryExecutionUnitTests {
@Mock
EntityManager em;
@Mock
AbstractJpaQuery jpaQuery;
AbstractStringBasedJpaQuery jpaQuery;
@Mock
ParameterBinder binder;
@Mock
@@ -81,11 +81,28 @@ public class JpaQueryExecutionUnitTests {
assertThat(new JpaQueryExecution() {
@Override
protected Object doExecute(AbstractJpaQuery query,
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) {
return null;
}
}.execute(jpaQuery, binder), is(nullValue()));
}
@@ -123,11 +140,18 @@ public class JpaQueryExecutionUnitTests {
static class StubQueryExecution extends JpaQueryExecution {
@Override
protected Object doExecute(AbstractJpaQuery query,
protected Object doExecute(AbstractStringBasedJpaQuery query,
ParameterBinder binder) {
return null;
}
@Override
protected Object doExecute(PartTreeJpaQuery query, Object[] parameters) {
return null;
}
}
static interface Dummy {

View File

@@ -57,7 +57,7 @@ public class JpaQueryMethodUnitTests {
EntityManager em;
Method daoMethod, invalidReturnType, pageableAndSort, pageableTwice,
sortableTwice, modifyingMethod;
sortableTwice, modifyingMethod;
/**
@@ -67,24 +67,24 @@ public class JpaQueryMethodUnitTests {
public void setUp() throws Exception {
daoMethod =
UserRepository.class.getMethod("findByLastname", String.class);
UserRepository.class.getMethod("findByLastname", String.class);
invalidReturnType =
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Pageable.class);
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Pageable.class);
pageableAndSort =
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Pageable.class, Sort.class);
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Pageable.class, Sort.class);
pageableTwice =
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Pageable.class, Pageable.class);
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Pageable.class, Pageable.class);
sortableTwice =
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Sort.class, Sort.class);
InvalidDao.class.getMethod(METHOD_NAME, String.class,
Sort.class, Sort.class);
modifyingMethod =
UserRepository.class
.getMethod("renameAllUsersTo", String.class);
UserRepository.class
.getMethod("renameAllUsersTo", String.class);
}
@@ -95,8 +95,6 @@ public class JpaQueryMethodUnitTests {
assertEquals("User.findByLastname", method.getNamedQueryName());
assertThat(method.getExecution(), is(CollectionExecution.class));
assertEquals("select x from User x where x.lastname = ?1",
new QueryCreator(method).constructQuery());
}
@@ -137,11 +135,11 @@ public class JpaQueryMethodUnitTests {
assertNull(method.getAnnotatedQuery());
Method daoMethod =
UserRepository.class
.getMethod("findByHadesQuery", String.class);
UserRepository.class
.getMethod("findByHadesQuery", String.class);
assertNotNull(new JpaQueryMethod(daoMethod, extractor, em)
.getAnnotatedQuery());
.getAnnotatedQuery());
}
@@ -192,11 +190,11 @@ public class JpaQueryMethodUnitTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsPageablesOnPersistenceProvidersNotExtractingQueries()
throws Exception {
throws Exception {
Method method =
UserRepository.class.getMethod("findByFirstname",
Pageable.class, String.class);
UserRepository.class.getMethod("findByFirstname",
Pageable.class, String.class);
when(extractor.canExtractQuery()).thenReturn(false);
@@ -208,7 +206,7 @@ public class JpaQueryMethodUnitTests {
public void recognizesModifyingMethod() {
JpaQueryMethod method =
new JpaQueryMethod(modifyingMethod, extractor, em);
new JpaQueryMethod(modifyingMethod, extractor, em);
assertTrue(method.isModifyingQuery());
}
@@ -217,8 +215,8 @@ public class JpaQueryMethodUnitTests {
public void rejectsModifyingMethodWithPageable() throws Exception {
Method method =
InvalidDao.class.getMethod("updateMethod", String.class,
Pageable.class);
InvalidDao.class.getMethod("updateMethod", String.class,
Pageable.class);
new JpaQueryMethod(method, extractor, em);
}
@@ -228,8 +226,8 @@ public class JpaQueryMethodUnitTests {
public void rejectsModifyingMethodWithSort() throws Exception {
Method method =
InvalidDao.class.getMethod("updateMethod", String.class,
Sort.class);
InvalidDao.class.getMethod("updateMethod", String.class,
Sort.class);
new JpaQueryMethod(method, extractor, em);
}

View File

@@ -22,6 +22,7 @@ import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import javax.persistence.Embeddable;
import javax.persistence.Query;
import org.junit.Before;
@@ -31,7 +32,6 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.QueryCreatorUnitTests.SampleEmbeddable;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.Parameters;
@@ -183,8 +183,8 @@ public class ParameterBinderUnitTests {
public void bindsEmbeddableCorrectly() throws Exception {
Method method =
QueryCreatorUnitTests.class.getMethod("findByEmbeddable",
SampleEmbeddable.class);
getClass()
.getMethod("findByEmbeddable", SampleEmbeddable.class);
Parameters parameters = new Parameters(method);
SampleEmbeddable embeddable = new SampleEmbeddable();
@@ -204,4 +204,24 @@ public class ParameterBinderUnitTests {
new Object[] { "name", sort });
assertThat(binder.getSort(), is(sort));
}
public SampleEntity findByEmbeddable(SampleEmbeddable embeddable) {
return null;
}
@SuppressWarnings("unused")
static class SampleEntity {
private SampleEmbeddable embeddable;
}
@Embeddable
@SuppressWarnings("unused")
static class SampleEmbeddable {
private String foo;
private String bar;
}
}

View File

@@ -1,327 +0,0 @@
/*
* Copyright 2008-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Date;
import javax.persistence.Embeddable;
import javax.persistence.EntityManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.query.JpaQueryMethodUnitTests.InvalidDao;
import org.springframework.data.repository.query.QueryCreationException;
/**
* Unit test for {@link QueryCreator}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class QueryCreatorUnitTests {
private Method method;
@Mock
QueryExtractor extractor;
@Mock
EntityManager em;
@Before
public void setup() throws SecurityException, NoSuchMethodException {
method =
QueryCreatorUnitTests.class.getMethod(
"findByFirstnameAndMethod", String.class);
}
@Test(expected = QueryCreationException.class)
public void rejectsInvalidProperty() throws Exception {
JpaQueryMethod finderMethod = new JpaQueryMethod(method, extractor, em);
new QueryCreator(finderMethod).constructQuery();
}
@Test
public void splitsKeywordsCorrectly() throws SecurityException,
NoSuchMethodException {
method =
QueryCreatorUnitTests.class.getMethod(
"findByNameOrOrganization", String.class, String.class);
assertCreatesQueryForMethod(
"where x.name = :name or x.organization = :organization",
method);
}
/**
* @throws NoSuchMethodException
* @throws SecurityException
* @see #265
* @throws Exception
*/
@Test
public void createsQueryWithEmbeddableCorrectly() throws SecurityException,
NoSuchMethodException {
method =
getClass()
.getMethod("findByEmbeddable", SampleEmbeddable.class);
assertCreatesQueryForMethod("where x.embeddable = :embeddable", method);
}
@Test
public void createsQueryWithBetweenKeywordCorrectly() throws Exception {
method =
getClass().getMethod("findByStartDateBetweenAndName",
Date.class, Date.class, String.class);
assertCreatesQueryForMethod(
"where x.startDate between :first and :second and x.name = :name",
method);
}
@Test
public void createsQueryWithLessThanKeywordCorrectly() throws Exception {
method = getClass().getMethod("findByAgeLessThan", int.class);
assertCreatesQueryForMethod("where x.age < :age", method);
}
@Test
public void createsQueryWithGreaterThanKeywordCorrectly() throws Exception {
method = getClass().getMethod("findByAgeGreaterThan", int.class);
assertCreatesQueryForMethod("where x.age > :age", method);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsModifyingMethodWithoutBacking()
throws SecurityException, NoSuchMethodException {
Method invalidModifyingMethod =
InvalidDao.class.getMethod("updateMethod", String.class);
JpaQueryMethod method =
new JpaQueryMethod(invalidModifyingMethod, extractor, em);
new QueryCreator(method);
}
@Test
public void parsesLikeOperatorCorrectly() throws Exception {
method = getClass().getMethod("findByNameLike", String.class);
assertCreatesQueryForMethod("where x.name like :name", method);
}
@Test
public void parsesNotOperatorCorrectly() throws Exception {
method = getClass().getMethod("findByNameNot", String.class);
assertCreatesQueryForMethod("where x.name <> :name", method);
}
@Test
public void parsesNotNullOperatorCorrectly() throws Exception {
method = getClass().getMethod("findByNameNotNull");
assertCreatesQueryForMethod("where x.name is not null", method);
}
@Test
public void parsesLikeCorrectly() throws Exception {
method = getClass().getMethod("findByNameLike", String.class);
assertCreatesQueryForMethod("where x.name like :name", method);
}
@Test
public void parsesNotLikeCorrectly() throws Exception {
method = getClass().getMethod("findByNameNotLike", String.class);
assertCreatesQueryForMethod("where x.name not like :name", method);
}
@Test
public void parsesOrderByClauseCorrectly() throws Exception {
method =
getClass().getMethod("findByNameOrderByOrganizationDesc",
String.class);
assertCreatesQueryForMethod(
"where x.name = :name order by x.organization desc", method);
}
/**
* Asserts that the query created for the given {@link Method} results in a
* query ending with the given {@link String}.
*
* @param queryEnd
* @param method
*/
private void assertCreatesQueryForMethod(String queryEnd, Method method) {
JpaQueryMethod queryMethod = new JpaQueryMethod(method, extractor, em);
String result = new QueryCreator(queryMethod).constructQuery();
assertThat(result, endsWith(queryEnd));
}
/**
* Sample method to test failing query creation.
*
* @param firstname
* @return
*/
public User findByFirstnameAndMethod(String firstname) {
return null;
}
/**
* A method to check that query keyowrds are considered correctly. The
* {@link QueryCreator} must not detect the {@code Or} in
* {@code Organization} as keyword.
*
* @param name
* @param organization
* @return
*/
public SampleEntity findByNameOrOrganization(String name,
String organization) {
return null;
}
/**
* Sample method to create a finder query for that references an
* {@link Embeddable}.
*
* @see #265
* @param embeddable
* @return
*/
public SampleEntity findByEmbeddable(SampleEmbeddable embeddable) {
return null;
}
public SampleEntity findByStartDateBetweenAndName(Date first, Date second,
String name) {
return null;
}
public SampleEntity findByAgeLessThan(int age) {
return null;
}
public SampleEntity findByAgeGreaterThan(int age) {
return null;
}
public SampleEntity findByNameLike(String name) {
return null;
}
public SampleEntity findByNameNotLike(String name) {
return null;
}
public SampleEntity findByNameNot(String name) {
return null;
}
public SampleEntity findByNameNotNull() {
return null;
}
public SampleEntity findByNameOrderByOrganizationDesc(String name) {
return null;
}
/**
* Sample class for keyword split check.
*
* @author Oliver Gierke
*/
@SuppressWarnings("unused")
static class SampleEntity {
private String organization;
private String name;
private Date startDate;
private int age;
private SampleEmbeddable embeddable;
}
@Embeddable
@SuppressWarnings("unused")
static class SampleEmbeddable {
private String foo;
private String bar;
}
}

View File

@@ -69,7 +69,7 @@ public class SimpleJpaQueryUnitTests {
public void appliesHintsCorrectly() throws Exception {
SimpleJpaQuery hadesQuery = new SimpleJpaQuery(method, em, "foobar");
hadesQuery.createQuery(em, new ParameterBinder(method.getParameters(),
hadesQuery.createQuery(new ParameterBinder(method.getParameters(),
new Object[] { "gierke" }));
verify(query).setHint("foo", "bar");
@@ -86,6 +86,6 @@ public class SimpleJpaQueryUnitTests {
SimpleJpaQuery hadesQuery =
new SimpleJpaQuery(method, em, "select u from User u");
assertThat(hadesQuery.createCountQuery(em), is(query));
assertThat(hadesQuery.createCountQuery(null), is(query));
}
}

View File

@@ -72,8 +72,8 @@ public class JpaRepositoryFactoryBeanUnitTests {
// Setup standard factory configuration
factory =
JpaRepositoryFactoryBean.create(
SimpleSampleRepository.class, entityManager);
JpaRepositoryFactoryBean.create(SimpleSampleRepository.class,
entityManager);
factory.setEntityManager(entityManager);
}
@@ -139,8 +139,7 @@ public class JpaRepositoryFactoryBeanUnitTests {
JpaRepositoryFactoryBean<SampleRepository> factory =
JpaRepositoryFactoryBean.create(SampleRepository.class,
entityManager);
JpaRepositoryFactoryBean.create(SampleRepository.class, entityManager);
try {
factory.afterPropertiesSet();
@@ -166,8 +165,8 @@ public class JpaRepositoryFactoryBeanUnitTests {
void someSampleMethod();
}
private interface SampleRepository extends
JpaRepository<User, Integer>, SampleCustomDao {
private interface SampleRepository extends JpaRepository<User, Integer>,
SampleCustomDao {
}
}

View File

@@ -131,8 +131,7 @@ public class JpaRepositoryFactoryUnitTests {
dao.customMethod(1);
}
private interface SimpleSampleDao extends
JpaRepository<User, Integer> {
private interface SimpleSampleDao extends JpaRepository<User, Integer> {
@Transactional
User readByPrimaryKey(Integer primaryKey);