diff --git a/pom.xml b/pom.xml
index 303a521e3..fa6a9fef7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
1.8.0.10
2.0.0
2.2.1
- 1.7.0.M1
+ 1.7.0.BUILD-SNAPSHOT
@@ -413,6 +413,69 @@
org.codehaus.mojo
wagon-maven-plugin
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ default-compile
+
+ compile
+
+
+
+
+ compile
+
+
+
+ org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor
+
+ ${project.build.directory}/generated-sources/test
+
+ only
+
+
+
+
+
+
+
+ org.hibernate
+ hibernate-jpamodelgen
+ 1.2.0.Final
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 1.8
+
+
+ add-test-source
+ generate-test-sources
+
+ add-test-source
+
+
+
+ ${project.build.directory}/generated-sources/test
+
+
+
+
+
diff --git a/src/main/java/org/springframework/data/jpa/domain/JpaSort.java b/src/main/java/org/springframework/data/jpa/domain/JpaSort.java
new file mode 100644
index 000000000..e9953bd2b
--- /dev/null
+++ b/src/main/java/org/springframework/data/jpa/domain/JpaSort.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Path;
+import javax.persistence.criteria.Root;
+import javax.persistence.metamodel.SingularAttribute;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.util.Assert;
+
+/**
+ * Sort option for queries that wraps JPA MetaModel {@link Expression}s for sorting.
+ *
+ * @author Thomas Darimont
+ */
+public class JpaSort extends Sort {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new {@link JpaSort} instance with the given {@link Path}s.
+ *
+ * @param jpaPaths must not be {@literal null} or empty.
+ */
+ public JpaSort(Path>... jpaPaths) {
+ this(Arrays.asList(jpaPaths));
+ }
+
+ /**
+ * Creates a new {@link JpaSort} instance with the given {@link Path}s.
+ *
+ * @param direction
+ * @param jpaPaths must not be {@literal null} or empty.
+ */
+ public JpaSort(Direction direction, Path>... jpaPaths) {
+ this(direction, Arrays.asList(jpaPaths));
+ }
+
+ /**
+ * Creates a new {@link JpaSort} instance with the given {@link Path}s.
+ *
+ * @param jpaPaths must not be {@literal null} or empty.
+ */
+ public JpaSort(List> jpaPaths) {
+ this(DEFAULT_DIRECTION, jpaPaths);
+ }
+
+ /**
+ * Creates a new {@link JpaSort} instance with the given {@link Path}s.
+ *
+ * @param direction
+ * @param jpaPaths must not be {@literal null} or empty.
+ */
+ public JpaSort(Direction direction, List> jpaPaths) {
+ super(direction, toPropertyPaths(jpaPaths));
+ }
+
+ /**
+ * @param jpaPaths must not be {@literal null} or empty.
+ * @return
+ */
+ private static List toPropertyPaths(List> jpaPaths) {
+
+ Assert.notEmpty(jpaPaths, "Jpa orders must not be null or empty!");
+
+ List propertyPaths = new ArrayList();
+
+ for (Path> path : jpaPaths) {
+ propertyPaths.add(toPropertyPath(path));
+ }
+
+ return propertyPaths;
+ }
+
+ /**
+ * @param path
+ * @return
+ */
+ @SuppressWarnings("rawtypes")
+ private static String toPropertyPath(Path path) {
+
+ StringBuilder attributePath = new StringBuilder();
+ Path current = path;
+ while (!(current instanceof Root)) {
+ String attributePathSegment = ((SingularAttribute) current.getModel()).getName();
+ if (attributePath.length() > 0) {
+ attributePath.insert(0, ".");
+ }
+ attributePath.insert(0, attributePathSegment);
+ current = current.getParentPath();
+ }
+
+ return attributePath.toString();
+ }
+}
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java
index cf2d4996b..d7695d854 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryDslJpaRepository.java
@@ -25,6 +25,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.EntityPathResolver;
+import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
@@ -99,7 +100,10 @@ public class QueryDslJpaRepository extends SimpleJpa
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, com.mysema.query.types.OrderSpecifier>[])
*/
public List findAll(Predicate predicate, OrderSpecifier>... orders) {
- return createQuery(predicate).orderBy(orders).list(path);
+
+ JPQLQuery query = createQuery(predicate);
+ query = querydsl.applySorting(new QSort(orders), query);
+ return query.list(path);
}
/*
diff --git a/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java b/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java
index 6efedc31d..c75cf725d 100644
--- a/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java
+++ b/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 the original author or authors.
+ * Copyright 2012-2013 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.
@@ -15,7 +15,9 @@
*/
package org.springframework.data.jpa.repository.support;
+import java.util.ArrayList;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
@@ -25,6 +27,7 @@ import javax.persistence.metamodel.EntityType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
+import org.springframework.data.querydsl.QSort;
import org.springframework.util.Assert;
import com.mysema.query.jpa.EclipseLinkTemplates;
@@ -35,6 +38,7 @@ import com.mysema.query.jpa.impl.JPAQuery;
import com.mysema.query.types.EntityPath;
import com.mysema.query.types.Expression;
import com.mysema.query.types.OrderSpecifier;
+import com.mysema.query.types.Path;
import com.mysema.query.types.path.EntityPathBase;
import com.mysema.query.types.path.PathBuilder;
@@ -42,6 +46,7 @@ import com.mysema.query.types.path.PathBuilder;
* Helper instance to ease access to Querydsl JPA query API.
*
* @author Oliver Gierke
+ * @author Thomas Darimont
*/
public class Querydsl {
@@ -126,8 +131,87 @@ public class Querydsl {
return query;
}
+ if (sort instanceof QSort) {
+ return addOrderByFrom((QSort) sort, query);
+ }
+
+ return addOrderByFrom(sort, query);
+ }
+
+ /**
+ * Applies the given {@link OrderSpecifier}s to the given {@link JPQLQuery}. Potentially transforms the given
+ * {@code OrderSpecifier}s to be able to injection potentially necessary left-joins.
+ *
+ * @param qsort must not be {@literal null}.
+ * @param query must not be {@literal null}.
+ */
+
+ private JPQLQuery addOrderByFrom(QSort qsort, JPQLQuery query) {
+ return query.orderBy(adjustOrderSpecifierIfNecessary(qsort.getOrderSpecifiers(), query));
+ }
+
+ /**
+ * Rewrites the given {@link OrderSpecifier} if necessary, e.g. generates proper aliases and left-joins to be created
+ * if we detect ordering by an nested attribute.
+ *
+ * @param originalOrderSpecifiers must not be {@literal null}.
+ * @param query must not be {@literal null}.
+ * @return
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private OrderSpecifier>[] adjustOrderSpecifierIfNecessary(List> originalOrderSpecifiers,
+ JPQLQuery query) {
+
+ Assert.notNull(originalOrderSpecifiers, "Original order specifiers must not be null!");
+ Assert.notNull(query, "Query must not be null!");
+
+ boolean orderModificationNecessary = false;
+ List> modifiedOrderSpecifiers = new ArrayList>();
+
+ for (OrderSpecifier> order : originalOrderSpecifiers) {
+
+ Path targetPath = ((Path) order.getTarget()).getMetadata().getParent();
+
+ boolean targetPathRootIsEntityRoot = targetPath.getRoot().equals(builder.getRoot());
+ boolean targetPathEqualsRootEnityPath = targetPath.toString().equals(builder.toString());
+
+ if (!targetPathRootIsEntityRoot) {
+
+ query.leftJoin((EntityPath) builder.get((String) targetPath.getMetadata().getElement()), targetPath);
+ } else if (targetPathRootIsEntityRoot && !targetPathEqualsRootEnityPath) {
+
+ PathBuilder joinPathBuilder = new PathBuilder(targetPath.getType(), targetPath.getMetadata().getElement()
+ .toString());
+ query.leftJoin((EntityPath) targetPath, joinPathBuilder);
+ OrderSpecifier> modifiedOrder = new OrderSpecifier(order.getOrder(), joinPathBuilder.get(((Path) order
+ .getTarget()).getMetadata().getElement().toString()), order.getNullHandling());
+ modifiedOrderSpecifiers.add(modifiedOrder);
+ orderModificationNecessary = true;
+ continue;
+ }
+
+ modifiedOrderSpecifiers.add(order);
+ }
+
+ return orderModificationNecessary ? modifiedOrderSpecifiers.toArray(new OrderSpecifier>[modifiedOrderSpecifiers
+ .size()]) : originalOrderSpecifiers.toArray(new OrderSpecifier>[originalOrderSpecifiers.size()]);
+ }
+
+ /**
+ * Converts the {@link Order} items of the given {@link Sort} into {@link OrderSpecifier} and attaches those to the
+ * given {@link JPQLQuery}.
+ *
+ * @param sort must not be {@literal null}.
+ * @param query must not be {@literal null}.
+ * @return
+ */
+ private JPQLQuery addOrderByFrom(Sort sort, JPQLQuery query) {
+
+ Assert.notNull(sort, "Sort must not be null!");
+ Assert.notNull(query, "Query must not be null!");
+
for (Order order : sort) {
- query.orderBy(toOrder(order, query));
+ query.orderBy(toOrderSpecifier(order, query));
}
return query;
@@ -140,7 +224,7 @@ public class Querydsl {
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
- private OrderSpecifier> toOrder(Order order, JPQLQuery query) {
+ private OrderSpecifier> toOrderSpecifier(Order order, JPQLQuery query) {
Expression> property = createExpressionAndPotentionallyAddLeftJoinForReferencedAssociation(order, query);
@@ -190,20 +274,29 @@ public class Querydsl {
}
/**
- * @param attribute
- * @param order
- * @param query
+ * Adds a left-join to the given {@link JPQLQuery} with a proper alias for the property referenced on the given
+ * {@link Order} relative to the given parent {@link Attribute}.
+ *
+ * @param parentAttribute must not be {@literal null}.
+ * @param order must not be {@literal null}.
+ * @param query must not be {@literal null}.
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
- private Expression> createLeftJoinForAttributeInOrderBy(Attribute, ?> attribute, Order order, JPQLQuery query) {
+ private Expression> createLeftJoinForAttributeInOrderBy(Attribute, ?> parentAttribute, Order order,
+ JPQLQuery query) {
- EntityPathBase> associationPathRoot = new EntityPathBase