Статьи

Использование jOOQ с Spring: сортировка и разбиение на страницы

JOOQ — это библиотека, которая помогает нам контролировать наш SQL. Он может генерировать код из нашей базы данных и позволяет нам создавать безопасные запросы к базе данных, используя его свободный API.

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

На этот раз мы узнаем, как мы можем реализовать простую функцию поиска, которая поддерживает сортировку и разбиение на страницы.

Давайте начнем.

Дополнительное чтение:

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

Добавление поддержки пагинации и сортировки в веб-слой

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

Конечно, мы могли бы реализовать компонент, который поддерживает это, но это не так просто, как кажется. Довольно легко создать HandlerMethodArgumentResolver, который находит эту информацию из HTTP-запроса и преобразует ее в объект, который затем передается нашему методу контроллера в качестве аргумента метода. Проблема в том, что существует множество «исключительных» ситуаций, которые делают эту задачу довольно сложной. Например,

  • Если эта информация не найдена в HTTP-запросе, мы должны вернуться к значениям по умолчанию.
  • Если необходимая информация отсутствует (например, номер страницы указан без указания размера страницы), мы должны либо вернуться к значениям по умолчанию, либо вернуть ошибку пользователю нашего REST API.

К счастью, нам не нужно реализовывать этот компонент. В проекте Spring Data Commons есть компонент, который извлекает информацию из страниц HTTP-запросов и сортирует их и позволяет внедрять эту информацию в методы контроллера.

Давайте выясним, что мы можем получить двоичные файлы Spring Data Commons с Maven.

Получение необходимых зависимостей с Maven

Мы можем получить необходимые двоичные файлы с помощью Maven, добавив следующее объявление зависимостей в раздел зависимостей нашего файла POM:

1
2
3
4
5
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
    <version>1.7.1.RELEASE</version>
</dependency>

Наш следующий шаг — внести некоторые изменения в конфигурацию контекста приложения нашего примера приложения. Давайте двигаться дальше и выясним, какие изменения мы должны сделать.

Настройка контекста приложения

Мы можем включить поддержку веб-нумерации в Spring Data, внеся одно простое изменение в класс конфигурации контекста приложения, который настраивает веб-уровень нашего примера приложения. Мы должны аннотировать класс конфигурации аннотацией @EnableSpringDataWebSupport . Это гарантирует, что требуемые бины регистрируются автоматически.

Документация API аннотации @EnableSpringDataWebSupport предоставляет дополнительную информацию о bean-компонентах, которые регистрируются при использовании этой аннотации.

Соответствующая часть класса WebAppContext выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
@Configuration
@ComponentScan({
        "net.petrikainulainen.spring.jooq.common.controller",
        "net.petrikainulainen.spring.jooq.todo.controller"
})
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppContext extends WebMvcConfigurerAdapter {
    //Other methods are omitted for the sake of clarity
}

Вот и все. Теперь мы внесли необходимые изменения в конфигурацию контекста приложения нашего примера приложения. Давайте выясним, как мы можем использовать поддержку веб-пагинации в нашем приложении.

Использование веб-пагинации

Когда мы хотим отсортировать и разбить на страницы результаты нашего запроса, мы должны выполнить следующие шаги:

  1. Добавьте конфигурацию подкачки и сортировки в запрос HTTP.
  2. Добавьте параметр метода Pageable в метод контроллера.

Во-первых , мы можем добавить конфигурацию подкачки и сортировки к HTTP-запросу, используя следующие параметры запроса:

  • Параметр запроса страницы указывает номер запрашиваемой страницы.
  • Параметр запроса размера указывает размер запрашиваемой страницы.
  • Параметр запроса сортировки определяет свойства, которые используются для сортировки результатов запроса. Это значение этого параметра запроса должно соответствовать синтаксису: свойство, свойство (, ASC | DESC) . Если направление сортировки не указано, результаты сортируются в порядке возрастания. Если вы хотите изменить порядок сортировки, вы должны использовать несколько параметров сортировки (например ,? Sort = title & sort = id, desc ).

Во-вторых , мы должны добавить параметр метода Pageable в наш метод контроллера. Соответствующая часть класса TodoController выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import javax.validation.Valid;
import java.util.List;
 
 
@RestController
@RequestMapping("/api/todo")
public class TodoController {
 
    private final TodoCrudService crudService;
 
    private final TodoSearchService searchService;
 
    @Autowired
    public TodoController(TodoCrudService crudService, TodoSearchService searchService) {
        this.crudService = crudService;
        this.searchService = searchService;
    }
 
