1. 概要

SpringMVCSpringData はそれぞれ、それ自体でアプリケーション開発を簡素化する優れた機能を果たします。 しかし、それらをまとめるとどうなるでしょうか。

このチュートリアルでは、 Spring DataのWebサポートリゾルバーがボイラープレートを減らし、コントローラーをより表現力豊かにする方法を見ていきます。

その過程で、 Querydsl と、SpringDataとの統合がどのように見えるかを見ていきます。

2. 少し背景

Spring DataのWebサポートは、標準のSpring MVCプラットフォーム上に実装された一連のWeb関連機能であり、コントローラー層に機能を追加することを目的としています。

Spring Data Webサポートの機能は、いくつかのresolverクラスを中心に構築されています。 リゾルバーは、 Spring Data リポジトリーと相互運用するコントローラーメソッドの実装を合理化し、追加機能でそれらを強化します。

これらの機能には、リポジトリレイヤーからのドメインオブジェクトのフェッチ、リポジトリ実装を明示的に呼び出す必要なしのセグメントとしてクライアントに送信できるコントローラー応答の構築が含まれますページネーションとソートをサポートするデータの。

また、1つ以上の要求パラメーターを受け取るコントローラーメソッドへの要求は、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>

この場合、 spring-boot-starter-web を含めました。これは、RESTfulコントローラーの作成に使用するためです。 spring-boot-starter-jpa は、永続レイヤーを実装するために使用します。 、および spring-boot-starter-test は、コントローラーAPIをテストします。

基盤となるデータベースとして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機能を提供するリポジトリ実装を簡単に作成できます。 したがって、 UserJPAエンティティで機能する単純なリポジトリインターフェースを定義しましょう。

@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}

UserRepository インターフェースの定義には、 PagingAndSortingRepository を拡張することを除いて、本質的に複雑なものはありません。

これはSpringMVCに信号を送り、データベースレコードの自動ページングおよび並べ替え機能を有効にします

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()メソッドのみを実装しています。

一見すると、メソッドの実装はかなり単純に見えます。 しかし、実際には、多くのSpringDataWebサポート機能を舞台裏でカプセル化しています。

このメソッドはUserインスタンスを引数として取るため、リクエストでドメインオブジェクトを明示的に渡す必要があると考えてしまう可能性があります。 しかし、私たちはしません。

Spring MVCは、 DomainClassConverter class を使用してidパス変数をドメインクラスのidタイプに変換し、リポジトリレイヤーから一致するドメインオブジェクトをフェッチするために使用します。 それ以上のルックアップは必要ありません。

たとえば、 http:// localhost:8080 / users / 1エンドポイントへのGETHTTPリクエストは、次の結果を返します。

{
  "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"));
    }
}

または、PostmanなどのRESTAPIテストツールを使用してメソッドをテストすることもできます。

DomainClassConverter の良いところは、コントローラーメソッドでリポジトリ実装を明示的に呼び出す必要がないことです。

解決可能なドメインクラスインスタンスとともにidパス変数を指定するだけで、ドメインオブジェクトのルックアップが自動的にトリガーされます。

5. PageableHandlerMethodArgumentResolverクラス

Spring MVCは、コントローラーとリポジトリーでのPageableタイプの使用をサポートしています。

簡単に言うと、 Pageable インスタンスは、ページング情報を保持するオブジェクトです。 したがって、 Pageable 引数をコントローラーメソッドに渡すと、SpringMVCはPageableHandlerMethodArgumentResolver クラスを使用して、PageableインスタンスをPageRequestオブジェクトに解決します。単純なPageableの実装。

5.1. Pageableをコントローラーメソッドパラメーターとして使用する

PageableHandlerMethodArgumentResolver クラスがどのように機能するかを理解するために、UserControllerクラスに新しいメソッドを追加しましょう。

@GetMapping("/users")
public Page<User> findAllUsers(Pageable pageable) {
    return userRepository.findAll(pageable);
}

findUserById()メソッドとは対照的に、ここでは、データベースに永続化されているすべての UserJPAエンティティをフェッチするためにリポジトリ実装を呼び出す必要があります。

メソッドはページ可能たとえば、エンティティのセット全体のサブセットを返し、 ページ物体。

