简体   繁体   English

如何在Spring Data Jpa中使用自然排序

[英]How to use natural sort with Spring Data Jpa

I have a table column that I want to order on. 我有一个要订购的表格列。 The problem is that the column value contains both numbers and text. 问题在于列值同时包含数字和文本。 For example, the result is now ordered like this. 例如,现在按以下顺序对结果进行排序。

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

But I'd like the result to be ordered naturally, like this 但是我希望结果像这样自然排序

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

When looking at the Spring configuration I can't seem to find an option that allows you to do this. 在查看Spring配置时,我似乎找不到允许您执行此操作的选项。 I use the Spring Pageable class to set my order field and direction, with an additional use of JPA specifications. 我使用Spring Pageable类来设置订单字段和方向,并额外使用JPA规范。 The method itself returns a Page with the first 20 results by default. 默认情况下,该方法本身返回一个前20个结果的Page。

I don't believe that Oracle supports Natural ordering out of the box so I have a stored procedure for that. 我不认为Oracle支持开箱即用的自然排序,因此我有一个存储过程。 Running the query using this procedure I get the desired result. 使用此过程运行查询,我得到期望的结果。

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

As you might expect I'd like to use that procedure by wrapping it around every ordered column that contains text. 如您所料,我想通过将其包装在每个包含文本的有序列周围来使用该过程。 While maintaining to use pageables/pages and specifications. 同时保持使用分页/页面和规范。 The research I done this far points me to a solution that might include 我到目前为止所做的研究为我提供了一个可能包括

  • Either creating and implementing a custom Specification 创建和实施自定义规范
  • Or extending the SimpleJpaRepository to change the way the Sort object is transformed. 或扩展SimpleJpaRepository来更改Sort对象的转换方式。

But I didn't seem to find a method that allows me to set the order using native SQL. 但是我似乎没有找到一种方法可以使用本机SQL设置订单。 The only way I found to set the order was by calling orderBy and including an Order object. 我发现设置订单的唯一方法是调用orderBy并包含一个Order对象。

So in general. 所以总的来说。

  1. Is there a way to globally enable natural ordering when using Spring Data Jpa, hibernate and the Oracle database 使用Spring Data Jpa,hibernate和Oracle数据库时,是否有一种全局启用自然排序的方法
  2. If not, how can I wrap a single order by column with my stored procedure while still being able to use the Page findAll(Pageable, Specifications) method? 如果没有,如何在存储过程中order by column包装单个order by column同时仍然可以使用Page findAll(Pageable, Specifications)方法?

Thanks in advance. 提前致谢。

After some digging into the source code of both Spring JPA and Hibernate I managed to find a solution to my problem. 在深入研究了Spring JPA和Hibernate的源代码之后,我设法找到了解决我的问题的方法。 I'm pretty sure this isn't a nice way to solve it, but it's the only one I could find. 我很确定这不是解决问题的好方法,但这是我唯一能找到的方法。

I ended up implementing a wrapper for the 'order by' part of the query by extending the SingularAttributePath class. 我最终通过扩展SingularAttributePath类为查询的“ order by”部分实现了包装器。 This class has a render method that generates the string which gets inserted into the actual query. 此类具有render方法,该方法生成将插入到实际查询中的字符串。 My implementation looks like this 我的实现看起来像这样

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

Next I extended the Order conversion functionality in the SimpleJpaRepository class. 接下来,我在SimpleJpaRepository类中扩展了Order转换功能。 By default this is done by calling QueryUtils.toOrders(sort, root, builder) . 默认情况下,这是通过调用QueryUtils.toOrders(sort, root, builder) But since the method calling it was impossible to override I ended up calling the toOrder method myself and altering the result to my liking. 但是由于调用该方法是不可能的,所以我最终自己调用了toOrder方法,并根据自己的喜好更改了结果。

This means replacing all orders in the result by my custom implementation of the SingularAttributePath class. 这意味着用我的SingularAttributePath类的自定义实现替换结果中的所有订单。 As an extra I extended the Sort class which is used by the Pageable class to have control over what gets wrapped and what doesn't (called NaturalOrder). 另外,我扩展了Pageable类使用的Sort类,以控制要包装的内容和不包装的内容(称为NaturalOrder)。 But I'll get to that in a second. 但是,我稍后再讲。 My implementation looks close to this (some checks are left out) 我的实现看起来与此接近(省略了一些检查)

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

The return list then gets added to the query by calling query.orderBy(resultlist) . 然后,通过调用query.orderBy(resultlist)将返回列表添加到查询中。 That's it for the back-end. 后端就是这样。

In order to control the wrap condition I also extended the Sort class used by the Pageable (mentioned this a few lines back). 为了控制包装条件,我还扩展了Pageable使用的Sort类(在后面几行提到)。 The only functionality I wanted to add was to have 4 types in the Direction enum. 我要添加的唯一功能是在“方向”枚举中有4种类型。

  • ASC (default ascending) ASC(默认升序)
  • DESC (default descending) DESC(默认降序)
  • NASC (normal ascending) NASC(正常升序)
  • NDESC (normal descending) NDESC(正常降序)

The last two values only act as placeholders. 最后两个值仅充当占位符。 They set the isNatural boolean (variable of the extended Order class) which gets used in the condition. 他们设置在条件中使用的isNatural布尔值(扩展Order类的变量)。 At the time they are converted to query they are mapped back to their default variants. 在将它们转换为查询时,它们被映射回其默认变体。

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

Lastly I replaced the SortHandlerMethodArgumentResolver used by the PageableHandlerMethodArgumentResolver . 最后我更换了SortHandlerMethodArgumentResolver由所使用的PageableHandlerMethodArgumentResolver The only thing this does is creating instances of my NaturalSort class and passing them into the Pageable object , instead of the default Sort class. 唯一要做的就是创建我的NaturalSort类的实例,并将它们传递给Pageable对象,而不是默认的Sort类。

In the end I'am able to call the same REST endpoint, while the result differs in the way it's sorted. 最后,我可以调用相同的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

I hope this solution can help those who have the same or a derived problem regarding natural sort and spring JPA. 我希望该解决方案可以帮助那些对自然排序和春季JPA有相同或衍生问题的人。 If you have any question or improvements ,please let me know. 如果您有任何疑问或改进,请告诉我。

I am not aware of any such feature. 我不知道有任何这样的功能。 You could however store the numbers in a separate column, and then order by that column, which should give a better sorting performance as an additional benefit. 但是,您可以将数字存储在单独的列中,然后按该列排序,这将带来更好的排序性能,这是附加的好处。

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

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