javascript
将jOOQ与Spring结合使用:排序和分页
JOOQ是一個庫,可以幫助我們控制SQL。 它可以從我們的數(shù)據(jù)庫生成代碼,并允許我們使用其流暢的API來構(gòu)建類型安全的數(shù)據(jù)庫查詢。
本教程前面的部分向我們介紹了如何配置應(yīng)用程序的應(yīng)用程序上下文,如何從數(shù)據(jù)庫生成代碼以及如何將CRUD操作添加到j(luò)OOQ存儲庫。
這次,我們將學(xué)習(xí)如何實現(xiàn)支持排序和分頁的簡單搜索功能。
讓我們開始吧。
補充閱讀:
- 將jOOQ與Spring結(jié)合使用:配置是本教程的第一部分,它描述了您可以配置使用jOOQ的Spring應(yīng)用程序的應(yīng)用程序上下文。 您可以在不閱讀本教程第一部分的情況下了解此博客文章,但是,如果您想在基于Spring的應(yīng)用程序中真正使用jOOQ,建議您也閱讀本教程的第一部分。
- 將jOOQ與Spring結(jié)合使用:代碼生成是本教程的第二部分,它描述了我們?nèi)绾螌?shù)據(jù)庫進行反向工程并創(chuàng)建代表不同數(shù)據(jù)庫表,記錄等的jOOQ查詢類。 因為這些類是類型安全SQL查詢的構(gòu)建塊, 所以建議您在閱讀本博客文章之前閱讀本教程的第二部分 。
- 在Spring中使用jOOQ:CRUD描述了如何為管理待辦事項的簡單應(yīng)用程序添加CRUD操作。 因為它涵蓋了使用Spring創(chuàng)建jOOQ存儲庫所需的信息, 所以建議您在閱讀此博客文章之前先閱讀它 。
向Web層添加分頁和排序支持
當(dāng)我們實現(xiàn)必須同時支持分頁和排序的搜索功能時,我們必須找出一種方法來向后端提供頁碼,頁大小,排序字段的名稱和排序順序。
我們當(dāng)然可以實現(xiàn)一個支持此功能的組件,但它并不像聽起來那么簡單。 創(chuàng)建HandlerMethodArgumentResolver很容易,它可以從HTTP請求中找到此信息并將其轉(zhuǎn)換為對象,然后將該對象作為方法參數(shù)傳遞給我們的控制器方法。 問題在于存在許多“例外”情況,這使此任務(wù)非常棘手。 例如,
- 如果從HTTP請求中找不到此信息,則必須回退到默認值。
- 如果缺少所需的信息(例如,沒有指定頁面大小就給出了頁碼),我們必須退回到默認值或向REST API用戶返回錯誤。
幸運的是,我們不必實現(xiàn)此組件。 Spring Data Commons項目具有一個組件 , 該組件從HTTP請求中提取分頁和排序信息,并允許我們將該信息注入到控制器方法中。
讓我們發(fā)現(xiàn)我們可以使用Maven獲得Spring Data Commons二進制文件。
使用Maven獲取所需的依賴關(guān)系
通過將以下依賴項聲明添加到POM文件的依賴項部分,我們可以使用Maven獲得所需的二進制文件:
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId><version>1.7.1.RELEASE</version> </dependency>下一步是對示例應(yīng)用程序的應(yīng)用程序上下文配置進行一些更改。 讓我們繼續(xù)前進,找出我們必須進行的更改。
配置應(yīng)用程序上下文
我們可以通過對應(yīng)用程序上下文配置類進行簡單的更改來啟用Spring Data的Web分頁支持,該類配置了示例應(yīng)用程序的Web層。 我們必須使用@EnableSpringDataWebSupport批注來批注配置類。 這樣可以確保所需的bean自動注冊。
@EnableSpringDataWebSupport批注的API文檔提供了有關(guān)使用此批注時注冊的bean的更多信息。
WebAppContext類的相關(guān)部分如下所示:
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 }這就對了。 現(xiàn)在,我們對示例應(yīng)用程序的應(yīng)用程序上下文配置進行了必要的更改。 讓我們找出如何在應(yīng)用程序中使用Web分頁支持。
使用網(wǎng)頁分頁
當(dāng)我們想對查詢結(jié)果進行排序和分頁時,我們必須遵循以下步驟:
首先 ,我們可以使用以下請求參數(shù)將分頁和排序配置添加到HTTP請求:
- 頁面請求參數(shù)指定請求的頁碼。
- size請求參數(shù)指定所請求頁面的大小。
- 排序請求參數(shù)指定用于對查詢結(jié)果進行排序的屬性。 此請求參數(shù)的此值必須遵循以下語法: property,property(,ASC | DESC) 。 如果未給出排序方向,則結(jié)果將按升序排序。 如果要切換排序順序,則必須使用多個排序參數(shù)(例如?sort = title&sort = id,desc )。
其次 ,我們必須在我們的控制器方法中添加一個Pageable方法參數(shù)。 TodoController類的相關(guān)部分如下所示:
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;@Autowiredpublic 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);} }現(xiàn)在,我們可以將搜索功能添加到我們的jOOQ存儲庫中。 讓我們找出這是如何完成的。
實施存儲庫層
我們要做的第一件事是向TodoService接口添加一個新的公共方法。 findBySearchTerm(String searchTerm,Pageable pageable)方法查找標(biāo)題或描述包含給定搜索詞的待辦事項,并按照作為方法參數(shù)給出的分頁和排序配置返回查詢結(jié)果。
TodoRepository接口的相關(guān)部分如下所示:
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 }此方法的實現(xiàn)有兩個職責(zé):
讓我們繼續(xù)前進,找出如何找到標(biāo)題或描述包含給定搜索詞的待辦事項。
實施搜索查詢
我們可以按照以下步驟實施搜索查詢:
我們的實現(xiàn)的源代碼如下所示:
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)@Overridepublic 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 }此示例的數(shù)據(jù)庫查詢非常簡單。 如果需要創(chuàng)建更復(fù)雜的數(shù)據(jù)庫查詢,則應(yīng)閱讀4.6節(jié)。 jOOQ參考手冊的條件表達式 。 它描述了如何在數(shù)據(jù)庫查詢中使用條件表達式。
現(xiàn)在,我們創(chuàng)建了一個存儲庫方法,該方法從數(shù)據(jù)庫中搜索待辦事項。 下一步是對該數(shù)據(jù)庫查詢的查詢結(jié)果進行排序。
查詢結(jié)果排序
在對搜索查詢的查詢結(jié)果進行排序之前,我們必須了解如何從Pageable對象獲取數(shù)據(jù)庫查詢的排序選項。
- 我們可以通過調(diào)用Pageable接口的getSort()方法來獲得對Sort對象的引用。 該對象包含從HTTP請求中找到的排序選項。
- 排序?qū)ο罂梢园銈€或多個排序選項。 Sort類的iterator()方法返回一個Iterator <Sort.Order>對象,當(dāng)我們要處理數(shù)據(jù)庫查詢的每個排序選項時可以使用該對象。
- Sort.Order類包含屬性名稱和排序方向 。
換句話說,我們必須滿足以下要求:
- 我們必須支持未指定排序選項的情況。
- 我們必須支持一種情況,其中我們的查詢結(jié)果通過使用多列進行排序。
- 我們必須假設(shè)每個列都有自己的排序順序。
我們可以通過對JOOQTodoRepository類進行以下更改來滿足這些要求:
我們的實現(xiàn)的源代碼如下所示(相關(guān)部分已突出顯示):
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)@Overridepublic 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 }此解決方案有效,但將我們的存儲庫層(和數(shù)據(jù)庫)的實現(xiàn)細節(jié)泄漏給了REST API的客戶端。 我們可以通過為列名稱指定一組允許的別名來避免這種情況,并實現(xiàn)一個轉(zhuǎn)換組件,將這些別名轉(zhuǎn)換為Todos類的字段名稱。
但是,因為這會增加我們的存儲庫類的復(fù)雜性,所以我們不會這樣做。
這實際上是泄漏抽象的一個很好的例子。 這個詞最初是由Joel Spolsky推廣的。 他“發(fā)明” 了泄漏抽象定律,該定律指出:
在某種程度上,所有非平凡的抽象都是泄漏的。
通過閱讀jOOQ參考手冊的4.3.2.9節(jié) ORDER BY子句,可以獲得有關(guān)ORDER BY子句的更多信息。
現(xiàn)在,我們在搜索查詢中添加了排序支持。 讓我們繼續(xù)并通過向findBySearchTerm()方法添加分頁支持來完成搜索功能。
分頁查詢結(jié)果
通過將LIMIT .. OFFSET子句添加到數(shù)據(jù)庫查詢中,我們可以對搜索查詢的查詢結(jié)果進行分頁。 我們可以通過對數(shù)據(jù)庫查詢的實現(xiàn)進行以下更改來做到這一點:
在對存儲庫方法進行了這些更改之后,存儲庫方法的源代碼如下所示(更改已突出顯示):
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)@Overridepublic 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 }您可以對限制更多信息..閱讀OFFSET條款部分4.3.2.10極限.. OFFSET的jOOQ參考手冊的條款 。
如果您需要實現(xiàn)“永恒滾動”(如時間軸上的Facebook),則應(yīng)考慮使用seek方法。 您可以從jOOQ網(wǎng)站獲得有關(guān)此信息的更多信息:
- 使用Seek方法使用jOOQ進行更快的SQL分頁
- 使用鍵集進行更快的SQL分頁,續(xù)
- SEEK子句@ jOOQ參考手冊
就這些了。 讓我們繼續(xù)并總結(jié)從這篇博客文章中學(xué)到的知識。
摘要
現(xiàn)在,我們已經(jīng)實現(xiàn)了支持排序和分頁的搜索功能。 本教程教會了我們?nèi)?#xff1a;
- 我們了解了如何使用Spring Data Commons項目的Web分頁支持。
- 我們學(xué)習(xí)了如何將ORDER BY子句添加到數(shù)據(jù)庫查詢中。
- 我們學(xué)習(xí)了如何在數(shù)據(jù)庫查詢中添加LIMIT .. OFFSET子句。
本教程的下一部分描述了如何集成Spring Data JPA和jOOQ,更重要的是,為什么要這樣做。
- Github上提供了此博客文章的示例應(yīng)用程序。
翻譯自: https://www.javacodegeeks.com/2014/05/using-jooq-with-spring-sorting-and-pagination.html
總結(jié)
以上是生活随笔為你收集整理的将jOOQ与Spring结合使用:排序和分页的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓导航破解root教程(安卓导航破解)
- 下一篇: Builder模式和Spring框架