    @RequestMapping(value = "/search", method = RequestMethod.GET)
    public List<TodoDTO> findBySearchTerm(@RequestParam("searchTerm") String searchTerm, Pageable pageable) {
        return searchService.findBySearchTerm(searchTerm, pageable);
    }
}

Теперь мы можем добавить функцию поиска в наш репозиторий jOOQ. Давайте выясним, как это делается.

Реализация уровня репозитория

Первое, что нам нужно сделать, это добавить новый публичный метод в интерфейс TodoService . Метод findBySearchTerm (String searchTerm, Pageable pageable) находит записи todo, заголовок или описание которых содержит заданный поисковый термин, и возвращает результаты запроса, следуя конфигурации подкачки и сортировки, заданной в качестве параметра метода.

Соответствующая часть интерфейса TodoRepository выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
import org.springframework.data.domain.Pageable;
 
import java.util.List;
 
public interface TodoRepository {
 
    public List<Todo> findBySearchTerm(String searchTerm, Pageable pageable);
 
    //Other methods are omitted for the sake of clarity
}

Реализация этого метода имеет две обязанности:

  1. Он должен найти записи todo, название или описание которых содержит заданный поисковый термин.
  2. Он должен обработать параметры сортировки и подкачки, найденные в объекте Pageable, и преобразовать их в форму, понятную jOOQ.

Давайте продолжим и выясним, как мы можем найти записи todo, название или описание которых содержит заданный поисковый термин.

Реализация поискового запроса

Мы можем реализовать поисковый запрос, выполнив следующие действия:

  1. Добавьте метод findBySearchTerm (String searchTerm, Pageable pageable) в класс JOOQTodoRepository .
  2. Аннотируйте метод с аннотацией @Transactional и установите значение его атрибута readOnly в true.
  3. Реализуйте метод findBySearchTerm () , выполнив следующие действия:
    1. Создайте подобное выражение, которое используется в нашем запросе к базе данных.
    2. Создайте новый оператор SELECT , вызвав метод selectFrom (таблица таблиц) интерфейса DSLContext, и укажите, что вы хотите выбрать информацию из таблицы задач .
    3. Укажите предложение WHERE оператора SELECT , вызвав метод where (условия сбора данных) интерфейса SelectWhereStep . Создайте параметр метода этого метода, выполнив следующие действия:
      1. Создайте аналогичные условия для столбцов описания и заголовка таблицы задач, вызвав метод likeIgnoreCase (String value) интерфейса Field . Передайте подобное выражение в качестве параметра метода.
      2. Объедините созданные одинаковые условия с помощью метода или (Условие другое) интерфейса Условие .
    4. Получите список объектов TodosRecord , вызвав метод fetchInto (тип класса) интерфейса ResultQuery . Передайте объект TodosRecord.class в качестве параметра метода.
    5. Преобразуйте список объектов TodosRecord в список объектов Todo , вызвав закрытый метод convertQueryResultsToModelObjects () . Этот метод выполняет итерацию списка объектов TodosRecord и преобразует каждый объект TodosRecord в объект Todo , вызывая метод convertQueryResultToModelObject () . Каждый объект Todo добавляется в список, который возвращается после обработки всех объектов TodosRecord .
    6. Вернуть список объектов Todo .

Исходный код нашей реализации выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.ArrayList;
import java.util.List;
 
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
 
@Repository
public class JOOQTodoRepository implements TodoRepository {
 
    private final DateTimeService dateTimeService;
 
    private final DSLContext jooq;
 
    //The constructor is omitted for the sake of clarity
 
    @Transactional(readOnly = true)
    @Override
    public List<Todo> findBySearchTerm(String searchTerm, Pageable pageable) {
        String likeExpression = "%" + searchTerm + "%";
 
        List<TodosRecord> queryResults = jooq.selectFrom(TODOS)
                .where(
                        TODOS.DESCRIPTION.likeIgnoreCase(likeExpression)
                                .or(TODOS.TITLE.likeIgnoreCase(likeExpression))
                )
                .fetchInto(TodosRecord.class);
 
        return convertQueryResultsToModelObjects(queryResults);
    }
 
    private List<Todo> convertQueryResultsToModelObjects(List<TodosRecord> queryResults) {
        List<Todo> todoEntries = new ArrayList<>();
 
        for (TodosRecord queryResult : queryResults) {
            Todo todoEntry = convertQueryResultToModelObject(queryResult);
            todoEntries.add(todoEntry);
        }
 
        return todoEntries;
    }
 
