Spring Data Webサポート

1. 概要

link:/spring-mvc-tutorial[Spring MVC]およびlink:/spring-data[Spring Data]は、それぞれ独自の方法でアプリケーション開発を簡素化する素晴らしい仕事をしています。 しかし、それらをまとめるとどうなりますか?
このチュートリアルでは、https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web [Spring DataのWebサポート]と*その_resolvers_を見ていきますボイラープレートを削減し、コントローラーの表現力を高めることができます。*
途中で、https://www.baeldung.com/intro-to-querydsl [Querydsl]と​​、Spring Dataとの統合がどのように見えるかについて説明します。

2. 背景のビット

Spring DataのWebサポートは、コントローラー層に追加機能を追加することを目的とした、標準のSpring MVCプラットフォームの上に実装されたWeb関連機能のセットです。
Spring Data Webサポートの機能は、いくつかの_resolver_クラスを中心に構築されています。 リゾルバーは、https://www.baeldung.com/spring-data [Spring Data]リポジトリーと相互運用するコントローラーメソッドの実装を合理化し、追加機能でそれらを強化します。
これらの機能には、リポジトリレイヤーからの*ドメインオブジェクトの取得*、リポジトリ実装を明示的に呼び出す必要なし*、およびページネーションと並べ替えをサポートするデータのセグメントとしてクライアントに送信できるコントローラー応答の構築*が含まれます。
また、1つ以上の要求パラメーターを取るコントローラーメソッドへの要求は、https://www.baeldung.com/intro-to-querydsl [Querydsl]クエリに内部的に解決できます。

3. デモスプリングブートプロジェクト

Spring Data Webサポートを使用してコントローラーの機能を改善する方法を理解するために、基本的な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>
この場合、_https://search.maven.org/search?q = g:org.springframework.boot%20AND%20a:spring-boot-starter-web [spring-boot-starter-web] _、 RESTfulコントローラーの作成に使用するため、_https://search.maven.org/search?q = g:org.springframework.boot%20AND%20a:spring-boot-starter-data-jpa [spring- boot-starter-jpa] _永続層を実装するため、および_https://search.maven.org/search?q = g:org.springframework.boot%20AND%20a:spring-boot-starter-test [spring- boot-starter-test] _コントローラーAPIのテスト用。
基になるデータベースとしてlink:/java-in-memory-databases[H2]を使用するため、https://search.maven.org/search?q = g:comを含めました。 h2database%20AND%20a:h2 [_com.h2database_]も同様です。
  • spring-boot-starter-web_はデフォルトでSpring Data Webサポートを有効にします。*したがって、アプリケーション内で動作させるために追加の @ Configuration_クラスを作成する必要はありません。

    逆に、Spring Boot以外のプロジェクトの場合は、_ @ Configuration_クラスを定義し、_ @ EnableWebMvc_および_ @ EnableSpringDataWebSupport_アノテーションを付ける必要があります。

3.1. ドメインクラス

次に、プロジェクトに単純な_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. リポジトリ層

コードをシンプルに保つため、デモ用のSpring Bootアプリケーションの機能は、H2インメモリデータベースからいくつかの_User_エンティティを取得するように絞り込まれます。
Spring Bootを使用すると、すぐに使用できる最小限のCRUD機能を提供するリポジトリ実装を簡単に作成できます。 したがって、_User_ JPAエンティティで動作する簡単なリポジトリインターフェイスを定義しましょう。
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}
_UserRepository_インターフェースの定義には、https://www.baeldung.com/spring-data-repositories [_PagingAndSortingRepository_]を拡張することを除いて、本質的に複雑なものはありません。
*これは、Spring MVCにシグナルを送信して、データベースレコードの自動ページングおよびソート機能を有効にします*。

3.3. コントローラー層

