有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Spring JPA REST按嵌套属性排序

我有实体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,但没有成功。我该怎么做才能让它工作


共 (5) 个答案

  1. # 1 楼答案

    从Spring数据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

    我发现的另一种选择是使用@ResResource(exported=false)。 这是无效的(特别是对于旧式Spring数据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.SortTranslatormappPropertyPath方法来检测注释:

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

    注释

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

    在您的项目中,包括排序为什么的可链接关联

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

    实际上,我并没有找到一个明确而成功的解决方案来解决这个问题,但我决定公开它,让大家思考一下,甚至让Spring团队考虑在nexts发行版中加入它

  2. # 2 楼答案

    这个page有一个可行的想法。其思想是在存储库顶部使用一个控制器,并分别应用投影

    下面是一段有效的代码(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;
            }
        }
    
    }
    
  3. # 3 楼答案

    您的MarketRepository可以有一个类似named query的:

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

    您可以通过@RequestParam从url获取name参数

  4. # 4 楼答案

    只需降级spring.data.‌​rest.webmvcHopper发布

    <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 Haythis 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,

    顺便说一句,我试过^{},报告说已经修复了,但没有与我一起工作

  5. # 5 楼答案

    我们曾经遇到过这样一种情况:我们想按链接实体中的字段进行排序(这是一对一的关系)。最初,我们使用基于https://stackoverflow.com/a/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。释放

    我们还拥有实体和投影的存储库:

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