Skip to content

Commit

Permalink
Add support for view property sorting with Spring Sort
Browse files Browse the repository at this point in the history
  • Loading branch information
Giovanni Lovato authored and beikov committed Sep 8, 2019
1 parent dcf77e8 commit 4f4acaa
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 15 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -681,9 +681,9 @@ DataNucleus requires bytecode enhancement to work properly which requires an ext
Usually when switching the JPA provider profile, it is recommended to trigger a _Rebuild Project_ action in IntelliJ to avoid strange errors causes by previous bytecode enhancement runs.
After that, the entities in the project *core/testsuite* have to be enhanced. This is done through a Maven command.

* DataNucleus 4: `mvn -P "datanucleus-4" -pl core/testsuite datanucleus:enhance`
* DataNucleus 5: `mvn -P "datanucleus-5" -pl core/testsuite datanucleus:enhance`
* DataNucleus 5.1: `mvn -P "datanucleus-5.1" -pl core/testsuite datanucleus:enhance`
* DataNucleus 4: `mvn -P "datanucleus-4,h2,deltaspike-1.8,spring-data-2.0.x,eclipselink" -pl core/testsuite,integration/spring-data/testsuite datanucleus:enhance`
* DataNucleus 5: `mvn -P "datanucleus-5,h2,deltaspike-1.8,spring-data-2.0.x,eclipselink" -pl core/testsuite,integration/spring-data/testsuite datanucleus:enhance`
* DataNucleus 5.1: `mvn -P "datanucleus-5.1,h2,deltaspike-1.8,spring-data-2.0.x,eclipselink" -pl core/testsuite,integration/spring-data/testsuite datanucleus:enhance`

After doing that, you should be able to execute any test in IntelliJ.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2014 - 2019 Blazebit.
*
* 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 com.blazebit.persistence.spring.data.base;

import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.view.EntityViewManager;
import com.blazebit.persistence.view.metamodel.ManagedViewType;
import com.blazebit.persistence.view.metamodel.MethodAttribute;
import com.blazebit.persistence.view.metamodel.PluralAttribute;
import com.blazebit.persistence.view.metamodel.SingularAttribute;
import com.blazebit.persistence.view.metamodel.Type;
import com.blazebit.persistence.view.metamodel.ViewType;
import org.springframework.data.domain.Sort;

/**
* Utility methods to handle entity view sorting.
*
* @author Moritz Becker
* @author Giovanni Lovato
* @since 1.4.0
*/