    private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
        return Todo.getBuilder(queryResult.getTitle())
                .creationTime(queryResult.getCreationTime())
                .description(queryResult.getDescription())
                .id(queryResult.getId())
                .modificationTime(queryResult.getModificationTime())
                .build();
    }
    
    //Other methods are omitted for the sake of clarity
}

Запрос к базе данных этого примера очень прост. Если вам нужно создать более сложные запросы к базе данных, вам следует прочитать раздел 4.6. Условные выражения справочника jOOQ . Он описывает, как вы можете использовать условные выражения в запросах вашей базы данных.

Теперь мы создали метод репозитория, который ищет записи todo из базы данных. Наш следующий шаг — сортировка результатов этого запроса к базе данных.

Сортировка результатов запроса

Прежде чем мы сможем отсортировать результаты нашего поискового запроса, мы должны понять, как мы можем получить параметры сортировки нашего запроса к базе данных из объекта Pageable .

  • Мы можем получить ссылку на объект Sort , вызвав метод getSort () интерфейса Pageable . Этот объект содержит параметры сортировки, найденные в HTTP-запросе.
  • Объект Sort может содержать ноль или более параметров сортировки. Метод iterator () класса Sort возвращает объект Iterator <Sort.Order>, который мы можем использовать, когда хотим обработать каждый параметр сортировки нашего запроса к базе данных.
  • Класс Sort.Order содержит имя свойства и направление сортировки .

Другими словами, мы должны выполнить следующие требования:

  • Мы должны поддерживать ситуацию, когда параметры сортировки не указаны.
  • Мы должны поддерживать ситуацию, когда результаты нашего запроса сортируются по нескольким столбцам.
  • Надо полагать, что у каждого столбца свой порядок сортировки.

Мы можем выполнить эти требования, внеся следующие изменения в класс JOOQTodoRepository :

  1. Добавьте частный метод getTableField (String sortFieldName) в класс репозитория и реализуйте этот метод, выполнив следующие действия:
    1. Используйте отражение, чтобы получить объект Field, который предоставляет информацию о запрошенном поле объекта Todos .
    2. Если поле не найдено или мы не можем получить к нему доступ, создайте новое исключение InvalidDataAccessApiUsageException .
    3. Если поле найдено, приведите возвращенный объект Field в объект TableField и верните его.
  2. Добавьте закрытый метод convertTableFieldToSortField (TableField tableField, Sort.Direction sortDirection) в класс репозитория и реализуйте метод, выполнив следующие действия:
    1. Если порядок сортировки этого поля возрастает, вызовите метод asc () интерфейса Field и верните возвращенный объект.
    2. В противном случае вызовите метод desc () интерфейса Field и верните возвращенный объект.
  3. Добавьте частный метод getSortFields (Sort sortSpecification) в класс репозитория и реализуйте его, выполнив следующие действия:
    1. Создайте новую коллекцию, которая содержит объекты SortField <?> .
    2. Если параметры сортировки не найдены, вернуть пустой объект Collection .
    3. Выполните итерацию объектов Sort.Order, найденных в объекте Sort, указанном в качестве параметра метода, и обработайте каждый объект Sort.Order , выполнив следующие действия:
      1. Преобразуйте каждый объект Sort.Order в объект SortField <?> С помощью методов getTableField () и convertTableFieldToSortField () .
      2. Добавьте каждый объект SortField <?> В коллекцию, созданную на первом этапе.
    4. Вернуть коллекцию объектов SortField <?> .
  4. Сортируйте результаты запроса, выполнив следующие действия:
    1. Получите объект Sort , вызвав метод getSort () интерфейса Pageable .
    2. Получите объект Collection <SortField <? >> , вызвав метод getSortFields () . Передайте объект Sort в качестве параметра метода.
    3. Создайте предложение ORDER BY , вызвав метод orderBy (Collection <? Extends SortField <? >> fields) интерфейса SelectSeekStepN, и передайте объект Collection <SortField <? >> в качестве параметра метода.

Исходный код нашей реализации выглядит следующим образом (соответствующая часть выделена):

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
import org.jooq.DSLContext;
import org.jooq.SortField;
import org.jooq.TableField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
 
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
 
@Repository
public class JOOQTodoRepository implements TodoRepository {
 
    private final DateTimeService dateTimeService;
 
    private final DSLContext jooq;
 
    //The constructor is omitted for the sake of clarity
 
