【问题标题】:Spring JPA REST sort by nested propertySpring JPA REST按嵌套属性排序
【发布时间】:2017-06-08 00:55:26
【问题描述】:

我有实体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&amp;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&amp;sort=event.name,asc,但没有成功。我应该怎么做才能让它发挥作用?

【问题讨论】:

  • 你不能保证JSon的顺序,即使对象是在反序列化之前排序的。
  • 我不明白你的意思。它是市场的列表,因此它必须保证订单。
  • @uiii,你找到解决这个问题的方法了吗?
  • 这里的任何更新我都面临同样的问题。
  • 我们遇到了同样的问题(使用 Spring Boot 1.5.9 和 Spring Data REST 2.6.9)。我们尝试用于排序的嵌套属性被包含@JsonProperty(access = READ_ONLY) 的 Jackson Mixin 覆盖。删除此注释会导致此嵌套属性的正确排序行为。

标签: java spring rest spring-data-jpa spring-data-rest


【解决方案1】:

只需降级 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 Hay评论this question

在 Hopper 版本中按嵌套属性排序对我来说效果很好,但我确实在 Ingalls 版本的 RC 版本中遇到了以下错误。在 Ingalls 版本的 RC 版本中出现错误。这被报告为已修复,

顺便说一句,我尝试了v3.0.0.M3,报告已修复但无法与我合作。

【讨论】:

    【解决方案2】:

    你的MarketRepository 可以有一个named query 像:

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

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

    【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      来自 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
      

      我尝试在 annotations 的帮助下通过可链接关联激活排序,但没有成功,因为我们总是需要覆盖 JacksonMappingAwareSortTranslator.SortTranslatormappPropertyPath 方法来检测注释:

                  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 团队考虑将其包含在下一个版本中。

      【讨论】:

        【解决方案5】:

        当我们想要按链接实体中的字段进行排序时(这是一对一的关系),我们遇到过这样的情况。最初,我们使用基于https://stackoverflow.com/a/54517551 的示例按链接字段进行搜索。

        因此,在我们的案例中,解决方法/hack 是提供自定义排序和可分页参数。 下面是例子:

        @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> {
        }
        

        【讨论】:

          【解决方案6】:

          基于 Spring Data JPA 文档4.4.3. Property Expressions

          ...您可以在方法名称中使用 _ 来手动定义遍历点...

          您可以在 REST 查询中添加下划线,如下所示:

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

          【讨论】:

            猜你喜欢
            • 2017-07-04
            • 2019-02-28
            • 1970-01-01
            • 1970-01-01
            • 2022-08-10
            • 1970-01-01
            • 2010-11-13
            • 2016-01-06
            • 1970-01-01
            相关资源
            最近更新 更多