次に、クライアントとリポジトリレイヤーの間の中間層として機能する、少なくとも基本的なRESTfulコントローラーを実装する必要があります。
したがって、コンストラクターで_UserRepository_インスタンスを取得し、_id_で_User_エンティティを検索するための単一のメソッドを追加するコントローラークラスを作成しましょう。
@RestController
public class UserController {

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

3.4。 アプリケーションを実行する

最後に、アプリケーションのメインクラスを定義し、H2データベースにいくつかの_User_エンティティを追加します。
@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_インスタンスを引数として取るため、ドメインオブジェクトをリクエストで明示的に渡す必要があると考えることになります。 しかし、そうではありません。
Spring MVCは_https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/support/DomainClassConverter.html [DomainClassConverter] _クラス*を使用して_id_パスを変換します変数をドメインクラスの_id_型に追加し、リポジトリレイヤーから一致するドメインオブジェクトを取得するために使用します*。 さらに検索する必要はありません。
たとえば、_http:// localhost:8080 / user / 1 [http:// localhost:8080 / users / 1] _エンドポイントへの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"));
    }
}
または、https://www.baeldung.com/postman-testing-collections [Postman]などのREST APIテストツールを使用してメソッドをテストすることもできます。
_DomainClassConverter_の良い点は、コントローラーメソッドでリポジトリ実装を明示的に呼び出す必要がないことです。
*解決可能なドメインクラスインスタンスとともに_id_パス変数を指定するだけで、ドメインオブジェクトのルックアップが自動的にトリガーされます*。

5. _PageableHandlerMethodArgumentResolver_クラス

Spring MVCは、コントローラーおよびリポジトリーでのhttps://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Pageable.html[_Pageable_]タイプの使用をサポートしています。
簡単に言えば、_Pageable_インスタンスはページング情報を保持するオブジェクトです。 したがって、コントローラーメソッドに_Pageable_引数を渡すと、Spring MVCは_https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/PageableHandlerMethodArgumentResolver.htmlを使用します[PageableHandlerMethodArgumentResolver] _クラス* _Pageable_インスタンスを_https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html [PageRequest] _に解決するオブジェクト、*は簡単な_Pageable_実装です。

* 5.1。 _Pageable_をコントローラーメソッドパラメーターとして使用*

_PageableHandlerMethodArgumentResolver_クラスの仕組みを理解するために、_UserController_クラスに新しいメソッドを追加しましょう。
@GetMapping("/users")
public Page<User> findAllUsers(Pageable pageable) {
    return userRepository.findAll(pageable);
}
_findUserById()_メソッドとは対照的に、ここでは、リポジトリ実装を呼び出して、データベースに保持されているすべての_User_ JPAエンティティをフェッチする必要があります。
このメソッドは_Pageable_インスタンスを取得するため、_Page <User> _オブジェクトに格納されているエンティティセット全体のサブセットを返します。
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html[_Page_]オブジェクト*は、複数のオブジェクトを公開するオブジェクトのリストのサブリストです。結果ページの合計数、取得するページの数など、ページ化された結果に関する情報を取得するために使用できるメソッド。
デフォルトでは、Spring MVCは_PageableHandlerMethodArgumentResolver_クラスを使用して、次のリクエストパラメーターを使用して_PageRequest_オブジェクトを構築します。
  • page:取得するページのインデックス–パラメータは
    インデックスがゼロで、デフォルト値は_0_

  • size:取得するページ数–デフォルト
    値は_20_です

  • sort:並べ替えに使用できる1つ以上のプロパティ
    次の形式を使用した結果:property1、property2(、asc | desc)–_たとえば、?sort = name&sort = email、asc_

    たとえば、_http:// localhost:8080 / users [http:// localhost:8080 / user] http:// localhost:8080 / users [s] _エンドポイントへの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"));
}

* 5.2。 ページングパラメーターのカスタマイズ*

多くの場合、ページングパラメーターをカスタマイズする必要があります。 これを実現する最も簡単な方法は、https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/PageableDefault.html [_ @ PageableDefault_]アノテーションを使用することです。
@GetMapping("/users")
public Page<User> findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
    return userRepository.findAll(pageable);
}
または、_PageRequest_の_https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html#of-int-int-orgを使用できます。 springframework.data.domain.Sort- [of()] _カスタム_PageRequest_オブジェクトを作成し、それをリポジトリメソッドに渡す静的ファクトリメソッド:
@GetMapping("/users")
public Page<User> findAllUsers() {
    Pageable pageable = PageRequest.of(0, 5);
    return userRepository.findAll(pageable);
}
最初のパラメーターはゼロベースのページインデックスであり、2番目のパラメーターは取得するページのサイズです。
上記の例では、最初のページ(_0_)から始まる_User_エンティティの_PageRequest_オブジェクトを作成し、ページには_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_エンドポイントへのGETリクエストは_User_オブジェクトの最初のページを返し、結果ページのサイズは2になります。
{
  "content": [
    {
      "id": 1,
      "name": "John"
    },
    {
      "id": 2,
      "name": "Robert"
    }
  ],

  // continues with pageable metadata

}