    @Transactional(readOnly = true)
    @Override
    public List<Todo> findBySearchTerm(String searchTerm, Pageable pageable) {
        String likeExpression = "%" + searchTerm + "%";
 
        List<TodosRecord> queryResults = jooq.selectFrom(TODOS)
                .where(
                        TODOS.DESCRIPTION.likeIgnoreCase(likeExpression)
                                .or(TODOS.TITLE.likeIgnoreCase(likeExpression))
                )
                .orderBy(getSortFields(pageable.getSort()))
                .fetchInto(TodosRecord.class);
 
        return convertQueryResultsToModelObjects(queryResults);
    }
 
    private Collection<SortField<?>> getSortFields(Sort sortSpecification) {
        Collection<SortField<?>> querySortFields = new ArrayList<>();
 
        if (sortSpecification == null) {
            return querySortFields;
        }
 
        Iterator<Sort.Order> specifiedFields = sortSpecification.iterator();
 
        while (specifiedFields.hasNext()) {
            Sort.Order specifiedField = specifiedFields.next();
 
            String sortFieldName = specifiedField.getProperty();
            Sort.Direction sortDirection = specifiedField.getDirection();
 
            TableField tableField = getTableField(sortFieldName);
            SortField<?> querySortField = convertTableFieldToSortField(tableField, sortDirection);
            querySortFields.add(querySortField);
        }
 
        return querySortFields;
    }
 
    private TableField getTableField(String sortFieldName) {
        TableField sortField = null;
        try {
            Field tableField = TODOS.getClass().getField(sortFieldName);
            sortField = (TableField) tableField.get(TODOS);
        } catch (NoSuchFieldException | IllegalAccessException ex) {
            String errorMessage = String.format("Could not find table field: {}", sortFieldName);
            throw new InvalidDataAccessApiUsageException(errorMessage, ex);
        }
 
        return sortField;
    }
 
    private SortField<?> convertTableFieldToSortField(TableField tableField, Sort.Direction sortDirection) {
        if (sortDirection == Sort.Direction.ASC) {
            return tableField.asc();
        }
        else {
            return tableField.desc();
        }
    }
 
    private List<Todo> convertQueryResultsToModelObjects(List<TodosRecord> queryResults) {
        List<Todo> todoEntries = new ArrayList<>();
 
        for (TodosRecord queryResult : queryResults) {
            Todo todoEntry = convertQueryResultToModelObject(queryResult);
            todoEntries.add(todoEntry);
        }
 
        return todoEntries;
    }
 
    private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
        return Todo.getBuilder(queryResult.getTitle())
                .creationTime(queryResult.getCreationTime())
                .description(queryResult.getDescription())
                .id(queryResult.getId())
                .modificationTime(queryResult.getModificationTime())
                .build();
    }
    
    //The other methods are omitted for the sake of clarity
}

Это решение работает, но оно передает сведения о реализации нашего уровня хранилища (и базы данных) клиентам нашего REST API. Мы могли бы избежать этого, указав группу разрешенных псевдонимов для имен столбцов и реализовав компонент перевода, который преобразует эти псевдонимы в имена полей класса Todos .

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

Это на самом деле отличный пример дырявой абстракции. Этот термин был первоначально популяризирован Джоэлем Спольски. Он «изобрел» закон об утечках абстракций, который гласит:

Все нетривиальные абстракции, в какой-то степени, негерметичны.

Вы можете получить больше информации об предложении ORDER BY , прочитав раздел 4.3.2.9 Предложение ORDER BY справочного руководства jOOQ .

Теперь мы добавили поддержку сортировки в наш поисковый запрос. Давайте продолжим и закончим нашу функцию поиска, добавив поддержку пагинации в метод findBySearchTerm () .

Разбивка результатов запроса

Мы можем разбить на страницы результаты нашего поискового запроса, добавив предложение LIMIT .. OFFSET в запрос к нашей базе данных. Мы можем сделать это, внеся следующие изменения в реализацию нашего запроса к базе данных:

  1. Укажите количество возвращаемых строк, вызвав метод limit (int NumberOfRows) интерфейса SelectLimitStep, и передайте размер страницы параметру метода (размер страницы можно получить, вызвав метод getPageSize () интерфейса Pageable ).
  2. Укажите смещение, вызвав метод offset (int offset) интерфейса SelectOffsetStep, и передайте смещение в качестве параметра метода (смещение можно получить, вызвав метод getOffset () интерфейса Pageable ).

