Spring JPA REST sort by nested property

淺唱寂寞╮ 提交于 2021-02-17 21:45:01

问题


I have entity Market and Event. Market entity has a column:

@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:

{
    "_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. What should I do to make it work?


回答1:


Just downgrade spring.data.‌​rest.webmvc to Hopper release

<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

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. This is reported as being fixed,

  • jira issue - Sorting by an embedded property no longer works in Ingalls RC1

BTW, I tried v3.0.0.M3 that reported that fixed but not working with me.




回答2:


Your MarketRepository could have a named query like :

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




回答3:


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)

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

}



回答4:


From Spring Data REST documentation:

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

An alternative that I found was use @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:

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:

            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.

@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.




回答5:


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 from https://github.com/jefferson-lima/sdr-filter-example/blob/master/src/main/java/lima/jefferson/sdrfilterexample/CompanyCustomController.java to search by linked fields.

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

We had also the repository for Entity and used projection:

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



回答6:


Try with an _ instead of .

/api/markets?projection=expanded&sort=event_name,asc


来源:https://stackoverflow.com/questions/41807631/spring-jpa-rest-sort-by-nested-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!