6. _SortHandlerMethodArgumentResolver_クラス

ページングは​​、多数のデータベースレコードを効率的に管理するための事実上のアプローチです。 しかし、レコードを特定の方法で並べ替えることができない場合、それだけではかなり役に立ちません。
このため、Spring MVCはhttps://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/SortHandlerMethodArgumentResolver.html[_SortHandlerMethodArgumentResolver_]クラスを提供します。 リゾルバーは、要求パラメーターまたは_httpsから_https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Sort.html [Sort] _インスタンスを自動的に作成します。 //docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/web/SortDefault.html[@SortDefault]_アノテーション*。

* 6.1。 sort Controllerメソッドパラメーターの使用*

_SortHandlerMethodArgumentResolver_クラスの仕組みを明確にするために、_findAllUsersSortedByName()_メソッドをコントローラークラスに追加します。
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@RequestParam("sort") String sort, Pageable pageable) {
    return userRepository.findAll(pageable);
}
この場合、_SortHandlerMethodArgumentResolver_クラスは、_sort_要求パラメーターを使用して_Sort_オブジェクトを作成します。
その結果、_http:// the%20http:// localhost:8080 / sortedusers [http:// localhost:8080 / sortedusers?sort = name] _エンドポイントへの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() Static Factoryメソッドの使用*

または、https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Sort.html#by-java.langを使用して_Sort_オブジェクトを作成することもできます.String ...- [_ Sort.by()_]静的ファクトリーメソッド。ソートされる_String_プロパティの非null、空でない_array_を受け取ります。
この場合、_name_プロパティのみでレコードをソートします。
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName() {
    Pageable pageable = PageRequest.of(0, 5, Sort.by("name"));
    return userRepository.findAll(pageable);
}
もちろん、ドメインクラスで宣言されている限り、複数のプロパティを使用できます。

* 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サポートでは、コントローラーメソッドでリクエストパラメーターを使用してlink:/intro-to-querydsl[Querydsl]の_http://www.querydslを構築できます。 com / static / querydsl / 4.1.3 / apidocs / com / querydsl / core / types / Predicate.html [Predicate] _ typesおよびQuerydslクエリを構築します。
物事を単純にするために、Spring MVCがリクエストパラメータをQuerydslに変換する方法を見るだけですhttp://www.querydsl.com/static/querydsl/4.1.3/apidocs/com/querydsl/core/types/dsl/ BooleanExpression.html [_BooleanExpression_]は、https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/querydsl/QuerydslPredicateExecutor.html [_QuerydslPredicateExecutor_]に順に渡されます。
これを実現するには、まず_https://search.maven.org/search?q = g:com.querydsl%20AND%20a:querydsl-apt [querydsl-apt] _および_https:// searchを追加する必要があります。 maven.org/search?q=g:com.querydsl%20AND%20a:querydsl-jpa[querydsl-jpa]_ _pom.xml_ファイルへのMaven依存関係:
<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);
}
メソッドの実装はかなり単純に見えますが、実際には表面の下で多くの機能を公開しています。
特定の名前に一致するすべての_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 = 1 [http:// localhost:8080 / filteredusers?id = 2] _
この場合、次の結果が得られます。
[
  {
    "id": 2,
    "name": "Robert"
  }
]
Querydsl Webサポートは、特定の条件に一致するデータベースレコードを取得するために使用できる非常に強力な機能であることは明らかです。
すべての場合において、プロセス全体は、*異なるリクエストパラメーターで単一のコントローラーメソッドを呼び出すだけ*に要約されます。

8. 結論

このチュートリアルでは、* Spring Webサポートの主要なコンポーネントを詳細に調べ、デモのSpring Bootプロジェクト内でそれを使用する方法を学びました。*
いつものように、このチュートリアルに示されているすべての例は、https://github.com/eugenp/tutorials/tree/master/spring-data-rest [GitHub]で入手できます。