После того, как мы внесли эти изменения в наш метод репозитория, исходный код нашего метода репозитория выглядит следующим образом (изменения выделены):

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
import org.jooq.DSLContext;
import org.jooq.SortField;
import org.jooq.TableField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
 
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
 
@Repository
public class JOOQTodoRepository implements TodoRepository {
 
    private final DateTimeService dateTimeService;
 
    private final DSLContext jooq;
 
    //The constructor is omitted for the sake of clarity
 
    @Transactional(readOnly = true)
    @Override
    public List<Todo> findBySearchTerm(String searchTerm, Pageable pageable) {
        String likeExpression = "%" + searchTerm + "%";
 
        List<TodosRecord> queryResults = jooq.selectFrom(TODOS)
                .where(
                        TODOS.DESCRIPTION.likeIgnoreCase(likeExpression)
                                .or(TODOS.TITLE.likeIgnoreCase(likeExpression))
                )
                .orderBy(getSortFields(pageable.getSort()))
                .limit(pageable.getPageSize()).offset(pageable.getOffset())
                .fetchInto(TodosRecord.class);
 
        return convertQueryResultsToModelObjects(queryResults);
    }
 
    private Collection<SortField<?>> getSortFields(Sort sortSpecification) {
        Collection<SortField<?>> querySortFields = new ArrayList<>();
 
        if (sortSpecification == null) {
            return querySortFields;
        }
 
        Iterator<Sort.Order> specifiedFields = sortSpecification.iterator();
 
        while (specifiedFields.hasNext()) {
            Sort.Order specifiedField = specifiedFields.next();
 
            String sortFieldName = specifiedField.getProperty();
            Sort.Direction sortDirection = specifiedField.getDirection();
 
            TableField tableField = getTableField(sortFieldName);
            SortField<?> querySortField = convertTableFieldToSortField(tableField, sortDirection);
            querySortFields.add(querySortField);
        }
 
        return querySortFields;
    }
 
    private TableField getTableField(String sortFieldName) {
        TableField sortField = null;
        try {
            Field tableField = TODOS.getClass().getField(sortFieldName);
            sortField = (TableField) tableField.get(TODOS);
        } catch (NoSuchFieldException | IllegalAccessException ex) {
            String errorMessage = String.format("Could not find table field: {}", sortFieldName);
            throw new InvalidDataAccessApiUsageException(errorMessage, ex);
        }
 
        return sortField;
    }
 
    private SortField<?> convertTableFieldToSortField(TableField tableField, Sort.Direction sortDirection) {
        if (sortDirection == Sort.Direction.ASC) {
            return tableField.asc();
        }
        else {
            return tableField.desc();
        }
    }
 
    private List<Todo> convertQueryResultsToModelObjects(List<TodosRecord> queryResults) {
        List<Todo> todoEntries = new ArrayList<>();
 
        for (TodosRecord queryResult : queryResults) {
            Todo todoEntry = convertQueryResultToModelObject(queryResult);
            todoEntries.add(todoEntry);
        }
 
        return todoEntries;
    }
 
    private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
        return Todo.getBuilder(queryResult.getTitle())
                .creationTime(queryResult.getCreationTime())
                .description(queryResult.getDescription())
                .id(queryResult.getId())
                .modificationTime(queryResult.getModificationTime())
                .build();
    }
    
    //Other methods are omitted for the sake of clarity
}

Вы можете получить более подробную информацию о предложении LIMIT .. OFFSET , прочитав раздел 4.3.2.10 Предложение LIMIT .. OFFSET справочного руководства jOOQ .

Если вам нужно реализовать «вечную прокрутку» (как та, что есть у Facebook на временной шкале), вам следует рассмотреть возможность использования метода поиска. Вы можете получить больше информации об этом на веб-сайте jOOQ:

Это все люди. Давайте продолжим и подведем итог тому, что мы узнали из этого блога.

Резюме

Теперь мы реализовали функцию поиска, которая поддерживает сортировку и разбиение на страницы. Этот урок научил нас трем вещам:

  • Мы узнали, как мы можем использовать поддержку веб-нумерации проекта Spring Data Commons.
  • Мы узнали, как мы можем добавить предложение ORDER BY в запрос к базе данных.
  • Мы узнали, как мы можем добавить предложение LIMIT .. OFFSET к запросу базы данных.

Следующая часть этого руководства описывает, как мы можем интегрировать Spring Data JPA и jOOQ, и, что более важно, почему мы должны это делать.

  • Пример приложения этого блога доступен на Github .