關注公衆號: 鍋外的大佬前端
每日推送國外優秀的技術翻譯文章,勵志幫助國內的開發者更好地成長!web
Spring MVC
和 Spring Data
都用他們的方式在簡化應用程序開發作的很棒。可是,若是咱們將他們組合在一塊兒呢?spring
在本教程中,咱們將查看 Spring Data
的Web支持 以及它的解析器如何減小樣板文件並使咱們的controller
更有表現力。數據庫
在此過程當中,咱們將查看Querydsl
及其與Spring Data
的集成。json
Spring Data
的Web
支持是一組在標準Spring MVC
平臺上實現的與Web
相關的功能,目的是爲controller
層添加額外的功能。數組
Spring Data web
支持的功能是圍繞幾個解析器類構建的。解析器簡化了和 Spring Data repository
交互的controller
方法的實現,還使用額外的功能加強了他們。bash
這些功能包括從repository
層獲取域對象,而不須要顯式調用repository
實現,以及構建能夠做爲支持分頁和排序的數據片斷髮送到客戶端的controller
響應。app
此外,對於帶有一個或多個請求參數到controller
方法的請求能夠在內部被解析爲 Querydsl
查詢。dom
要了解咱們如何使用Spring Data web
支持來改進咱們的controller
的功能,讓咱們建立一個基本的Spring Boot
項目。spring-boot
咱們的演示項目的Maven
依賴是至關標準的,咱們稍後將討論一些例外狀況:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
複製代碼
在這種狀況下,咱們引入了spring-boot-starter-web
,咱們將用他建立RESTful controller
,spring-boot-starter-jpa
用於實現持久層,以及spring-boot-starter-test
用於測試controller API
。
因爲咱們將採用H2
做爲底層數據庫,咱們也引入了com.h2database
。
讓咱們記住,spring-boot-starter-web
默認啓用了 Spring Data web
支持。所以,咱們不須要建立任何額外的 @Configuration
類來使其在咱們的應用程序中運行。
相反的,對於非Spring Boot
項目,咱們須要定義一個@Configuration
類並使用 @EnableWebMvc
和 @EnableSpringDataWebSupport
註解。
如今,讓咱們添加一個簡單的 User JPA
實體類到項目中,這樣咱們能夠有一個可用的域模型:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private final String name;
// standard constructor / getters / toString
}
複製代碼
爲了簡化代碼,咱們的演示 Spring Boot
應用程序的功能將縮小爲只從H2
內存數據庫中獲取一些 User 實體。
Spring Boot
能夠輕鬆建立提供開箱即用的最小CRUD
功能的repository
實現。所以,讓咱們定義一個和 User JPA
實體一塊兒運行的簡單的repository
接口:
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}
複製代碼
除了繼承 PagingAndSortingRepository
以外,UserRepository
接口的定義一點也不復雜。
這表示 Spring MVC
啓用了對數據庫記錄自動分頁和排序功能。
如今,咱們至少須要實現一個基礎的RESTful controller
,它充當客戶端和 repository
層間的中間層。
所以,讓咱們建立一個 controller
類,它在其構造器中獲取UserRepository
實例並添加一個方法來經過id
查找User
實體:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User findUserById(@PathVariable("id") User user) {
return user;
}
}
複製代碼
最後,讓咱們定義應用程序的主類,並使用一些User
實體填充H2
數據庫:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner initialize(UserRepository userRepository) {
return args -> {
Stream.of("John", "Robert", "Nataly", "Helen", "Mary").forEach(name -> {
User user = new User(name);
userRepository.save(user);
});
userRepository.findAll().forEach(System.out::println);
};
}
}
複製代碼
如今,讓咱們運行應用程序。和預期的同樣,咱們在啓動時看到打印到控制檯的持久化的 User 實體:
User{id=1, name=John}
User{id=2, name=Robert}
User{id=3, name=Nataly}
User{id=4, name=Helen}
User{id=5, name=Mary}
複製代碼
目前,UserController
類只實現了findUserById()
方法。
初看之下,方法實現看起來至關簡單。但它在幕後實際封裝了許多Spring Data web
支持的功能。因爲該方法將User
實例做爲參數,咱們最終可能任務咱們須要在請求中顯示傳遞domain
對象,但咱們沒有。
Spring MVC
使用 DomainClassConverter
類轉換id
路徑變量到domain
類的 id
類型並使用它從 repository
層獲取匹配的domain
對象,無需進一步查找。例如,http://localhost:8080/users/1
端點(endpoint)的GET
HTTP請求將返回以下結果:
{
"id":1,
"name":"John"
}
複製代碼
所以,咱們能夠建立繼承測試並檢查findUserById()
方法的行爲:
@Test
public void whenGetRequestToUsersEndPointWithIdPathVariable_thenCorrectResponse() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/users/{id}", "1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"));
}
}
複製代碼
或者,咱們可使用 REST API
測試工具,例如Postman
,來測試該方法。 DomainClassConverter
的好處是咱們不須要在controller
方法中顯式的調用 repository
實現,經過簡單的指定 id
路徑變量以及可解析的 domain
類實例,咱們已經觸發了domain
對象的查找。
Spring MVC
支持在controller
和repository
中使用 Pageable
類型。
簡單來講,Pageable
實例是一個保存分頁信息的對象。所以,當咱們傳遞Pageable
參數給controller
方法,Spring MVC
使用 PageableHandlerMethodArgumentResolver
類將Pageable
實例解析爲 PageRequest
對象,這是一個簡單的Pageable
實現。
要理解 PageableHandlerMethodArgumentResolver
類如何運行。讓咱們添加一個新方法到UserController
類:
@GetMapping("/users")
public Page<User> findAllUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
複製代碼
和 findUserById()
方法做爲對照,這裏咱們須要調用 repository
實現來或者數據庫中持久化的全部User JPA
實體。因爲該方法使用了Pageable
實例,所以它返回存儲在Page<User>
對象中實體集的子集。
Page
對象是咱們能夠用於檢索有關分頁結果信息的對象列表的子列表對象,包括結果總頁數和咱們正在檢索的頁數。
默認狀況下,Spring MVC
使用 PageableHandlerMethodArgumentResolver
類構造帶有一下請求參數的 PageRequest
對象:
例如,http://localhost:8080/users
端點(endpoint)的 GET
請求將返回一下輸出:
{
"content":[
{
"id":1,
"name":"John"
},
{
"id":2,
"name":"Robert"
},
{
"id":3,
"name":"Nataly"
},
{
"id":4,
"name":"Helen"
},
{
"id":5,
"name":"Mary"
}],
"pageable":{
"sort":{
"sorted":false,
"unsorted":true,
"empty":true
},
"pageSize":5,
"pageNumber":0,
"offset":0,
"unpaged":false,
"paged":true
},
"last":true,
"totalElements":5,
"totalPages":1,
"numberOfElements":5,
"first":true,
"size":5,
"number":0,
"sort":{
"sorted":false,
"unsorted":true,
"empty":true
},
"empty":false
}
複製代碼
咱們能夠看到,響應包括 first
、pageSize
、totalElements
和 totalPages
JSON 元素。這很是有用,由於前端可使用這些元素輕鬆建立分頁機制。
另外,咱們可使用集成測試來檢查findAllUsers()
方法:
@Test
public void whenGetRequestToUsersEndPoint_thenCorrectResponse() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/users")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$['pageable']['paged']").value("true"));
}
複製代碼
在許多狀況下,咱們須要自定義分頁參數。完成此操做的最簡單方法是使用 @PageableDefault
註解:
@GetMapping("/users")
public Page<User> findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
return userRepository.findAll(pageable);
}
複製代碼
或者,咱們可使用 PageRequest
的 of()
靜態工廠方法建立自定義PageRequest
對象並將其傳遞給repository
方法:
@GetMapping("/users")
public Page<User> findAllUsers() {
Pageable pageable = PageRequest.of(0, 5);
return userRepository.findAll(pageable);
}
複製代碼
第一個參數是從 0 開始的頁數,第二個是咱們但願檢索的頁面大小。
在上面的例子中,咱們建立了一個User
實體的PageRequest
對象,從第一頁(0)開始,頁面有 5 個實體。
另外,咱們可使用page
和size
請求參數構建PageRequest
對象:
@GetMapping("/users")
public Page<User> findAllUsers(@RequestParam("page") int page,
@RequestParam("size") int size, Pageable pageable) {
return userRepository.findAll(pageable);
}
複製代碼
使用此實現,http://localhost:8080/users?page=0&size=2 端點(endpoint)的 GET
請求將返回User
對象的第一頁,結果頁的大小將爲 2:
{
"content": [
{
"id": 1,
"name": "John"
},
{
"id": 2,
"name": "Robert"
}
],
// continues with pageable metadata
}
複製代碼
分頁是有效管理大量數據庫記錄的業界標準作法。可是,就其自己而言,若是咱們不能以某種特定方式對記錄進行排序那將毫無用處。
爲此,Spring MVC
提供了SortHandlerMethodArgumentResolver
類。該解析器自動從請求參數或@SortDefault
註解建立Sort
實例。
爲了弄清楚 SortHandlerMethodArgumentResolver
類如何運做,讓咱們添加 findAllUsersSortedByName()
方法到controller
類:
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@RequestParam("sort") String sort, Pageable pageable) {
return userRepository.findAll(pageable);
}
複製代碼
在這種狀況下,SortHandlerMethodArgumentResolver
類將使用sort
請求參數建立Sort
對象。
所以,http://localhost:8080/sortedusers?sort=name端點(endpoint)的GET
請求將返回一個JSON
數組,按 name
屬性排序的User
對象列表:
{
"content": [
{
"id": 4,
"name": "Helen"
},
{
"id": 1,
"name": "John"
},
{
"id": 5,
"name": "Mary"
},
{
"id": 3,
"name": "Nataly"
},
{
"id": 2,
"name": "Robert"
}
],
// continues with pageable metadata
}
複製代碼
或者,咱們可使用Sort.by()
靜態工廠方法建立一個Sort
對象,該方法接受一個非null
,非空(empty)的用於排序的String
屬性的數組。
在這種狀況下,咱們將只經過name
屬性對記錄進行排序:
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName() {
Pageable pageable = PageRequest.of(0, 5, Sort.by("name"));
return userRepository.findAll(pageable);
}
複製代碼
固然,咱們可使用多個屬性,只要他們是在domain
類中聲明瞭的。
一樣,咱們可使用 @SortDefault
註解得到相同的結果:
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@SortDefault(sort = "name",
direction = Sort.Direction.ASC) Pageable pageable) {
return userRepository.findAll(pageable);
}
複製代碼
最後,讓咱們建立集成測試檢查方法的行爲:
@Test
public void whenGetRequestToSorteredUsersEndPoint_thenCorrectResponse() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/sortedusers")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$['sort']['sorted']").value("true"));
}
複製代碼
正如咱們介紹中提到的,Spring Data web
支持容許咱們在controller
方法中使用請求參數構建Querydsl
的Predicate
類型並構造Querydsl
查詢。
爲了簡單起見,咱們將看到Spring MVC
如何將請求參數轉換爲Querydsl BooleanExpression
,而後傳遞給QuerydslPredicateExecutor
。
爲此,咱們首先須要添加querydsl-apt
和 querydsl-jpa Maven
依賴到 pom.xml
文件:
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
複製代碼
接下來,咱們須要重構咱們的 UserRepository
接口,該接口還必須繼承 QuerydslPredicateExecutor
接口:
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long>,
QuerydslPredicateExecutor<User> {
}
複製代碼
最後,讓咱們將如下方法添加到UserController
類:
@GetMapping("/filteredusers")
public Iterable<User> getUsersByQuerydslPredicate(@QuerydslPredicate(root = User.class)
Predicate predicate) {
return userRepository.findAll(predicate);
}
複製代碼
儘管方法實現看起來很是簡單,但它實際上暴露了許多表面之下的功能。
假設咱們想要從數據庫中獲取匹配給定name
的全部User
實體。咱們能夠經過調用方法並在URL
中指定name
請求參數來實現:
http://localhost:8080/filteredusers?name=John
正如預期,請求將返回如下結果:
[
{
"id": 1,
"name": "John"
}
]
複製代碼
和咱們前面作的同樣,咱們可使用集成測試檢查getUsersByQuerydslPredicate()
方法:
@Test
public void whenGetRequestToFilteredUsersEndPoint_thenCorrectResponse() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/filteredusers")
.param("name", "John")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("John"));
}
複製代碼
這只是Querydsl web
支持如何運行的一個基礎示例。但它實際上沒有暴露出它的全部能力。
如今,假設咱們但願獲取匹配給定id
的User
實體。在這種狀況下,咱們只須要在URL
中傳遞 id
請求參數:
http://localhost:8080/filteredusers?id=2
在這種狀況下,咱們將獲得這個結果:
[
{
"id": 2,
"name": "Robert"
}
]
複製代碼
很明顯,Querydsl web
支持是一個很是強大的功能,咱們可使用它來獲取匹配給定條件的數據庫記錄。在全部狀況下,整個過程歸結爲只調用具備不一樣請求參數的單個 controller
方法。
在本教程中,咱們深刻查看了Spring web
支持的關鍵組件並學習瞭如何在演示Spring Boot
項目中使用它。
和往常同樣,本教程中顯示的全部示例均可以在 GitHub
上得到。
做者:Alejandro Ugarte
譯者:Darren Luo 推薦關注公衆號:鍋外的大佬
推送國外優秀的技術翻譯文章,勵志幫助國內的開發者更好地成長!