public final class EntityViewSortUtil {

private EntityViewSortUtil() {
}

/**
* Resolves the deterministic select item alias for an entity view attribute.
*
* @param evm entity view manager
* @param entityViewClass entity view class
* @param attributePath the absolute attribute path based on the {@code entityViewClass} for which the select alias should be resolved
* @return the select item alias for the (nested) entity view attribute targeted by {@code attributePath} or {@code null}
* if the {@code attributePath} cannot be resolved
*/
private static String resolveViewAttributeSelectAlias(EntityViewManager evm, Class<?> entityViewClass, String attributePath) {
ManagedViewType<?> viewType = evm.getMetamodel().view(entityViewClass);
StringBuilder aliasBuilder = new StringBuilder(((ViewType<?>) viewType).getName());
for (String pathElement : attributePath.split("\\.")) {
if (viewType == null) {
return null;
} else {
MethodAttribute<?, ?> attribute = viewType.getAttribute(pathElement);
if (attribute == null) {
return null;
} else {
aliasBuilder.append('_').append(pathElement);
Type<?> type;
if (attribute instanceof SingularAttribute) {
type = ((SingularAttribute<?, ?>) attribute).getType();
} else {
type = ((PluralAttribute<?, ?, ?>) attribute).getElementType();
}
if (type instanceof ManagedViewType) {
// It's a view type, continue descending
viewType = (ManagedViewType<?>) type;
} else {
// It's a basic type, so we cannot got further
viewType = null;
}
}
}
}
return aliasBuilder.toString();
}

public static void applySort(EntityViewManager evm, Class<?> entityViewClass, FullQueryBuilder<?, ?> cb, Sort sort) {
for (Sort.Order order : sort) {
String entityViewAttributeAlias;
if ((entityViewAttributeAlias = EntityViewSortUtil.resolveViewAttributeSelectAlias(evm, entityViewClass, order.getProperty())) == null) {
cb.orderBy(order.getProperty(), order.isAscending(), order.getNullHandling() == Sort.NullHandling.NULLS_FIRST);
} else {
cb.orderBy(entityViewAttributeAlias, order.isAscending(), order.getNullHandling() == Sort.NullHandling.NULLS_FIRST);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
package com.blazebit.persistence.spring.data.base.query;

import com.blazebit.persistence.CriteriaBuilderFactory;
import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.OrderByBuilder;
import com.blazebit.persistence.PagedList;
import com.blazebit.persistence.PaginatedCriteriaBuilder;
import com.blazebit.persistence.criteria.BlazeCriteria;
import com.blazebit.persistence.criteria.BlazeCriteriaBuilder;
import com.blazebit.persistence.criteria.BlazeCriteriaQuery;
import com.blazebit.persistence.spring.data.base.EntityViewSortUtil;
import com.blazebit.persistence.spring.data.base.query.JpaParameters.JpaParameter;
import com.blazebit.persistence.spring.data.repository.EntityViewSettingProcessor;
import com.blazebit.persistence.view.EntityViewManager;
Expand Down Expand Up @@ -58,6 +61,16 @@
/**
* Implementation is similar to {@link PartTreeJpaQuery} but was modified to work with entity views.
*
* About sorting
* The implementation of both {@link AbstractPartTreeBlazePersistenceQuery} and {@link com.blazebit.persistence.spring.data.base.repository.AbstractEntityViewAwareRepository}
* aims to support mixed sorting, i.e. sorting by entity view attributes and entity attributes at the same time.
* To make this work we abstain from using the attribute sorter API of {@link EntityViewSetting} because it would not
* allow us to add the entity attribute sorts. Instead, we add both entity view attribute and entity attribute sorters
* uniformly via the core order API of {@link OrderByBuilder} *after* the entity view settings have been applied:
* - For entity view attribute sorts we resolve deterministic aliases from the order property that correspond to the
* select item aliases
* - For entity attribute sorts we just use the order property as is
*
* @author Moritz Becker
* @author Christian Beikov
* @since 1.2.0
Expand Down Expand Up @@ -294,7 +307,9 @@ protected TypedQuery<?> createQuery0(CriteriaQuery<?> criteriaQuery, Object[] va
} else {
EntityViewSetting<?, ?> setting = EntityViewSetting.create(entityViewClass);
setting = processSetting(setting, values);
return evm.applySetting(setting, cb).getQuery();
FullQueryBuilder<?, ?> fqb = evm.applySetting(setting, cb);
processSort(fqb, values);
return fqb.getQuery();
}
}

Expand All @@ -305,7 +320,7 @@ Query createPaginatedQuery(Object[] values, boolean withCount) {

if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) {
FixedJpaQueryCreator creator = createCreator(accessor, persistenceProvider);
criteriaQuery = invokeQueryCreator(creator, getDynamicSort(values));
criteriaQuery = invokeQueryCreator(creator, entityViewClass == null ? getDynamicSort(values) : null);
expressions = creator.getParameterExpressions();
}

Expand All @@ -329,11 +344,15 @@ Query createPaginatedQuery(Object[] values, boolean withCount) {
if (withCount) {
EntityViewSetting<?, ?> setting = EntityViewSetting.create(entityViewClass, firstResult, maxResults);
setting = processSetting(setting, values);
jpaQuery = (TypedQuery<Object>) ((PaginatedCriteriaBuilder<?>) evm.applySetting(setting, cb)).withCountQuery(true).getQuery();
PaginatedCriteriaBuilder<?> pcb = ((PaginatedCriteriaBuilder<?>) evm.applySetting(setting, cb)).withCountQuery(true);
processSort(pcb, values);
jpaQuery = (TypedQuery<Object>) pcb.getQuery();
} else {
EntityViewSetting<?, ?> setting = EntityViewSetting.create(entityViewClass, firstResult, maxResults + 1);
setting = processSetting(setting, values);
jpaQuery = (TypedQuery<Object>) ((PaginatedCriteriaBuilder<?>) evm.applySetting(setting, cb)).withHighestKeysetOffset(1).withCountQuery(false).getQuery();
PaginatedCriteriaBuilder<?> pcb = ((PaginatedCriteriaBuilder<?>) evm.applySetting(setting, cb)).withHighestKeysetOffset(1).withCountQuery(false);
processSort(pcb, values);
jpaQuery = (TypedQuery<Object>) pcb.getQuery();
}
}

Expand All @@ -358,6 +377,17 @@ Query createPaginatedQuery(Object[] values, boolean withCount) {
return setting;
}

protected void processSort(FullQueryBuilder<?, ?> cb, Object[] values) {
Sort sort;
int sortIndex;
int pageableIndex;
if ((sortIndex = parameters.getSortIndex()) >= 0 && (sort = (Sort) values[sortIndex]) != null) {
EntityViewSortUtil.applySort(evm, entityViewClass, cb, sort);
} else if ((pageableIndex = parameters.getPageableIndex()) >= 0 && (sort = ((Pageable) values[pageableIndex]).getSort()) != null) {
EntityViewSortUtil.applySort(evm, entityViewClass, cb, sort);
}
}

@SuppressWarnings({ "rawtypes", "unchecked" })
protected void processSpecification(CriteriaQuery<?> criteriaQuery, Object[] values) {
BlazeCriteriaQuery<?> blazeCriteriaQuery = (BlazeCriteriaQuery<?>) criteriaQuery;
Expand Down Expand Up @@ -409,7 +439,7 @@ public Query createQuery(Object[] values) {

if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) {
FixedJpaQueryCreator creator = createCreator(accessor, persistenceProvider);
criteriaQuery = invokeQueryCreator(creator, getDynamicSort(values));
criteriaQuery = invokeQueryCreator(creator, entityViewClass == null ? getDynamicSort(values) : null);
expressions = creator.getParameterExpressions();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.blazebit.persistence.criteria.BlazeCriteria;
import com.blazebit.persistence.parser.EntityMetamodel;
import com.blazebit.persistence.spi.ExtendedManagedType;
import com.blazebit.persistence.spring.data.base.EntityViewSortUtil;
import com.blazebit.persistence.spring.data.base.query.KeysetAwarePageImpl;
import com.blazebit.persistence.spring.data.repository.KeysetPageable;
import com.blazebit.persistence.view.EntityViewManager;
Expand Down Expand Up @@ -446,7 +447,10 @@ protected <S extends E> TypedQuery<V> getQuery(Specification<S> spec, Class<S> d
BlazeCriteriaQuery<S> cq = BlazeCriteria.get(cbf, domainClass);
Root<S> root = this.applySpecificationToCriteria(spec, domainClass, cq);

if (sort != null) {
Class<V> entityViewClass = metadata == null
|| metadata.getEntityViewClass() == null ? this.entityViewClass : (Class<V>) metadata.getEntityViewClass();

if (sort != null && entityViewClass == null) {
cq.orderBy(QueryUtils.toOrders(sort, root, BlazeCriteria.get(cbf)));
}
CriteriaBuilder<S> cb = cq.createCriteriaBuilder(entityManager);
Expand All @@ -461,7 +465,6 @@ protected <S extends E> TypedQuery<V> getQuery(Specification<S> spec, Class<S> d
boolean withExtractAllKeysets = false;

TypedQuery<V> query;
Class<V> entityViewClass = metadata == null || metadata.getEntityViewClass() == null ? this.entityViewClass : (Class<V>) metadata.getEntityViewClass();
if (entityViewClass == null) {
if (pageable == null) {
query = (TypedQuery<V>) cb.getQuery();
Expand All @@ -486,7 +489,11 @@ protected <S extends E> TypedQuery<V> getQuery(Specification<S> spec, Class<S> d
} else {
if (pageable == null) {
EntityViewSetting<V, CriteriaBuilder<V>> setting = EntityViewSetting.create(entityViewClass);
query = evm.applySetting(setting, cb).getQuery();
CriteriaBuilder<V> fqb = evm.applySetting(setting, cb);
if (sort != null) {
EntityViewSortUtil.applySort(evm, entityViewClass, fqb, sort);
}
query = fqb.getQuery();
} else {
EntityViewSetting<V, PaginatedCriteriaBuilder<V>> setting = EntityViewSetting.create(entityViewClass, getOffset(pageable), pageable.getPageSize());
if (pageable instanceof KeysetPageable) {
Expand All @@ -502,6 +509,9 @@ protected <S extends E> TypedQuery<V> getQuery(Specification<S> spec, Class<S> d
paginatedCriteriaBuilder.withExtractAllKeysets(withExtractAllKeysets);
}
paginatedCriteriaBuilder.withCountQuery(withCountQuery);
if (sort != null || (sort = pageable.getSort()) != null) {
EntityViewSortUtil.applySort(evm, entityViewClass, paginatedCriteriaBuilder, sort);
}
query = paginatedCriteriaBuilder.getQuery();
}
}
Expand Down
2 changes: 1 addition & 1 deletion integration/spring-data/testsuite/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,7 @@
<version>4.0.0-release</version>
<configuration>
<api>JPA</api>
<persistenceUnitName>TestsuiteBase</persistenceUnitName>
<persistenceUnitName>TEST-PU</persistenceUnitName>
<verbose>false</verbose>
<fork>true</fork>
<log4jConfiguration>${basedir}/log4j.properties</log4jConfiguration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
* @author Christian Beikov
Expand All @@ -34,6 +38,7 @@ public class Person implements Serializable {
private Long id;
private String name;
private long age;
private Set<Document> documents = new HashSet<>(0);

public Person() {
}
Expand Down Expand Up @@ -73,4 +78,13 @@ public long getAge() {
public void setAge(long age) {
this.age = age;
}

@OneToMany(mappedBy = "owner")
public Set<Document> getDocuments() {
return documents;
}

public void setDocuments(Set<Document> documents) {
this.documents = documents;
}
}
Loading

0 comments on commit 4f4acaa

Please sign in to comment.