Page オブジェクトは、結果ページの総数や結果ページの数など、ページングされた結果に関する情報を取得するために使用できるいくつかのメソッドを公開するオブジェクトのリストのサブリストです。取得しているページ。

デフォルトでは、SpringMVCはPageableHandlerMethodArgumentResolver クラスを使用して、次のリクエストパラメーターを使用してPageRequestオブジェクトを構築します。

  • page :取得するページのインデックス–パラメーターはゼロインデックスであり、デフォルト値は0です。
  • size :取得するページ数–デフォルト値は 20
  • 選別 :次の形式を使用して、結果を並べ替えるために使用できる1つ以上のプロパティ: property1、property2(、asc | desc)– 例えば、 ?sort = name&sort = email、asc

たとえば、 http:// localhost:8080 / users エンドポイントへの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 、および totalPagesJSON要素が含まれています。 フロントエンドはこれらの要素を使用してページングメカニズムを簡単に作成できるため、これは非常に便利です。

さらに、統合テストを使用して、 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 オブジェクトを作成し、リポジトリメソッドに渡すこともできます。

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

最初のパラメータはゼロベースのページインデックスであり、2番目のパラメータは取得するページのサイズです。

上記の例では、UserエンティティのPageRequestオブジェクトを作成しました。最初のページ( 0 )から始まり、ページには5[があります。 X159X]エントリ。

さらに、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 エンドポイントはの最初のページを返しますユーザーオブジェクト、および結果ページのサイズは2になります:

{
  "content": [
    {
      "id": 1,
      "name": "John"
    },
    {
      "id": 2,
      "name": "Robert"
    }
  ],
   
  // continues with pageable metadata
  
}

6. SortHandlerMethodArgumentResolverクラス

ページングは、多数のデータベースレコードを効率的に管理するための事実上のアプローチです。 しかし、それ自体では、特定の方法でレコードを並べ替えることができなければ、まったく役に立ちません。

この目的のために、SpringMVCはSortHandlerMethodArgumentResolverクラスを提供します。 resolver は、リクエストパラメータまたは@SortDefaultアノテーションからSortインスタンスを自動的に作成します。

6.1. sortコントローラーメソッドパラメーターの使用

SortHandlerMethodArgumentResolver クラスがどのように機能するかを明確に理解するために、 findAllUsersSortedByName()メソッドをコントローラークラスに追加しましょう。

@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 エンドポイントへのGETリクエストは、UserオブジェクトのリストがソートされたJSON配列を返します。 名前プロパティ:

{
  "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でも空でもないarrayのソートするStringプロパティ。

この場合、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. QuerydslWebサポート

はじめに述べたように、Spring Data Webサポートでは、コントローラーメソッドでリクエストパラメーターを使用して、QuerydslPredicateタイプを構築し、Querydslクエリを構築できます。

簡単にするために、SpringMVCがリクエストパラメーターをQuerydsl BooleanExpression に変換し、それがQuerydslPredicateExecutorに渡される方法を見ていきます。

これを実現するには、最初にquerydsl-aptおよびquerydsl-jpaMavenの依存関係を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);
}

メソッドの実装はかなり単純に見えますが、実際には表面下に多くの機能が公開されています。

指定された名前に一致するすべてのUserエンティティをデータベースからフェッチするとします。 このは、メソッドを呼び出し、URLで名前要求パラメーターを指定するだけで実現できます。

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"));
}

これは、QuerydslWebサポートがどのように機能するかの基本的な例にすぎません。 しかし、実際にはその力のすべてを明らかにしているわけではありません。

さて、フェッチしたいとしましょうユーザー与えられたものと一致するエンティティ id。 このような場合には、 URLでidリクエストパラメータを渡す必要があります

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

この場合、次の結果が得られます。

[
  {
    "id": 2,
    "name": "Robert"
  }
]

Querydsl Webサポートが、特定の条件に一致するデータベースレコードをフェッチするために使用できる非常に強力な機能であることは明らかです。

すべての場合において、プロセス全体は、異なる要求パラメーターを使用して単一のコントローラーメソッドを呼び出すだけでに要約されます。

8. 結論

このチュートリアルでは、 Spring Webサポートの主要コンポーネントを詳しく調べ、デモSpring Bootプロジェクト内での使用方法を学びました。

いつものように、このチュートリアルに示されているすべての例は、GitHubで入手できます。