如何对 Spring Data Jpa 使用自然排序

How to use natural sort with Spring Data Jpa

我有一个要订购的 table 专栏。问题是列值包含数字和文本。例如,结果现在是这样排序的。

1. group one
10. group ten
11. group eleven
2. group two

但我希望结果自然排序,就像这样

1. group one
2. group two
10. group ten
11. group eleven

在查看 Spring 配置时,我似乎找不到允许您执行此操作的选项。我使用 Spring Pageable class 来设置我的订单字段和方向,另外还使用了 JPA 规范。该方法本身 returns 默认情况下包含前 20 个结果的页面。

我认为 Oracle 不支持开箱即用的自然排序,因此我有一个存储过程。 运行 使用此过程的查询我得到了想要的结果。

select ... from group order by NATURALSORT(group.name) asc

如您所料,我想通过将每个包含文本的有序列包装起来来使用该过程。同时保持使用 pageables/pages 和规范。到目前为止,我所做的研究为我指明了一个可能包括

的解决方案

但我似乎没有找到允许我使用本机设置顺序的方法SQL。我发现设置顺序的唯一方法是调用 orderBy 并包含一个 Order 对象。

所以一般来说。

  1. 有没有办法在使用 Spring Data Jpa、hibernate 和 Oracle 数据库时全局启用自然排序
  2. 如果不是,我怎样才能用我的存储过程包装一个 order by column,同时仍然能够使用 Page findAll(Pageable, Specifications) 方法?

提前致谢。

我不知道有任何此类功能。但是,您可以将数字存储在单独的列中,然后按该列排序,这应该会提供更好的排序性能作为额外的好处。

在深入研究了 Spring JPA 和 Hibernate 的源代码之后,我设法找到了解决问题的方法。我很确定这不是解决问题的好方法,但这是我能找到的唯一方法。

我最终通过扩展 SingularAttributePath class 为查询的 'order by' 部分实现了一个包装器。这个 class 有一个 render 方法,可以生成插入到实际查询中的字符串。我的实现看起来像这样

@Override
public String render(RenderingContext renderingContext) {
  String render = super.render(renderingContext);
  render = "MYPACKAGE.NSORT(" + render + ")";
  return render;
}

接下来我在 SimpleJpaRepository class 中扩展了订单转换功能。默认情况下,这是通过调用 QueryUtils.toOrders(sort, root, builder) 来完成的。但是由于调用它的方法无法覆盖,我最终自己调用了 toOrder 方法并根据自己的喜好更改结果。

这意味着用我对 SingularAttributePath class 的自定义实现替换结果中的所有订单。作为额外,我扩展了 Sort class,Pageable class 使用它来控制什么被包裹,什么不被包裹(称为 NaturalOrder)。但我会在一秒钟内解决这个问题。我的实现看起来很接近这个(一些检查被遗漏了)

// Call the original method to convert the orders
List<Order> orders = QueryUtils.toOrders(sort, root, builder);
for (Order order : orders) {
  // Fetch the original order object from the sort for comparing
  SingularAttributePath orderExpression = (SingularAttributePath) order.getExpression();
  Sort.Order originalOrder = sort.getOrderFor(orderExpression.getAttribute().getName());
  // Check if the original order object is instantiated from my custom order class
  // Also check if the the order should be natural
  if (originalOrder instanceof NaturalSort.NaturalOrderm && ((NaturalSort.NaturalOrder) originalOrder).isNatural()){
    // replace the order with the custom class
    Order newOrder = new OrderImpl(new NaturalSingularAttributePathImpl(builder, expression.getJavaType(), expression.getPathSource(), expression.getAttribute()));
    resultList.add(newOrder);
  }else{
    resultList.add(order);
  }
}
return resultList;

然后通过调用 query.orderBy(resultlist) 将 return 列表添加到查询中。这就是后端。

为了控制换行条件,我还扩展了 Pageable 使用的 Sort class(前面几行提到过)。我想添加的唯一功能是在 Direction 枚举中有 4 种类型。

  • ASC(默认升序)
  • DESC(默认降序)
  • NASC(正常升序)
  • NDESC(正常降序)

最后两个值仅用作占位符。他们设置了在条件中使用的 isNatural 布尔值(扩展 Order class 的变量)。在将它们转换为查询时,它们会映射回它们的默认变体。

public Direction getNativeDirection() {
  if (this == NaturalDirection.NASC)
    return Direction.ASC;
  if (this == NaturalDirection.NDESC)
    return Direction.DESC;
  return Direction.fromString(String.valueOf(this));
}

最后我替换了 PageableHandlerMethodArgumentResolver 使用的 SortHandlerMethodArgumentResolver。唯一要做的就是创建我的 NaturalSort class 的实例并将它们传递给 Pageable 对象,而不是默认的 Sort class.

最终我能够调用同一个 REST 端点,但结果在排序方式上有所不同。

Default Sorting
/api/v1/items?page=0&size=20&sort=name,asc
Natural Sorting
/api/v1/items?page=0&size=20&sort=name,nasc

我希望这个解决方案可以帮助那些在自然排序和 spring JPA 方面有相同或派生问题的人。如果您有任何问题或改进,请告诉我。