diff --git a/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java b/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java
new file mode 100644
index 000000000..2e205e337
--- /dev/null
+++ b/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java
@@ -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}.
+ * 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.
+ *
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+public class QueryByExamplePredicateBuilder {
+
+ private static final Set ASSOCIATION_TYPES;
+
+ static {
+ ASSOCIATION_TYPES = new HashSet(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 Predicate getPredicate(Root root, CriteriaBuilder cb, Example 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 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 getPredicates(String path, CriteriaBuilder cb, Path> from, ManagedType> type,
+ Object value, Example> example, PathNode currentNode) {
+
+ List predicates = new ArrayList();
+ 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 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 siblings = new ArrayList();;
+ 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();
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/jpa/domain/Example.java b/src/main/java/org/springframework/data/jpa/domain/Example.java
deleted file mode 100644
index e3cc4d507..000000000
--- a/src/main/java/org/springframework/data/jpa/domain/Example.java
+++ /dev/null
@@ -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 Query by Example queries
- *
- * @author Thomas Darimont
- * @param
- */
-public class Example {
-
- private final T prototype;
- private final Set 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. 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 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 getIgnoredAttributes() {
- return Collections.unmodifiableSet(ignoredAttributes);
- }
-
- public boolean isAttributeIgnored(String attributePath) {
- return ignoredAttributes.contains(attributePath);
- }
-
- public static Example exampleOf(T prototype) {
- return new Example(prototype);
- }
-
- public static Builder newExample(T prototype) {
- return new Builder(prototype);
- }
-
- /**
- * A {@link Builder} for {@link Example}s.
- *
- * @author Thomas Darimont
- * @param
- */
- public static class Builder {
-
- private final T prototype;
- private Set 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 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 ignoring(Collection attributeNames) {
-
- Assert.notNull(attributeNames, "attributeNames must not be null!");
-
- this.ignoredAttributeNames = new HashSet(attributeNames);
- return this;
- }
-
- /**
- * Constructs the actual {@link Example} instance.
- *
- * @return
- */
- public Example build() {
- return new Example(prototype, ignoredAttributeNames);
- }
- }
-}
diff --git a/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java
index 35c28e8ec..901dd92d6 100644
--- a/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java
+++ b/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java
@@ -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