Как использовать естественную сортировку с Spring Data Jpa

У меня есть столбец таблицы, который я хочу заказать. Проблема в том, что значение столбца содержит как числа, так и текст. Например, результат теперь упорядочен так.

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 для установки поля порядка и направления с дополнительным использованием спецификаций JPA. Сам метод по умолчанию возвращает страницу с первыми 20 результатами.

Я не верю, что Oracle поддерживает естественное упорядочивание из коробки, поэтому у меня есть хранимая процедура для этого. Запустив запрос с помощью этой процедуры, я получаю желаемый результат.

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

Как и следовало ожидать, я хотел бы использовать эту процедуру, обернув ее вокруг каждого упорядоченного столбца, содержащего текст. Поддерживая использование страниц / страниц и спецификаций. Исследование, которое я провел до сих пор, указывает мне на решение, которое может включать

  • Либо создание и реализация пользовательской спецификации
  • Или расширение SimpleJpaRepository, чтобы изменить способ преобразования объекта Sort.

Но, похоже, я не нашел метода, который позволил бы мне установить порядок с помощью собственного SQL. Я нашел единственный способ установить порядок - вызвать orderBy и включить объект Order.

Так в общем.

  1. Есть ли способ глобально включить естественный порядок при использовании Spring Data Jpa, спящего режима и базы данных Oracle?
  2. Если нет, как я могу заключить одну order by column в свою хранимую процедуру, сохранив при этом возможность использовать Page findAll(Pageable, Specifications) метод?

Заранее спасибо.


person Robin Hermans    schedule 12.09.2016    source источник


Ответы (2)


Покопавшись в исходном коде Spring JPA и Hibernate, мне удалось найти решение моей проблемы. Я почти уверен, что это не лучший способ решить эту проблему, но это единственный способ, который я смог найти.

В итоге я реализовал оболочку для части запроса «порядок по», расширив класс SingularAttributePath. У этого класса есть метод рендеринга, который генерирует строку, которая вставляется в фактический запрос. Моя реализация выглядит так

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

Затем я расширил функциональность преобразования заказов в классе SimpleJpaRepository. По умолчанию это делается путем вызова QueryUtils.toOrders(sort, root, builder). Но поскольку вызов метода было невозможно переопределить, я сам вызвал метод toOrder и изменил результат по своему вкусу.

Это означает замену всех заказов в результате моей собственной реализацией класса SingularAttributePath. В качестве дополнения я расширил класс Sort, который используется классом Pageable, чтобы контролировать, что будет упаковано, а что нет (так называемый 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). Это все, что касается серверной части.

Чтобы контролировать условие переноса, я также расширил класс Sort, используемый Pageable (упомянул об этом несколько строк назад). Единственная функциональность, которую я хотел добавить, - это наличие 4 типов в перечислении Direction.

  • ASC (по умолчанию по возрастанию)
  • DESC (по умолчанию по убыванию)
  • NASC (нормальный восходящий)
  • NDESC (нормальный по убыванию)

Последние два значения действуют только как заполнители. Они устанавливают логическое значение isNatural (переменная расширенного класса Order), которое используется в условии. Когда они преобразуются в запрос, они снова сопоставляются с вариантами по умолчанию.

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

Наконец, я заменил SortHandlerMethodArgumentResolver, используемый PageableHandlerMethodArgumentResolver. Единственное, что это делает, - это создание экземпляров моего класса NaturalSort и передача их в объект Pageable вместо класса Sort по умолчанию.

В конце концов, я могу вызвать ту же конечную точку 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

Я надеюсь, что это решение может помочь тем, у кого есть такая же или производная проблема, связанная с естественной сортировкой и пружинным JPA. Если у вас есть какие-либо вопросы или улучшения, дайте мне знать.

person Robin Hermans    schedule 19.09.2016

Я не знаю ни о какой такой функции. Однако вы можете сохранить числа в отдельном столбце, а затем упорядочить их по этому столбцу, что должно дать лучшую производительность сортировки в качестве дополнительного преимущества.

person Dragan Bozanovic    schedule 12.09.2016
comment
На самом деле это не вариант, поскольку имя задается пользователем. Так что я не могу повлиять на содержание этой информации. - person Robin Hermans; 12.09.2016
comment
Есть ли способ добавить собственный SQL к сгенерированному запросу из метода findAll? - person Robin Hermans; 12.09.2016
comment
@RobinHermans Значит, нет предопределенного формата вводимого текста? Это означает, что этот пользователь может ввести Это 2-я группа, и она должна быть перед Это 10-я группа в порядке сортировки? - person Dragan Bozanovic; 12.09.2016
comment
да. Вид упорядочивания, который вы получаете в проводнике Windows - person Robin Hermans; 12.09.2016
comment
@RobinHermans Извините, я не знаю ничего подобного, что вы могли бы использовать в этом случае. - person Dragan Bozanovic; 12.09.2016