Spring Data Web支持

關注公衆號: 鍋外的大佬前端

每日推送國外優秀的技術翻譯文章,勵志幫助國內的開發者更好地成長!web

1.概述

Spring MVCSpring Data 都用他們的方式在簡化應用程序開發作的很棒。可是,若是咱們將他們組合在一塊兒呢?spring

在本教程中,咱們將查看 Spring Data的Web支持 以及它的解析器如何減小樣板文件並使咱們的controller更有表現力。數據庫

在此過程當中,咱們將查看Querydsl及其與Spring Data的集成。json

2.背景

Spring DataWeb支持是一組在標準Spring MVC平臺上實現的與Web相關的功能,目的是爲controller層添加額外的功能。數組

Spring Data web支持的功能是圍繞幾個解析器類構建的。解析器簡化了和 Spring Data repository交互的controller方法的實現,還使用額外的功能加強了他們。bash

這些功能包括從repository層獲取域對象,而不須要顯式調用repository實現,以及構建能夠做爲支持分頁和排序的數據片斷髮送到客戶端的controller響應。app

此外,對於帶有一個或多個請求參數到controller方法的請求能夠在內部被解析爲 Querydsl查詢。dom

3.用Spring Boot項目演示

要了解咱們如何使用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 controllerspring-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 註解。

3.1.domain類

如今,讓咱們添加一個簡單的 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

}
複製代碼

3.2.repository層

爲了簡化代碼,咱們的演示 Spring Boot 應用程序的功能將縮小爲只從H2內存數據庫中獲取一些 User 實體。

Spring Boot能夠輕鬆建立提供開箱即用的最小CRUD功能的repository 實現。所以,讓咱們定義一個和 User JPA實體一塊兒運行的簡單的repository 接口:

@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}
複製代碼

除了繼承 PagingAndSortingRepository 以外,UserRepository 接口的定義一點也不復雜。

這表示 Spring MVC啓用了對數據庫記錄自動分頁和排序功能。

3.3.controller層

如今,咱們至少須要實現一個基礎的RESTful controller,它充當客戶端和 repository 層間的中間層。

所以,讓咱們建立一個 controller 類,它在其構造器中獲取UserRepository實例並添加一個方法來經過id查找User實體:

@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public User findUserById(@PathVariable("id") User user) {
        return user;
    }
}
複製代碼

3.4.運行應用程序

最後,讓咱們定義應用程序的主類,並使用一些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}
複製代碼

4.DomainClassConverter類

目前,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對象的查找。

5.PageableHandlerMethodArgumentResolver類

Spring MVC支持在controllerrepository 中使用 Pageable類型。

簡單來講,Pageable實例是一個保存分頁信息的對象。所以,當咱們傳遞Pageable參數給controller方法,Spring MVC使用 PageableHandlerMethodArgumentResolver類將Pageable實例解析爲 PageRequest對象,這是一個簡單的Pageable實現。

5.1.使用Pageable做爲controller方法參數

要理解 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對象:

  • page:咱們要檢索的頁數——該參數從 0 開始且它的默認值爲 0
  • size:咱們要檢索的頁面的內容數量——默認值是 20
  • sort:咱們可使用一個或多個屬性對結果排序,使用如下格式:property1,property2(,asc|desc) ——舉個例子,?sort=name&sort=email,asc

例如,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
}
複製代碼

咱們能夠看到,響應包括 firstpageSizetotalElementstotalPages 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"));
}
複製代碼

5.2.自定義分頁參數

在許多狀況下,咱們須要自定義分頁參數。完成此操做的最簡單方法是使用 @PageableDefault註解:

@GetMapping("/users")
public Page<User> findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
    return userRepository.findAll(pageable);
}
複製代碼

或者,咱們可使用 PageRequestof() 靜態工廠方法建立自定義PageRequest對象並將其傳遞給repository方法:

@GetMapping("/users")
public Page<User> findAllUsers() {
    Pageable pageable = PageRequest.of(0, 5);
    return userRepository.findAll(pageable);
}
複製代碼

第一個參數是從 0 開始的頁數,第二個是咱們但願檢索的頁面大小。

在上面的例子中,咱們建立了一個User實體的PageRequest對象,從第一頁(0)開始,頁面有 5 個實體。

另外,咱們可使用pagesize請求參數構建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

}
複製代碼

6.SortHandlerMethodArgumentResolver類

分頁是有效管理大量數據庫記錄的業界標準作法。可是,就其自己而言,若是咱們不能以某種特定方式對記錄進行排序那將毫無用處。

爲此,Spring MVC提供了SortHandlerMethodArgumentResolver類。該解析器自動從請求參數或@SortDefault註解建立Sort實例。

6.1.使用 sort controller 方法參數

爲了弄清楚 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

}
複製代碼

6.2.使用 Sort.by() 靜態工廠方法

或者,咱們可使用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類中聲明瞭的。

6.3.使用@SortDefault註解

一樣,咱們可使用 @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"));
}
複製代碼

7. Querydsl Web支持

正如咱們介紹中提到的,Spring Data web支持容許咱們在controller方法中使用請求參數構建QuerydslPredicate類型並構造Querydsl查詢。

爲了簡單起見,咱們將看到Spring MVC 如何將請求參數轉換爲Querydsl BooleanExpression,而後傳遞給QuerydslPredicateExecutor

爲此,咱們首先須要添加querydsl-aptquerydsl-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支持如何運行的一個基礎示例。但它實際上沒有暴露出它的全部能力。

如今,假設咱們但願獲取匹配給定idUser實體。在這種狀況下,咱們只須要在URL中傳遞 id請求參數:

http://localhost:8080/filteredusers?id=2

在這種狀況下,咱們將獲得這個結果:

[
  {
    "id": 2,
    "name": "Robert"
  }
]
複製代碼

很明顯,Querydsl web支持是一個很是強大的功能,咱們可使用它來獲取匹配給定條件的數據庫記錄。在全部狀況下,整個過程歸結爲只調用具備不一樣請求參數的單個 controller 方法。

8.總結

在本教程中,咱們深刻查看了Spring web支持的關鍵組件並學習瞭如何在演示Spring Boot項目中使用它。

和往常同樣,本教程中顯示的全部示例均可以在 GitHub 上得到。

原文連接:www.baeldung.com/spring-data…

做者:Alejandro Ugarte

譯者:Darren Luo 推薦關注公衆號:鍋外的大佬

推送國外優秀的技術翻譯文章,勵志幫助國內的開發者更好地成長!

相關文章
相關標籤/搜索