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