Spring JPA REST 按嵌套属性排序

21

我有实体 MarketEventMarket 实体有一列:

@ManyToOne(fetch = FetchType.EAGER)
private Event event;

接下来我有一个代码仓库:

public interface MarketRepository extends PagingAndSortingRepository<Market, Long> {
}

和一个投影:

@Projection(name="expanded", types={Market.class})
public interface ExpandedMarket {
    public String getName();
    public Event getEvent();
}

使用REST查询/api/markets?projection=expanded&sort=name,asc成功获取按市场名称排序的带有嵌套事件属性的市场列表:

{
    "_embedded" : {
        "markets" : [ {
            "name" : "Match Odds",
            "event" : {
                "id" : 1,
                "name" : "Watford vs Crystal Palace"
            },
            ...
        }, {
            "name" : "Match Odds",
            "event" : {
                "id" : 2,
                "name" : "Arsenal vs West Brom",
            },
            ...
        },
        ...
    }
}

但是我需要按事件名称排序的市场列表,我尝试了查询/api/markets?projection=expanded&sort=event.name,asc,但它没有起作用。我该怎么做才能使其起作用?


即使在反序列化之前对对象进行了排序,也无法保证JSon的顺序。 - Essex Boy
2
我不明白你的意思。这是一个市场列表,所以必须保证顺序。 - uiii
@uiii,您解决了这个问题吗? - Kadzhaev Marat
1
这里有任何更新吗?我仍然遇到同样的问题。 - Khaled Lela
我们遇到了相同的问题(使用Spring Boot 1.5.9和Spring Data REST 2.6.9)。我们尝试用于排序的嵌套属性被包含在一个Jackson Mixin中,其中包含@JsonProperty(access = READ_ONLY)。删除此注释可导致该嵌套属性的正确排序行为。 - jensfischerhh
你最终解决了这个问题吗?@uiii - sb9
6个回答

7
基于Spring Data JPA文档4.4.3. 属性表达式
您可以在方法名称中使用下划线手动定义遍历点。
您可以按以下方式在REST查询中放置下划线:
/api/markets?projection=expanded&sort=event_name,asc

1
为什么这不是最受欢迎的答案我无法理解。这是最简单和最容易的方法。 - Alain Cruz
对我来说运行得非常好 - times29

3

只需将spring.data.rest.webmvc降级至Hopper版本。

<spring.data.jpa.version>1.10.10.RELEASE</spring.data.jpa.ve‌​rsion> 
<spring.data.‌​rest.webmvc.version>‌​2.5.10.RELEASE</spri‌​ng.data.rest.webmvc.‌​version>

projection=expanded&sort=event.name,asc // works
projection=expanded&sort=event_name,asc // this works too

感谢@Alan Hay这个问题的评论。

按嵌套属性排序在Hopper版本中对我来说运行良好,但我在Ingalls RC版本中遇到了以下bug。此问题已被报告为已解决。

顺便说一下,我尝试了v3.0.0.M3,它报告说已经修复了该问题,但对我没有用。


1
我们曾经遇到一个情况,需要按照关联实体中的字段进行排序(这是一对一的关系)。最初,我们使用基于https://dev59.com/E1oV5IYBdhLWcg3wc-no#54517551的示例来搜索关联字段。
因此,在我们的情况下,解决方法/技巧是提供自定义排序和可分页参数。以下是示例:
@org.springframework.data.rest.webmvc.RepositoryRestController
public class FilteringController {

private final EntityRepository repository;

@RequestMapping(value = "/entities",
        method = RequestMethod.GET)

public ResponseEntity<?> filter(
        Entity entity,
        org.springframework.data.domain.Pageable page,
        org.springframework.data.web.PagedResourcesAssembler assembler,
        org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler entityAssembler,
        org.springframework.web.context.request.ServletWebRequest webRequest
) {

    Method enclosingMethod = new Object() {}.getClass().getEnclosingMethod();
    Sort sort = new org.springframework.data.web.SortHandlerMethodArgumentResolver().resolveArgument(
            new org.springframework.core.MethodParameter(enclosingMethod, 0), null, webRequest, null
    );

    ExampleMatcher matcher = ExampleMatcher.matching()
            .withIgnoreCase()
            .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
    Example example = Example.of(entity, matcher);

    Page<?> result = this.repository.findAll(example, PageRequest.of(
            page.getPageNumber(),
            page.getPageSize(),
            sort
    ));
    PagedModel search = assembler.toModel(result, entityAssembler);
    search.add(linkTo(FilteringController.class)
            .slash("entities/search")
            .withRel("search"));
    return ResponseEntity.ok(search);
}
}

使用的Spring Boot版本为2.3.8.RELEASE。

我们还有一个用于实体和投影的存储库:

@RepositoryRestResource
public interface JpaEntityRepository extends JpaRepository<Entity, Long> {
}

0

你的 MarketRepository 可以有一个类似于 named query 的查询:

public interface MarketRepository exten PagingAndSortingRepository<Market, Long> {
    Page<Market> findAllByEventByName(String name, Page pageable);
}

您可以使用@RequestParam从URL中获取您的name参数


是的,但这不是我想要的。我想获取所有市场并按事件名称对它们进行排序。 - uiii
好的,那么您可以尝试使用 Order By:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation - Alexandru Hodis
这与问题无关。问题是“Spring未解析嵌套属性排序”。 - withoutOne

0

这个页面有一个可行的想法。这个想法是在仓库之上使用控制器,并单独应用投影。

这里是一段可行的代码(SpringBoot 2.2.4)

import ro.vdinulescu.AssignmentsOverviewProjection;
import ro.vdinulescu.repository.AssignmentRepository;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.PagedModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RepositoryRestController
public class AssignmentController {
    @Autowired
    private AssignmentRepository assignmentRepository;

    @Autowired
    private ProjectionFactory projectionFactory;

    @Autowired
    private PagedResourcesAssembler<AssignmentsOverviewProjection> resourceAssembler;

    @GetMapping("/assignments")   
    public PagedModel<EntityModel<AssignmentsOverviewProjection>> listAssignments(@RequestParam(required = false) String search,
                                                                                  @RequestParam(required = false) String sort,
                                                                                  Pageable pageable) {
        // Spring creates the Pageable object correctly for simple properties,
        // but for nested properties we need to fix it manually   
        pageable = fixPageableSort(pageable, sort, Set.of("client.firstName", "client.age"));

        Page<Assignment> assignments = assignmentRepository.filter(search, pageable);
        Page<AssignmentsOverviewProjection> projectedAssignments = assignments.map(assignment -> projectionFactory.createProjection(
                AssignmentsOverviewProjection.class,
                assignment));

        return resourceAssembler.toModel(projectedAssignments);
    }

    private Pageable fixPageableSort(Pageable pageable, String sortStr, Set<String> allowedProperties) {
        if (!pageable.getSort().equals(Sort.unsorted())) {
            return pageable;
        }

        Sort sort = parseSortString(sortStr, allowedProperties);
        if (sort == null) {
            return pageable;
        }

        return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
    }

    private Sort parseSortString(String sortStr, Set<String> allowedProperties) {
        if (StringUtils.isBlank(sortStr)) {
            return null;
        }

        String[] split = sortStr.split(",");
        if (split.length == 1) {
            if (!allowedProperties.contains(split[0])) {
                return null;
            }
            return Sort.by(split[0]);
        } else if (split.length == 2) {
            if (!allowedProperties.contains(split[0])) {
                return null;
            }
            return Sort.by(Sort.Direction.fromString(split[1]), split[0]);
        } else {
            return null;
        }
    }

}

0
从Spring Data REST文档中:

链接关联(即指向顶级资源的链接)排序不受支持。

https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting

我发现的另一种选择是使用 @ResResource(exported=false)。 这并不适用于旧版 Spring Data REST 项目,因为它会避免资源/实体加载 HTTP 链接:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
 com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value

我尝试使用注释启用可链接关联的排序,但没有成功,因为我们总是需要覆盖JacksonMappingAwareSortTranslator.SortTranslator类中的mappPropertyPath方法来检测注释。
            if (associations.isLinkableAssociation(persistentProperty)) {
                if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
                    return Collections.emptyList();
                }
            }

注释

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SortByLinkableAssociation {
}

在你的项目中,包含@SortByLinkableAssociation来对可链接关联进行排序。
@ManyToOne(fetch = FetchType.EAGER)
@SortByLinkableAssociation
private Event event;

实际上,我没有找到一个清晰且成功的解决方案来解决这个问题,但我决定公开它,让大家思考一下,甚至让Spring团队考虑在下一个版本中加以考虑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接