DATAJPA-218 - Add Predicate based QBE implementation.
We convert a given Example to a set of and combined Predicates using CriteriaBuilder. Cycles within associations are not allowed and result in an InvalidDataAccessApiUsageException. At this time only SingularAttributes are taken into concern. Switched to types used in DATACMNS-810. Related tickets: DATACMNS-810. Original pull request: #164.
This commit is contained in:
committed by
Oliver Gierke
parent
6ec173bc9a
commit
88abb0dbc3
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2016 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.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.From;
|
||||
import javax.persistence.criteria.Path;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
|
||||
import javax.persistence.metamodel.ManagedType;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Example.NullHandler;
|
||||
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link QueryByExamplePredicateBuilder} creates a single {@link CriteriaBuilder#and(Predicate...)} combined
|
||||
* {@link Predicate} for a given {@link Example}. <br />
|
||||
* The builder includes any {@link SingularAttribute} of the {@link Example#getProbe()} applying {@link String} and
|
||||
* {@literal null} matching strategies configured on the {@link Example}. Ignored paths are no matter of their actual
|
||||
* value not considered. <br />
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
public class QueryByExamplePredicateBuilder {
|
||||
|
||||
private static final Set<PersistentAttributeType> ASSOCIATION_TYPES;
|
||||
|
||||
static {
|
||||
ASSOCIATION_TYPES = new HashSet<PersistentAttributeType>(Arrays.asList(PersistentAttributeType.MANY_TO_MANY,
|
||||
PersistentAttributeType.MANY_TO_ONE, PersistentAttributeType.ONE_TO_MANY, PersistentAttributeType.ONE_TO_ONE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the {@link Predicate} representing the {@link Example}.
|
||||
*
|
||||
* @param root must not be {@literal null}.
|
||||
* @param cb must not be {@literal null}.
|
||||
* @param example must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public static <T> Predicate getPredicate(Root<T> root, CriteriaBuilder cb, Example<T> example) {
|
||||
|
||||
Assert.notNull(root, "Root must not be null!");
|
||||
Assert.notNull(cb, "CriteriaBuilder must not be null!");
|
||||
Assert.notNull(example, "Root must not be null!");
|
||||
|
||||
List<Predicate> predicates = getPredicates("", cb, root, root.getModel(), example.getSampleObject(), example,
|
||||
new PathNode("root", null, example.getSampleObject()));
|
||||
|
||||
if (predicates.isEmpty()) {
|
||||
return cb.isTrue(cb.literal(false));
|
||||
}
|
||||
|
||||
if (predicates.size() == 1) {
|
||||
return predicates.iterator().next();
|
||||
}
|
||||
|
||||
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
static List<Predicate> getPredicates(String path, CriteriaBuilder cb, Path<?> from, ManagedType<?> type,
|
||||
Object value, Example<?> example, PathNode currentNode) {
|
||||
|
||||
List<Predicate> predicates = new ArrayList<Predicate>();
|
||||
DirectFieldAccessFallbackBeanWrapper beanWrapper = new DirectFieldAccessFallbackBeanWrapper(value);
|
||||
|
||||
for (SingularAttribute attribute : type.getSingularAttributes()) {
|
||||
|
||||
String currentPath = !StringUtils.hasText(path) ? attribute.getName() : path + "." + attribute.getName();
|
||||
|
||||
if (example.isIgnoredPath(currentPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object attributeValue = example.getValueTransformerForPath(currentPath).convert(
|
||||
beanWrapper.getPropertyValue(attribute.getName()));
|
||||
|
||||
if (attributeValue == null) {
|
||||
|
||||
if (example.getNullHandler().equals(NullHandler.INCLUDE)) {
|
||||
predicates.add(cb.isNull(from.get(attribute)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED)) {
|
||||
|
||||
predicates.addAll(getPredicates(currentPath, cb, from.get(attribute.getName()),
|
||||
(ManagedType<?>) attribute.getType(), attributeValue, example, currentNode));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isAssociation(attribute)) {
|
||||
|
||||
if (!(from instanceof From)) {
|
||||
throw new JpaSystemException(new IllegalArgumentException(String.format(
|
||||
"Unexpected path type for %s. Found % where From.class was expected.", currentPath, from)));
|
||||
}
|
||||
|
||||
PathNode node = currentNode.add(attribute.getName(), attributeValue);
|
||||
if (node.spansCycle()) {
|
||||
throw new InvalidDataAccessApiUsageException(String.format(
|
||||
"Path '%s' from root %s must not span a cyclic property reference!\r\n%s", currentPath,
|
||||
ClassUtils.getShortName(example.getSampleType()), node));
|
||||
}
|
||||
|
||||
predicates.addAll(getPredicates(currentPath, cb, ((From<?, ?>) from).join(attribute.getName()),
|
||||
(ManagedType<?>) attribute.getType(), attributeValue, example, node));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attribute.getJavaType().equals(String.class)) {
|
||||
|
||||
Expression<String> expression = from.get(attribute);
|
||||
if (example.isIgnoreCaseForPath(currentPath)) {
|
||||
expression = cb.lower(expression);
|
||||
attributeValue = attributeValue.toString().toLowerCase();
|
||||
}
|
||||
|
||||
switch (example.getStringMatcherForPath(currentPath)) {
|
||||
|
||||
case DEFAULT:
|
||||
case EXACT:
|
||||
predicates.add(cb.equal(expression, attributeValue));
|
||||
break;
|
||||
case CONTAINING:
|
||||
predicates.add(cb.like(expression, "%" + attributeValue + "%"));
|
||||
break;
|
||||
case STARTING:
|
||||
predicates.add(cb.like(expression, attributeValue + "%"));
|
||||
break;
|
||||
case ENDING:
|
||||
predicates.add(cb.like(expression, "%" + attributeValue));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported StringMatcher "
|
||||
+ example.getStringMatcherForPath(currentPath));
|
||||
}
|
||||
} else {
|
||||
predicates.add(cb.equal(from.get(attribute), attributeValue));
|
||||
}
|
||||
}
|
||||
|
||||
return predicates;
|
||||
}
|
||||
|
||||
private static boolean isAssociation(Attribute<?, ?> attribute) {
|
||||
return ASSOCIATION_TYPES.contains(attribute.getPersistentAttributeType());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PathNode} is used to dynamically grow a directed graph structure that allows to detect cycles within its
|
||||
* direct predecessor nodes by comparing parent node values using {@link System#identityHashCode(Object)}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static class PathNode {
|
||||
|
||||
String name;
|
||||
PathNode parent;
|
||||
List<PathNode> siblings = new ArrayList<PathNode>();;
|
||||
Object value;
|
||||
|
||||
public PathNode(String edge, PathNode parent, Object value) {
|
||||
|
||||
this.name = edge;
|
||||
this.parent = parent;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
PathNode add(String attribute, Object value) {
|
||||
|
||||
PathNode node = new PathNode(attribute, this, value);
|
||||
siblings.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
boolean spansCycle() {
|
||||
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String identityHex = ObjectUtils.getIdentityHexString(value);
|
||||
PathNode tmp = parent;
|
||||
|
||||
while (tmp != null) {
|
||||
|
||||
if (ObjectUtils.getIdentityHexString(tmp.value).equals(identityHex)) {
|
||||
return true;
|
||||
}
|
||||
tmp = tmp.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (parent != null) {
|
||||
sb.append(parent.toString());
|
||||
sb.append(" -");
|
||||
sb.append(name);
|
||||
sb.append("-> ");
|
||||
}
|
||||
|
||||
sb.append("[{ ");
|
||||
sb.append(ObjectUtils.nullSafeToString(value));
|
||||
sb.append(" }]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.domain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A wrapper around a prototype object that can be used in <quote>Query by Example<quote> queries
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @param <T>
|
||||
*/
|
||||
public class Example<T> {
|
||||
|
||||
private final T prototype;
|
||||
private final Set<String> ignoredAttributes;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Example} with the given {@code prototype}.
|
||||
*
|
||||
* @param prototype must not be {@literal null}
|
||||
*/
|
||||
public Example(T prototype) {
|
||||
this(prototype, Collections.<String> emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Example} with the given {@code prototype} ignoring the given attributes.
|
||||
*
|
||||
* @param prototype prototype must not be {@literal null}
|
||||
* @param attributeNames prototype must not be {@literal null}
|
||||
*/
|
||||
public Example(T prototype, Set<String> attributeNames) {
|
||||
|
||||
Assert.notNull(prototype, "Prototype must not be null!");
|
||||
Assert.notNull(attributeNames, "attributeNames must not be null!");
|
||||
|
||||
this.prototype = prototype;
|
||||
this.ignoredAttributes = attributeNames;
|
||||
}
|
||||
|
||||
public T getPrototype() {
|
||||
return prototype;
|
||||
}
|
||||
|
||||
public Set<String> getIgnoredAttributes() {
|
||||
return Collections.unmodifiableSet(ignoredAttributes);
|
||||
}
|
||||
|
||||
public boolean isAttributeIgnored(String attributePath) {
|
||||
return ignoredAttributes.contains(attributePath);
|
||||
}
|
||||
|
||||
public static <T> Example<T> exampleOf(T prototype) {
|
||||
return new Example<T>(prototype);
|
||||
}
|
||||
|
||||
public static <T> Builder<T> newExample(T prototype) {
|
||||
return new Builder<T>(prototype);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Builder} for {@link Example}s.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @param <T>
|
||||
*/
|
||||
public static class Builder<T> {
|
||||
|
||||
private final T prototype;
|
||||
private Set<String> ignoredAttributeNames;
|
||||
|
||||
/**
|
||||
* @param prototype
|
||||
*/
|
||||
public Builder(T prototype) {
|
||||
|
||||
Assert.notNull(prototype, "Prototype must not be null!");
|
||||
|
||||
this.prototype = prototype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to specify attribute names that should be ignored.
|
||||
*
|
||||
* @param attributeNames
|
||||
* @return
|
||||
*/
|
||||
public Builder<T> ignoring(String... attributeNames) {
|
||||
|
||||
Assert.notNull(attributeNames, "attributeNames must not be null!");
|
||||
|
||||
return ignoring(Arrays.asList(attributeNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to specify attribute names that should be ignored.
|
||||
*
|
||||
* @param attributeNames
|
||||
* @return
|
||||
*/
|
||||
public Builder<T> ignoring(Collection<String> attributeNames) {
|
||||
|
||||
Assert.notNull(attributeNames, "attributeNames must not be null!");
|
||||
|
||||
this.ignoredAttributeNames = new HashSet<String>(attributeNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the actual {@link Example} instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Example<T> build() {
|
||||
return new Example<T>(prototype, ignoredAttributeNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor {
|
||||
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
|
||||
|
||||
/**
|
||||
* Hibernate persistence provider.
|
||||
@@ -117,13 +117,14 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor {
|
||||
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
|
||||
return new HibernateScrollableResultsIterator<Object>(jpaQuery);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* EclipseLink persistence provider.
|
||||
*/
|
||||
ECLIPSELINK(Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE),
|
||||
Collections.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
|
||||
ECLIPSELINK(Collections.singleton(ECLIPSELINK_ENTITY_MANAGER_INTERFACE), Collections
|
||||
.singleton(ECLIPSELINK_JPA_METAMODEL_TYPE)) {
|
||||
|
||||
public String extractQueryString(Query query) {
|
||||
return ((JpaQuery<?>) query).getDatabaseQuery().getJPQLString();
|
||||
@@ -163,6 +164,7 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor {
|
||||
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
|
||||
return new EclipseLinkScrollableResultsIterator<Object>(jpaQuery);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -201,6 +203,7 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor {
|
||||
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
|
||||
return new OpenJpaResultStreamingIterator<Object>(jpaQuery);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -243,6 +246,7 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor {
|
||||
public Object getIdentifierFrom(Object entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -384,8 +388,8 @@ public enum PersistenceProvider implements QueryExtractor,ProxyIdAccessor {
|
||||
}
|
||||
|
||||
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Streaming results is not implement for this PersistenceProvider: " + name());
|
||||
throw new UnsupportedOperationException("Streaming results is not implement for this PersistenceProvider: "
|
||||
+ name());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2013 the original author or authors.
|
||||
* Copyright 2008-2016 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.
|
||||
@@ -20,8 +20,10 @@ import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Example;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
@@ -29,6 +31,7 @@ import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
* JPA specific extension of {@link org.springframework.data.repository.Repository}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
|
||||
@@ -91,15 +94,34 @@ public interface JpaRepository<T, ID extends Serializable> extends PagingAndSort
|
||||
* @see EntityManager#getReference(Class, Object)
|
||||
*/
|
||||
T getOne(ID id);
|
||||
|
||||
|
||||
/**
|
||||
* Returns all instances of the type specified by the given {@link Example}.
|
||||
*
|
||||
* This method is deliberately <b>not<b> named {@code findByExample} to not interfere
|
||||
* with existing repository methods that rely on query derivation.
|
||||
*
|
||||
* @param example must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
List<T> findWithExample(Example<T> example);
|
||||
List<T> findAllByExample(Example<T> example);
|
||||
|
||||
/**
|
||||
* Returns all instances of the type specified by the given {@link Example}.
|
||||
*
|
||||
* @param example must not be {@literal null}.
|
||||
* @param sort can be {@literal null}.
|
||||
* @return all entities sorted by the given options
|
||||
* @since 1.10
|
||||
*/
|
||||
List<T> findAllByExample(Example<T> example, Sort sort);
|
||||
|
||||
/**
|
||||
* Returns a {@link Page} of entities meeting the paging restriction specified by the given {@link Example} limited to
|
||||
* criteria provided in the {@code Pageable} object.
|
||||
*
|
||||
* @param example must not be {@literal null}.
|
||||
* @param pageable can be {@literal null}.
|
||||
* @return a {@link Page} of entities
|
||||
* @since 1.10
|
||||
*/
|
||||
Page<T> findAllByExample(Example<T> example, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.notNull(method, "JpaQueryMethod must not be null!");
|
||||
|
||||
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(),
|
||||
getQueryMethod().getEntityInformation().getJavaType());
|
||||
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(), getQueryMethod()
|
||||
.getEntityInformation().getJavaType());
|
||||
|
||||
for (Map.Entry<String, Object> hint : hints.entrySet()) {
|
||||
query.setHint(hint.getKey(), hint.getValue());
|
||||
|
||||
@@ -19,7 +19,6 @@ import static org.springframework.data.jpa.repository.query.QueryUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -38,16 +37,14 @@ import javax.persistence.criteria.ParameterExpression;
|
||||
import javax.persistence.criteria.Path;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Example;
|
||||
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
@@ -59,7 +56,6 @@ import org.springframework.data.jpa.repository.query.QueryUtils;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
|
||||
@@ -420,33 +416,27 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.JpaRepository#findWithExample(org.springframework.data.jpa.domain.Example)
|
||||
*/
|
||||
public List<T> findWithExample(Example<T> example) {
|
||||
@Override
|
||||
public List<T> findAllByExample(Example<T> example) {
|
||||
return findAll(new ExampleSpecification<T>(example));
|
||||
}
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.JpaRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAllByExample(Example<T> example, Sort sort) {
|
||||
return findAll(new ExampleSpecification<T>(example), sort);
|
||||
}
|
||||
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<T> query = builder.createQuery(getDomainClass());
|
||||
Root<T> root = query.from(getDomainClass());
|
||||
|
||||
BeanWrapper bean = new BeanWrapperImpl(example.getPrototype());
|
||||
|
||||
List<Predicate> predicates = new ArrayList<Predicate>();
|
||||
for (Attribute<?, ?> attribute : em.getMetamodel().managedType(getDomainClass()).getAttributes()) {
|
||||
|
||||
Object value = bean.getPropertyValue(attribute.getName());
|
||||
|
||||
// TODO support different matching modes configured on the provided Example
|
||||
if (value == null //
|
||||
|| (value instanceof Collection && CollectionUtils.isEmpty((Collection<?>) value))
|
||||
|| (value instanceof Map && CollectionUtils.isEmpty((Map<?, ?>) value))
|
||||
|| example.isAttributeIgnored(attribute.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
predicates.add(builder.equal(root.get(attribute.getName()), value));
|
||||
}
|
||||
|
||||
return em.createQuery(query.where(predicates.toArray(new Predicate[predicates.size()]))).getResultList();
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.JpaRepository#findAllByExample(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Page<T> findAllByExample(Example<T> example, Pageable pageable) {
|
||||
return findAll(new ExampleSpecification<T>(example), pageable);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -698,4 +688,37 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
|
||||
return path.in(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} that gives access to the {@link Predicate} instance representing the values contained in the
|
||||
* {@link Example}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
* @param <T>
|
||||
*/
|
||||
private static class ExampleSpecification<T> implements Specification<T> {
|
||||
|
||||
private final Example<T> example;
|
||||
|
||||
/**
|
||||
* Creates new {@link ExampleSpecification}.
|
||||
*
|
||||
* @param example
|
||||
*/
|
||||
public ExampleSpecification(Example<T> example) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
this.example = example;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder)
|
||||
*/
|
||||
@Override
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
|
||||
return QueryByExamplePredicateBuilder.getPredicate(root, cb, example);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2016 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.convert;
|
||||
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.domain.Example.*;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.Path;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.ManagedType;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
import javax.persistence.metamodel.Type;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class QueryByExamplePredicateBuilderUnitTests {
|
||||
|
||||
@Mock CriteriaBuilder cb;
|
||||
@Mock Root root;
|
||||
@Mock EntityType<Person> personEntityType;
|
||||
@Mock Expression expressionMock;
|
||||
@Mock Predicate falsePredicate;
|
||||
@Mock Predicate dummyPredicate;
|
||||
@Mock Predicate listPredicate;
|
||||
@Mock Path dummyPath;
|
||||
|
||||
Set<SingularAttribute<? super Person, ?>> personEntityAttribtues;
|
||||
|
||||
SingularAttribute<? super Person, Long> personIdAttribute;
|
||||
SingularAttribute<? super Person, String> personFirstnameAttribute;
|
||||
SingularAttribute<? super Person, Long> personAgeAttribute;
|
||||
SingularAttribute<? super Person, Person> personFatherAttribute;
|
||||
SingularAttribute<? super Person, Skill> personSkillAttribute;
|
||||
SingularAttribute<? super Person, Address> personAddressAttribute;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
personIdAttribute = new SingluarAttributeStub<Person, Long>("id", PersistentAttributeType.BASIC, Long.class);
|
||||
personFirstnameAttribute = new SingluarAttributeStub<Person, String>("firstname", PersistentAttributeType.BASIC,
|
||||
String.class);
|
||||
personAgeAttribute = new SingluarAttributeStub<Person, Long>("age", PersistentAttributeType.BASIC, Long.class);
|
||||
personFatherAttribute = new SingluarAttributeStub<Person, Person>("father", PersistentAttributeType.MANY_TO_ONE,
|
||||
Person.class);
|
||||
personSkillAttribute = new SingluarAttributeStub<Person, Skill>("skill", PersistentAttributeType.MANY_TO_ONE,
|
||||
Skill.class);
|
||||
personAddressAttribute = new SingluarAttributeStub<Person, Address>("address", PersistentAttributeType.EMBEDDED,
|
||||
Address.class);
|
||||
|
||||
personEntityAttribtues = new LinkedHashSet<SingularAttribute<? super Person, ?>>();
|
||||
personEntityAttribtues.add(personIdAttribute);
|
||||
personEntityAttribtues.add(personFirstnameAttribute);
|
||||
personEntityAttribtues.add(personAgeAttribute);
|
||||
personEntityAttribtues.add(personFatherAttribute);
|
||||
personEntityAttribtues.add(personAddressAttribute);
|
||||
personEntityAttribtues.add(personSkillAttribute);
|
||||
|
||||
when(root.get(any(SingularAttribute.class))).thenReturn(dummyPath);
|
||||
when(root.getModel()).thenReturn(personEntityType);
|
||||
when(personEntityType.getSingularAttributes()).thenReturn(personEntityAttribtues);
|
||||
|
||||
when(cb.equal(any(Expression.class), any(String.class))).thenReturn(dummyPredicate);
|
||||
when(cb.equal(any(Expression.class), any(Long.class))).thenReturn(dummyPredicate);
|
||||
when(cb.like(any(Expression.class), any(String.class))).thenReturn(dummyPredicate);
|
||||
|
||||
when(cb.literal(any(Boolean.class))).thenReturn(expressionMock);
|
||||
when(cb.isTrue(eq(expressionMock))).thenReturn(falsePredicate);
|
||||
when(cb.and(Matchers.<Predicate> anyVararg())).thenReturn(listPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getPredicateShouldThrowExceptionOnNullRoot() {
|
||||
QueryByExamplePredicateBuilder.getPredicate(null, cb, exampleOf(new Person()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getPredicateShouldThrowExceptionOnNullCriteriaBuilder() {
|
||||
QueryByExamplePredicateBuilder.getPredicate(root, null, exampleOf(new Person()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getPredicateShouldThrowExceptionOnNullExample() {
|
||||
QueryByExamplePredicateBuilder.getPredicate(root, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void emptyCriteriaListShouldResultFalsePredicate() {
|
||||
assertThat(QueryByExamplePredicateBuilder.getPredicate(root, cb, exampleOf(new Person())), equalTo(falsePredicate));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void singleElementCriteriaShouldJustReturnIt() {
|
||||
|
||||
Person p = new Person();
|
||||
p.firstname = "foo";
|
||||
|
||||
assertThat(QueryByExamplePredicateBuilder.getPredicate(root, cb, exampleOf(p)), equalTo(dummyPredicate));
|
||||
verify(cb, times(1)).equal(any(Expression.class), eq("foo"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void multiPredicateCriteriaShouldReturnCombinedOnes() {
|
||||
|
||||
Person p = new Person();
|
||||
p.firstname = "foo";
|
||||
p.age = 2L;
|
||||
|
||||
assertThat(QueryByExamplePredicateBuilder.getPredicate(root, cb, exampleOf(p)), equalTo(listPredicate));
|
||||
|
||||
verify(cb, times(1)).equal(any(Expression.class), eq("foo"));
|
||||
verify(cb, times(1)).equal(any(Expression.class), eq(2L));
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
@Id Long id;
|
||||
String firstname;
|
||||
Long age;
|
||||
|
||||
Person father;
|
||||
Address address;
|
||||
Skill skill;
|
||||
}
|
||||
|
||||
static class Address {
|
||||
|
||||
String city;
|
||||
String country;
|
||||
}
|
||||
|
||||
static class Skill {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
static class SingluarAttributeStub<X, T> implements SingularAttribute<X, T> {
|
||||
|
||||
private String name;
|
||||
private PersistentAttributeType attributeType;
|
||||
private Class<T> type;
|
||||
|
||||
public SingluarAttributeStub(String name,
|
||||
javax.persistence.metamodel.Attribute.PersistentAttributeType attributeType, Class<T> type) {
|
||||
this.name = name;
|
||||
this.attributeType = attributeType;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public javax.persistence.metamodel.Attribute.PersistentAttributeType getPersistentAttributeType() {
|
||||
return attributeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedType<X> getDeclaringType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getJavaType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Member getJavaMember() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssociation() {
|
||||
return !attributeType.equals(PersistentAttributeType.BASIC)
|
||||
&& !attributeType.equals(PersistentAttributeType.EMBEDDED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public javax.persistence.metamodel.Bindable.BindableType getBindableType() {
|
||||
return BindableType.SINGULAR_ATTRIBUTE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getBindableJavaType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isId() {
|
||||
return ObjectUtils.nullSafeEquals(name, "id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptional() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<T> getType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,6 @@ import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.data.jpa.domain.sample.Category;
|
||||
import org.springframework.data.jpa.domain.sample.Product;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.provider.ProxyIdAccessor;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.jpa.repository.sample.CategoryRepository;
|
||||
import org.springframework.data.jpa.repository.sample.ProductRepository;
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
package org.springframework.data.jpa.repository;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.domain.Example.*;
|
||||
import static org.springframework.data.domain.Sort.Direction.*;
|
||||
import static org.springframework.data.jpa.domain.Specifications.*;
|
||||
import static org.springframework.data.jpa.domain.Specifications.not;
|
||||
import static org.springframework.data.jpa.domain.sample.UserSpecifications.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -42,6 +41,7 @@ import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -49,20 +49,24 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Example.StringMatcher;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.PropertySpecifier;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.jpa.domain.Example;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.domain.sample.Address;
|
||||
import org.springframework.data.jpa.domain.sample.Role;
|
||||
import org.springframework.data.jpa.domain.sample.SpecialUser;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.sample.SampleEvaluationContextExtension.SampleSecurityContextHolder;
|
||||
import org.springframework.data.jpa.repository.sample.UserRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -1907,42 +1911,343 @@ public class UserRepositoryTests {
|
||||
|
||||
assertThat(users, hasSize(2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void queryByExample() {
|
||||
|
||||
public void findAllByExample() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setAge(28);
|
||||
prototype.setCreatedAt(null);
|
||||
|
||||
List<User> users = repository.findWithExample(Example.exampleOf(prototype));
|
||||
|
||||
|
||||
List<User> users = repository.findAllByExample(exampleOf(prototype));
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void findAllByNullExample() {
|
||||
repository.findAllByExample(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void queryByExampleWithExcludedAttributes() {
|
||||
|
||||
public void findAllByExampleWithExcludedAttributes() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setAge(28);
|
||||
|
||||
List<User> users = repository.findWithExample(Example.newExample(prototype).ignoring("createdAt").build());
|
||||
|
||||
|
||||
List<User> users = repository.findAllByExample(newExampleOf(prototype).ignore("createdAt").get());
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithAssociation() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
firstUser.setManager(secondUser);
|
||||
thirdUser.setManager(firstUser);
|
||||
repository.save(Arrays.asList(firstUser, thirdUser));
|
||||
|
||||
User manager = new User();
|
||||
manager.setLastname("Arrasz");
|
||||
manager.setAge(secondUser.getAge());
|
||||
manager.setCreatedAt(null);
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setCreatedAt(null);
|
||||
prototype.setManager(manager);
|
||||
|
||||
List<User> users = repository.findAllByExample(newExampleOf(prototype).ignore("age").get());
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithEmbedded() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
firstUser.setAddress(new Address("germany", "dresden", "", ""));
|
||||
repository.save(firstUser);
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setCreatedAt(null);
|
||||
prototype.setAddress(new Address("germany", null, null, null));
|
||||
|
||||
List<User> users = repository.findAllByExample(newExampleOf(prototype).ignore("age").get());
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithStartingStringMatcher() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("Ol");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsStartingWith().ignore("age", "createdAt").get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example);
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithEndingStringMatcher() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("ver");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsEndingWith().ignore("age", "createdAt").get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example);
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void findAllByExampleWithRegexStringMatcher() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("^Oliver$");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).withStringMatcher(StringMatcher.REGEX).ignore("age", "createdAt")
|
||||
.get();
|
||||
|
||||
repository.findAllByExample(example);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithIgnoreCase() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("oLiVer");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsWithIgnoreCase().ignore("age", "createdAt").get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example);
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithStringMatcherAndIgnoreCase() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("oLiV");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsStartingWith().matchStringsWithIgnoreCase()
|
||||
.ignore("age", "createdAt").get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example);
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithIncludeNull() {
|
||||
|
||||
// something is wrong with OpenJPA - I do not know what
|
||||
Assume.assumeThat(PersistenceProvider.fromEntityManager(em), not(equalTo(PersistenceProvider.OPEN_JPA)));
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
firstUser.setAddress(new Address("andor", "caemlyn", "", ""));
|
||||
|
||||
User fifthUser = new User();
|
||||
fifthUser.setEmailAddress("foo@bar.com");
|
||||
fifthUser.setActive(firstUser.isActive());
|
||||
fifthUser.setAge(firstUser.getAge());
|
||||
fifthUser.setFirstname(firstUser.getFirstname());
|
||||
fifthUser.setLastname(firstUser.getLastname());
|
||||
|
||||
repository.save(Arrays.asList(firstUser, fifthUser));
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname(firstUser.getFirstname());
|
||||
|
||||
Example<User> example = newExampleOf(prototype).includeNullValues()
|
||||
.ignore("id", "binaryData", "lastname", "emailAddress", "age", "createdAt").get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example);
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(fifthUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithPropertySpecifier() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("oLi");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsWithIgnoreCase().ignore("age", "createdAt")
|
||||
.withPropertySpecifier(PropertySpecifier.newPropertySpecifier("firstname").matchStringStartingWith().get())
|
||||
.get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example);
|
||||
|
||||
assertThat(users, hasSize(1));
|
||||
assertThat(users.get(0), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithSort() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User user1 = new User("Oliver", "Srping", "o@s.de");
|
||||
user1.setAge(30);
|
||||
|
||||
repository.save(user1);
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("oLi");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsStartingWith().matchStringsWithIgnoreCase()
|
||||
.ignore("age", "createdAt").get();
|
||||
|
||||
List<User> users = repository.findAllByExample(example, new Sort(DESC, "age"));
|
||||
|
||||
assertThat(users, hasSize(2));
|
||||
assertThat(users.get(0), is(user1));
|
||||
assertThat(users.get(1), is(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test
|
||||
public void findAllByExampleWithPageable() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
for (int i = 0; i < 99; i++) {
|
||||
User user1 = new User("Oliver-" + i, "Srping", "o" + i + "@s.de");
|
||||
user1.setAge(30 + i);
|
||||
|
||||
repository.save(user1);
|
||||
}
|
||||
|
||||
User prototype = new User();
|
||||
prototype.setFirstname("oLi");
|
||||
|
||||
Example<User> example = newExampleOf(prototype).matchStringsStartingWith().matchStringsWithIgnoreCase()
|
||||
.ignore("age", "createdAt").get();
|
||||
|
||||
Page<User> users = repository.findAllByExample(example, new PageRequest(0, 10, new Sort(DESC, "age")));
|
||||
|
||||
assertThat(users.getSize(), is(10));
|
||||
assertThat(users.hasNext(), is(true));
|
||||
assertThat(users.getTotalElements(), is(100L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void findAllByExampleShouldNotAllowCycles() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User user1 = new User();
|
||||
user1.setFirstname("user1");
|
||||
|
||||
user1.setManager(user1);
|
||||
|
||||
Example<User> example = newExampleOf(user1).matchStringsStartingWith().matchStringsWithIgnoreCase()
|
||||
.ignore("age", "createdAt").get();
|
||||
|
||||
repository.findAllByExample(example, new PageRequest(0, 10, new Sort(DESC, "age")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-218
|
||||
*/
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void findAllByExampleShouldNotAllowCyclesOverSeveralInstances() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
User user1 = new User();
|
||||
user1.setFirstname("user1");
|
||||
|
||||
User user2 = new User();
|
||||
user2.setFirstname("user2");
|
||||
|
||||
user1.setManager(user2);
|
||||
user2.setManager(user1);
|
||||
|
||||
Example<User> example = newExampleOf(user1).matchStringsStartingWith().matchStringsWithIgnoreCase()
|
||||
.ignore("age", "createdAt").get();
|
||||
|
||||
repository.findAllByExample(example, new PageRequest(0, 10, new Sort(DESC, "age")));
|
||||
}
|
||||
|
||||
private Page<User> executeSpecWithSort(Sort sort) {
|
||||
|
||||
flushTestUsers();
|
||||
@@ -1953,4 +2258,5 @@ public class UserRepositoryTests {
|
||||
assertThat(result.getTotalElements(), is(2L));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user