简体   繁体   English

"Spring JPA REST按嵌套属性排序"

[英]Spring JPA REST sort by nested property

I have entity Market and Event .我有实体MarketEvent Market entity has a column: Market实体有一栏:

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

Next I have a repository:接下来我有一个存储库:

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

and a projection:和一个投影:

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

using REST query /api/markets?projection=expanded&sort=name,asc I get successfully the list of markets with nested event properties ordered by market's name:使用 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",
            },
            ...
        },
        ...
    }
}

But what I need is to get list of markets ordered by event's name , I tried the query /api/markets?projection=expanded&sort=event.name,asc but it didn't work.但我需要的是获取按事件名称排序的市场列表,我尝试了查询/api/markets?projection=expanded&sort=event.name,asc但它没有用。 What should I do to make it work?我应该怎么做才能让它工作?

Just downgrade spring.data.‌​rest.webmvc to Hopper release只需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

Thanks @Alan Hay comment on this question感谢@Alan Hay这个问题的评论

Ordering by nested properties works fine for me in the Hopper release but I did experience the following bug in an RC version of the Ingalls release.bug in an RC version of the Ingalls release.在 Hopper 版本中按嵌套属性排序对我来说效果很好,但我确实在 Ingalls 版本的 RC 版本中遇到了以下错误。 This is reported as being fixed,这被报告为已修复,

BTW , I tried v3.0.0.M3 that reported that fixed but not working with me.顺便说一句,我尝试了v3.0.0.M3 ,它报告说已修复但对我不起作用。

Are there any news on this issue? 关于这个问题有什么消息吗? Also with Spring Boot 2.1 and the Spring Data Lovelace release sorting by nested properties doesn't work. 同样在Spring Boot 2.1和Spring Data Lovelace版本中,无法按嵌套属性进行排序。

Your MarketRepository could have a named query like :您的MarketRepository可以有一个named query如:

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

You can get your name param from the url with @RequestParam您可以使用@RequestParam从 url 中获取您的name参数

This page has an idea that works.这个页面有一个可行的想法。 The idea is to use a controller on top of the repository, and apply the projection separately.这个想法是在存储库顶部使用控制器,并单独应用投影。

Here's a piece of code that works (SpringBoot 2.2.4)这是一段有效的代码(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;
        }
    }

}

From Spring Data REST documentation:来自 Spring Data REST 文档:

Sorting by linkable associations (that is, links to top-level resources) is not supported.不支持按可链接关联(即顶级资源的链接)排序。

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

An alternative that I found was use @ResResource(exported=false) .我发现的另一种方法是使用@ResResource(exported=false) This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:这是无效的(特别是对于遗留的 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

I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:我试图激活排序联协会与注释的帮助,但没有成功,因为我们需要总是需要重写mappPropertyPath的方法JacksonMappingAwareSortTranslator.SortTranslator检测注释:

            if (associations.isLinkableAssociation(persistentProperty)) {
                if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
                    return Collections.emptyList();
                }
            }

Annotation注解

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

At your project incluide @SortByLinkableAssociation at linkable associations that whats sort.在您的项目中,在可链接的关联中包含@SortByLinkableAssociation排序。

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

Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.真的,我没有找到解决这个问题的明确和成功的解决方案,但决定公开它让思考它甚至 Spring 团队考虑将其包含在下一个版本中。

We had a case when we wanted to sort by fields which were in linked entity (it was one-to-one relationship).我们有一个案例,当我们想按链接实体中的字段排序(这是一对一的关系)。 Initially, we used example based on https://stackoverflow.com/a/54517551 to search by linked fields.最初,我们使用基于https://stackoverflow.com/a/54517551 的示例按链接字段进行搜索。

So the workaround/hack in our case was to supply custom sort and pageable parameters.因此,我们案例中的解决方法/黑客是提供自定义排序和可分页参数。 Below is the example:下面是示例:

@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);
}
}

Used version of Spring boot: 2.3.8.RELEASE使用的Spring boot版本:2.3.8.RELEASE

We had also the repository for Entity and used projection:我们还有实体的存储库并使用了投影:

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

I have entity Market and Event .我有实体MarketEvent Market entity has a column: Market实体具有一列:

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

Next I have a repository:接下来,我有一个存储库:

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

and a projection:和一个投影:

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

using REST query /api/markets?projection=expanded&sort=name,asc I get successfully the list of markets with nested event properties ordered by market's name:使用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",
            },
            ...
        },
        ...
    }
}

But what I need is to get list of markets ordered by event's name , I tried the query /api/markets?projection=expanded&sort=event.name,asc but it didn't work.但是我需要获取按事件名称排序的市场列表,我尝试了查询/api/markets?projection=expanded&sort=event.name,asc但没有成功。 What should I do to make it work?我应该怎么做才能使其正常工作?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM