[英]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 我到目前为止所做的研究为我提供了一个可能包括
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. 所以总的来说。
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种类型。
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.