【Spring Boot】並び替え・ソート機能を実装する|簡単な掲示板アプリの作成
Spring BootのWebアプリにデータを並び替える(ソート)機能を実装する方法を解説します。Spring Data JPAを活用し、掲示板の投稿リストを「作成日」や「タイトル」で昇順・降順にソートする実装方法を、画面操作からデータベース連携まで一貫して学べます。
開発環境
- OS: Windows10
- Visual Studio Code: 1.73.0
- Java: OpenJDK 23
- Spring Boot: 3.4.0
- Lombok: 1.18.30
- Thymeleaf Layout Dialect: 3.3.0
- Spring Security: 6.1.1
- Thymeleaf Extras Spring Security: 3.1.2.RELEASE
- maven-compiler-plugin: 3.8.1
サンプルコード
/src/main/java/com/example/bbs/controller/PostController.java
/src/main/java/com/example/bbs/controller/PostController.java1 @GetMapping 2 public String listPosts( 3 @RequestParam(value = "keyword", required = false) String keyword, 4- @RequestParam(value = "matchType", required = false) String matchType, 5+ @RequestParam(value = "matchType", required = false, defaultValue = "contains") String matchType, 6+ @RequestParam(value = "sortBy", required = false, defaultValue = "createdAt") String sortBy, 7+ @RequestParam(value = "sortOrder", required = false, defaultValue = "asc") String sortOrder, 8 Model model) { 9 10 // ログインユーザーを取得してモデルに渡す 11 // 検索フォームの入力値を保持 12 List<Post> posts; 13 if (keyword != null && !keyword.isEmpty() && matchType != null && !matchType.isEmpty()) { 14- posts = postService.searchPosts(keyword, matchType); 15+ posts = postService.searchPosts(keyword, matchType, sortBy, sortOrder); 16 } else { 17- posts = postService.findAll(); 18+ posts = postService.findAll(sortBy, sortOrder); 19 } 20 21+ // 検索結果をモデルに追加 22 model.addAttribute("posts", posts); 23 24 return "posts/list"; 25
/src/main/java/com/example/bbs/repository/PostRepository.java
/src/main/java/com/example/bbs/repository/PostRepository.java1 import java.util.List; 2 3 import org.springframework.data.jpa.repository.JpaRepository; 4+import org.springframework.data.domain.Sort; 5 6 public interface PostRepository extends JpaRepository<Post, Long> { 7 // 部分一致 8- List<Post> findByTitleContainingOrContentContaining(String titleKeyword, String contentKeyword); 9+ List<Post> findByTitleContainingOrContentContaining(String titleKeyword, String contentKeyword, Sort sort); 10 11 // 前方一致 12- List<Post> findByTitleStartingWithOrContentStartingWith(String titlePrefix, String contentPrefix); 13+ List<Post> findByTitleStartingWithOrContentStartingWith(String titlePrefix, String contentPrefix, Sort sort); 14 15 // 後方一致 16- List<Post> findByTitleEndingWithOrContentEndingWith(String titleSuffix, String contentSuffix); 17+ List<Post> findByTitleEndingWithOrContentEndingWith(String titleSuffix, String contentSuffix, Sort sort); 18 } 19
/src/main/java/com/example/bbs/service/PostService.java
/src/main/java/com/example/bbs/service/PostService.java1 import com.example.bbs.repository.PostRepository; 2 import org.springframework.stereotype.Service; 3 4+import org.springframework.data.domain.Sort; 5+ 6 import java.util.List; 7 import java.util.Optional; 8 9 this.postRepository = postRepository; 10 } 11 12- public List<Post> findAll() { 13- return postRepository.findAll(); 14+ public List<Post> findAll(String sortBy, String sortOrder) { 15+ // Sort.Orderを使ってソート順を設定 16+ Sort.Order order; 17+ if (sortOrder.equals("asc")) { 18+ order = new Sort.Order(Sort.Direction.ASC, sortBy); 19+ } else { 20+ order = new Sort.Order(Sort.Direction.DESC, sortBy); 21+ } 22+ 23+ // Sortオブジェクトを作成 24+ Sort sort = Sort.by(order); 25+ 26+ return postRepository.findAll(sort); 27 } 28 29- public List<Post> searchPosts(String keyword, String matchType) { 30+ public List<Post> searchPosts(String keyword, String matchType, String sortBy, String sortOrder) { 31+ // Sort.Orderを使ってソート順を設定 32+ Sort.Order order; 33+ if (sortOrder.equals("asc")) { 34+ order = new Sort.Order(Sort.Direction.ASC, sortBy); // 昇順 35+ } else { 36+ order = new Sort.Order(Sort.Direction.DESC, sortBy); // 降順 37+ } 38+ 39+ // Sortオブジェクトを作成 40+ Sort sort = Sort.by(order); 41+ 42+ // 検索条件に基づいて投稿を取得 43 switch (matchType) { 44 case "startswith": // 前方一致 45- return postRepository.findByTitleStartingWithOrContentStartingWith(keyword, keyword); 46+ return postRepository.findByTitleStartingWithOrContentStartingWith(keyword, keyword, sort); 47 case "endswith": // 後方一致 48- return postRepository.findByTitleEndingWithOrContentEndingWith(keyword, keyword); 49+ return postRepository.findByTitleEndingWithOrContentEndingWith(keyword, keyword, sort); 50 case "contains": // 部分一致(デフォルト) 51 default: 52- return postRepository.findByTitleContainingOrContentContaining(keyword, keyword); 53+ return postRepository.findByTitleContainingOrContentContaining(keyword, keyword, sort); 54 } 55 } 56 57
/src/main/resources/templates/posts/list.html
/src/main/resources/templates/posts/list.html1 <!-- 検索フォーム --> 2 <form th:action="@{/posts}" method="get" class="mb-3"> 3 <div class="row"> 4- <div class="col-md-6"> 5+ <!-- 検索キーワード --> 6+ <div class="col-md-4"> 7 <input type="text" name="keyword" class="form-control" placeholder="Search" th:value="${param.keyword}" required> 8 </div> 9+ <!-- マッチタイプ --> 10 <div class="col-md-2"> 11 <select name="matchType" class="form-control"> 12 <option value="contains" th:selected="${param.matchType?.toString() == 'contains'}">部分一致</option> 13 <option value="endswith" th:selected="${param.matchType?.toString() == 'endswith'}">後方一致</option> 14 </select> 15 </div> 16+ <!-- ソート基準 --> 17+ <div class="col-md-2"> 18+ <select name="sortBy" class="form-control"> 19+ <option value="createdAt" th:selected="${param.sortBy?.toString() == 'createdAt'}">作成日</option> 20+ <option value="title" th:selected="${param.sortBy?.toString() == 'title'}">タイトル</option> 21+ </select> 22+ </div> 23+ <!-- 並び順 --> 24+ <div class="col-md-2"> 25+ <select name="sortOrder" class="form-control"> 26+ <option value="asc" th:selected="${param.sortOrder?.toString() == 'asc'}">昇順</option> 27+ <option value="desc" th:selected="${param.sortOrder?.toString() == 'desc'}">降順</option> 28+ </select> 29+ </div> 30+ <!-- 検索ボタン --> 31 <div class="col-md-2"> 32 <button type="submit" class="btn btn-primary">検索</button> 33 </div> 34 </div> 35 </form> 36- 37 <h2>掲示板一覧</h2> 38 <table class="table table-striped"> 39 <thead> 40
コード解説
変更点: Controllerにソート条件のパラメータを追加
/src/main/java/com/example/bbs/controller/PostController.java1 @GetMapping 2 public String listPosts( 3 @RequestParam(value = "keyword", required = false) String keyword, 4- @RequestParam(value = "matchType", required = false) String matchType, 5+ @RequestParam(value = "matchType", required = false, defaultValue = "contains") String matchType, 6+ @RequestParam(value = "sortBy", required = false, defaultValue = "createdAt") String sortBy, 7+ @RequestParam(value = "sortOrder", required = false, defaultValue = "asc") String sortOrder, 8 Model model) { 9
この変更では、PostController の listPosts メソッドに、新しく二つの @RequestParam アノテーションを追加しました。これは、Webブラウザから送られてくるHTTPリクエストのパラメータを受け取るための設定です。
-
@RequestParam(value = "sortBy", required = false, defaultValue = "createdAt") String sortByvalue = "sortBy": HTTPリクエストの中でsortByという名前のパラメータを探し、その値を取得します。例えば、URLが.../posts?sortBy=titleの場合、sortBy変数には"title"が格納されます。required = false: このパラメータは必須ではないことを意味します。もしsortByパラメータがリクエストに含まれていなくてもエラーになりません。defaultValue = "createdAt": もしsortByパラメータがリクエストに含まれていなかった場合、デフォルトの値として"createdAt"がsortBy変数に設定されます。これにより、どのカラムでソートするか指定がない場合は「作成日」でソートされるようになります。
-
@RequestParam(value = "sortOrder", required = false, defaultValue = "asc") String sortOrdervalue = "sortOrder": HTTPリクエストの中でsortOrderという名前のパラメータを探し、その値を取得します。例えば、URLが.../posts?sortOrder=descの場合、sortOrder変数には"desc"が格納されます。required = false: このパラメータも必須ではありません。defaultValue = "asc": もしsortOrderパラメータがリクエストに含まれていなかった場合、デフォルトの値として"asc"(昇順) がsortOrder変数に設定されます。
これらの変更により、コントローラがソートの基準(sortBy)と並び順(sortOrder)をWebリクエストから受け取れるようになり、デフォルト値のおかげでパラメータが指定されない場合でも、作成日の昇順で表示されるようになります。また、matchType パラメータにもデフォルト値 "contains" が設定されました。
変更点: ControllerからServiceへのソート条件の引き渡し
/src/main/java/com/example/bbs/controller/PostController.java1 // 検索フォームの入力値を保持 2 List<Post> posts; 3 if (keyword != null && !keyword.isEmpty() && matchType != null && !matchType.isEmpty()) { 4- posts = postService.searchPosts(keyword, matchType); 5+ posts = postService.searchPosts(keyword, matchType, sortBy, sortOrder); 6 } else { 7- posts = postService.findAll(); 8+ posts = postService.findAll(sortBy, sortOrder); 9 } 10 11+ // 検索結果をモデルに追加 12 model.addAttribute("posts", posts); 13 14 return "posts/list";
この変更では、PostController の listPosts メソッド内で呼び出しているサービス層のメソッドに、ソート条件を示す sortBy と sortOrder の二つの引数を追加しました。
具体的には、以下の二つの箇所が変更されています。
-
postService.searchPosts(keyword, matchType, sortBy, sortOrder);- ユーザーが検索キーワードを入力した場合に呼ばれる検索処理のメソッドです。これまでの
keywordとmatchTypeに加えて、どの基準で(sortBy)、どのように並べ替えるか(sortOrder)の情報をサービス層に渡すように変更しました。
- ユーザーが検索キーワードを入力した場合に呼ばれる検索処理のメソッドです。これまでの
-
postService.findAll(sortBy, sortOrder);- 検索キーワードが入力されていない場合に、全ての投稿を取得するメソッドです。こちらも、新たに
sortByとsortOrderの情報をサービス層に渡し、取得する際にソートされるように変更しました。
- 検索キーワードが入力されていない場合に、全ての投稿を取得するメソッドです。こちらも、新たに
このように、コントローラが受け取ったソート条件を、データの取得や加工を行うサービス層に渡すことで、実際にデータベースからソートされたデータを取得するための準備が整います。
変更点: RepositoryでのSortクラスのインポート
/src/main/java/com/example/bbs/repository/PostRepository.java1 import java.util.List; 2 3 import org.springframework.data.jpa.repository.JpaRepository; 4+import org.springframework.data.domain.Sort; 5 6 public interface PostRepository extends JpaRepository<Post, Long> {
この変更では、PostRepository.java ファイルに org.springframework.data.domain.Sort クラスをインポートしました。
Sort クラスは、Spring Data JPA が提供する機能の一つで、データベースからデータを取得する際に、そのデータの並び順(ソート順)を動的に指定するために使います。
Repositoryインターフェースのメソッドに Sort オブジェクトを引数として渡すことで、SQLの ORDER BY 句に相当する処理をSpring Data JPAが自動的に生成し、ソートされた結果を取得できるようになります。この後の変更で、この Sort オブジェクトを実際にメソッドの引数として利用します。
変更点: Repository検索メソッドへのSort引数追加
/src/main/java/com/example/bbs/repository/PostRepository.java1 public interface PostRepository extends JpaRepository<Post, Long> { 2 // 部分一致 3- List<Post> findByTitleContainingOrContentContaining(String titleKeyword, String contentKeyword); 4+ List<Post> findByTitleContainingOrContentContaining(String titleKeyword, String contentKeyword, Sort sort); 5 6 // 前方一致 7- List<Post> findByTitleStartingWithOrContentStartingWith(String titlePrefix, String contentPrefix); 8+ List<Post> findByTitleStartingWithOrContentStartingWith(String titlePrefix, String contentPrefix, Sort sort); 9 10 // 後方一致 11- List<Post> findByTitleEndingWithOrContentEndingWith(String titleSuffix, String contentSuffix); 12+ List<Post> findByTitleEndingWithOrContentEndingWith(String titleSuffix, String contentSuffix, Sort sort); 13 }
この変更では、PostRepository インターフェースで定義されている各検索メソッドに、新たに Sort sort という引数を追加しました。
具体的には、以下の三つのメソッドが変更されました。
List<Post> findByTitleContainingOrContentContaining(String titleKeyword, String contentKeyword, Sort sort);List<Post> findByTitleStartingWithOrContentStartingWith(String titlePrefix, String contentPrefix, Sort sort);List<Post> findByTitleEndingWithOrContentEndingWith(String titleSuffix, String contentSuffix, Sort sort);
これらのメソッドは、これまでタイトルまたはコンテンツで部分一致、前方一致、後方一致の検索を行うためのものでした。今回 Sort sort を引数に追加することで、これらの検索結果をさらに指定された Sort オブジェクトの条件に従って並べ替えることができるようになりました。
Spring Data JPAでは、メソッド名に OrderBy を含めることで静的にソート条件を指定できますが、このように Sort オブジェクトを引数として渡すことで、実行時に動的にソートの基準や方向を切り替えることが可能になります。これにより、柔軟なソート機能を実現できます。
変更点: ServiceでのSortクラスのインポート
/src/main/java/com/example/bbs/service/PostService.java1 import com.example.bbs.repository.PostRepository; 2 import org.springframework.stereotype.Service; 3 4+import org.springframework.data.domain.Sort; 5+ 6 import java.util.List; 7 import java.util.Optional; 8
この変更では、PostService.java ファイルに org.springframework.data.domain.Sort クラスをインポートしました。
サービス層はビジネスロジックを記述する場所であり、ここで Sort オブジェクトを生成し、それをRepository層に渡すことでデータの並び替えを指示します。この Sort クラスを扱うために、事前にインポートが必要となります。
変更点: ServiceのfindAllメソッドにソートロジックを追加
/src/main/java/com/example/bbs/service/PostService.java1 this.postRepository = postRepository; 2 } 3 4- public List<Post> findAll() { 5- return postRepository.findAll(); 6+ public List<Post> findAll(String sortBy, String sortOrder) { 7+ // Sort.Orderを使ってソート順を設定 8+ Sort.Order order; 9+ if (sortOrder.equals("asc")) { 10+ order = new Sort.Order(Sort.Direction.ASC, sortBy); 11+ } else { 12+ order = new Sort.Order(Sort.Direction.DESC, sortBy); 13+ } 14+ 15+ // Sortオブジェクトを作成 16+ Sort sort = Sort.by(order); 17+ 18+ return postRepository.findAll(sort); 19 } 20
この変更では、PostService クラスの findAll メソッドを修正しました。
変更点は以下の通りです。
-
メソッドシグネチャの変更:
- これまでは引数なしの
public List<Post> findAll()でしたが、ソート基準と並び順を受け取れるようにpublic List<Post> findAll(String sortBy, String sortOrder)に変更されました。sortByはソート対象のフィールド名(例: "createdAt", "title")を、sortOrderは並び順("asc": 昇順, "desc": 降順)を受け取ります。
- これまでは引数なしの
-
Sort.Orderオブジェクトの作成:Sort.Orderは、具体的なソートの条件(どのフィールドを、どちらの向きでソートするか)を定義するクラスです。if (sortOrder.equals("asc"))の条件で、受け取ったsortOrderが "asc"(昇順)であればSort.Direction.ASCを、そうでなければSort.Direction.DESC(降順) を使用してSort.Orderオブジェクトを生成しています。new Sort.Order(Sort.Direction.ASC, sortBy)のように、ソートの方向とソート基準となるフィールド名を指定します。
-
Sortオブジェクトの作成:Sort.by(order)を使って、作成したSort.Orderオブジェクトを元にSortオブジェクトを生成します。Sortオブジェクトは複数のソート条件をまとめることもできますが、今回は一つの条件のみを指定しています。
-
Repositoryメソッドの呼び出し:
- 最後に
postRepository.findAll(sort)を呼び出しています。JpaRepositoryのfindAllメソッドは、引数としてSortオブジェクトを受け取ることができ、このSortオブジェクトに基づいてデータベースからソートされたデータを取得します。
- 最後に
この修正により、サービス層で動的にソート条件を組み立て、それをリポジトリ層に渡すことで、ユーザーの指定に応じた並び順で投稿リストを取得できるようになりました。
変更点: ServiceのsearchPostsメソッドにソートロジックを追加
/src/main/java/com/example/bbs/service/PostService.java1 2- public List<Post> searchPosts(String keyword, String matchType) { 3+ public List<Post> searchPosts(String keyword, String matchType, String sortBy, String sortOrder) { 4+ // Sort.Orderを使ってソート順を設定 5+ Sort.Order order; 6+ if (sortOrder.equals("asc")) { 7+ order = new Sort.Order(Sort.Direction.ASC, sortBy); // 昇順 8+ } else { 9+ order = new Sort.Order(Sort.Direction.DESC, sortBy); // 降順 10+ } 11+ 12+ // Sortオブジェクトを作成 13+ Sort sort = Sort.by(order); 14+ 15+ // 検索条件に基づいて投稿を取得 16 switch (matchType) { 17 case "startswith": // 前方一致 18- return postRepository.findByTitleStartingWithOrContentStartingWith(keyword, keyword); 19+ return postRepository.findByTitleStartingWithOrContentStartingWith(keyword, keyword, sort); 20 case "endswith": // 後方一致 21- return postRepository.findByTitleEndingWithOrContentEndingWith(keyword, keyword); 22+ return postRepository.findByTitleEndingWithOrContentEndingWith(keyword, keyword, sort); 23 case "contains": // 部分一致(デフォルト) 24 default: 25- return postRepository.findByTitleContainingOrContentContaining(keyword, keyword); 26+ return postRepository.findByTitleContainingOrContentContaining(keyword, keyword, sort); 27 } 28 } 29
この変更では、PostService クラスの searchPosts メソッドも修正しました。
主な変更点は以下の通りです。
-
メソッドシグネチャの変更:
- これまでの
public List<Post> searchPosts(String keyword, String matchType)に加えて、String sortByとString sortOrderの引数が追加されました。これにより、検索と同時にソート条件も受け取れるようになります。
- これまでの
-
Sort.OrderおよびSortオブジェクトの作成:findAllメソッドと同様に、受け取ったsortByとsortOrderの値を使ってSort.Orderオブジェクトを生成し、そこからSortオブジェクトを作成しています。if (sortOrder.equals("asc"))の条件で昇順か降順かを判断し、適切なSort.Directionを指定します。
-
Repository検索メソッドの呼び出し:
switch文の中の各検索ケースで、postRepositoryの対応するメソッドを呼び出す際に、新しく作成したSortオブジェクトを引数として渡しています。- 例えば、
postRepository.findByTitleContainingOrContentContaining(keyword, keyword, sort);のように変更されました。
この修正により、検索機能とソート機能を組み合わせることが可能になりました。ユーザーが指定したキーワードで投稿を検索し、その検索結果をさらにユーザーが指定した基準と順序で並べ替えて表示できるようになります。
変更点: HTMLにソート基準選択用のドロップダウンを追加
/src/main/resources/templates/posts/list.html1 <!-- 検索フォーム --> 2 <form th:action="@{/posts}" method="get" class="mb-3"> 3 <div class="row"> 4- <div class="col-md-6"> 5+ <!-- 検索キーワード --> 6+ <div class="col-md-4"> 7 <input type="text" name="keyword" class="form-control" placeholder="Search" th:value="${param.keyword}" required> 8 </div> 9+ <!-- マッチタイプ --> 10 <div class="col-md-2"> 11 <select name="matchType" class="form-control"> 12 <option value="contains" th:selected="${param.matchType?.toString() == 'contains'}">部分一致</option> 13 <option value="endswith" th:selected="${param.matchType?.toString() == 'endswith'}">後方一致</option> 14 </select> 15 </div> 16+ <!-- ソート基準 --> 17+ <div class="col-md-2"> 18+ <select name="sortBy" class="form-control"> 19+ <option value="createdAt" th:selected="${param.sortBy?.toString() == 'createdAt'}">作成日</option> 20+ <option value="title" th:selected="${param.sortBy?.toString() == 'title'}">タイトル</option> 21+ </select> 22+ </div>
この変更では、掲示板一覧画面のHTMLファイル (posts/list.html) に、ユーザーがソート基準を選択するための新しいドロップダウンメニュー(<select> 要素)を追加しました。
-
<div class="col-md-2">: Bootstrapのグリッドシステムを利用して、このドロップダウンが画面上で占める横幅を定義しています。 -
<select name="sortBy" class="form-control">:name="sortBy": このselect要素で選択された値が、HTTPリクエストのsortByパラメータとして送信されます。このsortByという名前は、Controllerで定義した@RequestParam("sortBy")と一致している必要があります。class="form-control": Bootstrapのスタイルを適用し、見た目を整えています。
-
<option value="createdAt" th:selected="${param.sortBy?.toString() == 'createdAt'}">作成日</option>:value="createdAt": このオプションが選択されたときに、HTTPリクエストのsortByパラメータとして"createdAt"という値が送信されます。この値は、Postエンティティ(データベースのテーブル)のフィールド名と一致している必要があります。th:selected="${param.sortBy?.toString() == 'createdAt'}": これはThymeleafの機能で、もし現在のリクエストのsortByパラメータが"createdAt"であれば、このオプションが初期状態で選択された状態になります。これにより、ページを再読み込みしたり検索を実行したりしても、以前選択していたソート基準が保持されます。作成日: ユーザーに表示される選択肢のテキストです。
-
<option value="title" th:selected="${param.sortBy?.toString() == 'title'}">タイトル</option>:value="title": このオプションが選択されると"title"が送信されます。これもPostエンティティのフィールド名です。th:selectedは「作成日」のオプションと同様に、現在の選択状態を保持します。タイトル: ユーザーに表示される選択肢のテキストです。
このHTMLの追加により、ユーザーは「作成日」または「タイトル」をソートの基準として選択できるようになり、選択した値がバックエンドに送信されることで、動的なソートが可能になります。
変更点: HTMLに並び順選択用のドロップダウンを追加
/src/main/resources/templates/posts/list.html1+ <!-- 並び順 --> 2+ <div class="col-md-2"> 3+ <select name="sortOrder" class="form-control"> 4+ <option value="asc" th:selected="${param.sortOrder?.toString() == 'asc'}">昇順</option> 5+ <option value="desc" th:selected="${param.sortOrder?.toString() == 'desc'}">降順</option> 6+ </select> 7+ </div> 8+ <!-- 検索ボタン --> 9 <div class="col-md-2"> 10 <button type="submit" class="btn btn-primary">検索</button> 11 </div>
この変更では、掲示板一覧画面のHTMLファイル (posts/list.html) に、ユーザーが並び順(昇順か降順か)を選択するための新しいドロップダウンメニュー(<select> 要素)を追加しました。
-
<div class="col-md-2">: Bootstrapのグリッドシステムを利用して、このドロップダウンが画面上で占める横幅を定義しています。 -
<select name="sortOrder" class="form-control">:name="sortOrder": このselect要素で選択された値が、HTTPリクエストのsortOrderパラメータとして送信されます。このsortOrderという名前は、Controllerで定義した@RequestParam("sortOrder")と一致している必要があります。class="form-control": Bootstrapのスタイルを適用しています。
-
<option value="asc" th:selected="${param.sortOrder?.toString() == 'asc'}">昇順</option>:value="asc": このオプションが選択されたときに、HTTPリクエストのsortOrderパラメータとして"asc"という値が送信されます。この値は、Service層でSort.Direction.ASCと連携します。th:selected="${param.sortOrder?.toString() == 'asc'}": Thymeleafの機能で、もし現在のリクエストのsortOrderパラメータが"asc"であれば、このオプションが初期状態で選択された状態になります。昇順: ユーザーに表示される選択肢のテキストです。
-
<option value="desc" th:selected="${param.sortOrder?.toString() == 'desc'}">降順</option>:value="desc": このオプションが選択されたときに、HTTPリクエストのsortOrderパラメータとして"desc"という値が送信されます。この値は、Service層でSort.Direction.DESCと連携します。th:selectedは「昇順」のオプションと同様に、現在の選択状態を保持します。降順: ユーザーに表示される選択肢のテキストです。
このHTMLの追加により、ユーザーは「昇順」または「降順」を並び順として選択できるようになり、選択した値がバックエンドに送信されることで、動的なソートが可能になります。
変更点: HTML検索フォームのレイアウト調整
/src/main/resources/templates/posts/list.html1 <!-- 検索フォーム --> 2 <form th:action="@{/posts}" method="get" class="mb-3"> 3 <div class="row"> 4- <div class="col-md-6"> 5+ <!-- 検索キーワード --> 6+ <div class="col-md-4"> 7 <input type="text" name="keyword" class="form-control" placeholder="Search" th:value="${param.keyword}" required> 8 </div> 9+ <!-- マッチタイプ --> 10 <div class="col-md-2"> 11 <select name="matchType" class="form-control"> 12 <option value="contains" th:selected="${param.matchType?.toString() == 'contains'}">部分一致</option> 13 <option value="endswith" th:selected="${param.matchType?.toString() == 'endswith'}">後方一致</option> 14 </select> 15 </div> 16+ <!-- ソート基準 --> 17+ <div class="col-md-2"> 18+ <select name="sortBy" class="form-control"> 19+ <option value="createdAt" th:selected="${param.sortBy?.toString() == 'createdAt'}">作成日</option> 20+ <option value="title" th:selected="${param.sortBy?.toString() == 'title'}">タイトル</option> 21+ </select> 22+ </div> 23+ <!-- 並び順 --> 24+ <div class="col-md-2"> 25+ <select name="sortOrder" class="form-control"> 26+ <option value="asc" th:selected="${param.sortOrder?.toString() == 'asc'}">昇順</option> 27+ <option value="desc" th:selected="${param.sortOrder?.toString() == 'desc'}">降順</option> 28+ </select> 29+ </div> 30+ <!-- 検索ボタン --> 31 <div class="col-md-2"> 32 <button type="submit" class="btn btn-primary">検索</button> 33 </div> 34 </div> 35 </form> 36- 37 <h2>掲示板一覧</h2> 38 <table class="table table-striped"> 39 <thead>
この変更では、HTMLの検索フォームのレイアウトを調整しました。
-
検索キーワードの幅を変更:
- これまでの
<div class="col-md-6">(横幅の6/12)から<div class="col-md-4">(横幅の4/12)に変更しました。これにより、検索キーワードの入力欄が少し狭くなり、その分他の要素を横に並べるスペースを確保しています。
- これまでの
-
新しいソート関連の要素を追加:
- ソート基準 (
sortBy) と並び順 (sortOrder) を選択するドロップダウンメニューがそれぞれ<div class="col-md-2">で追加されました。これにより、検索キーワード、マッチタイプ、ソート基準、並び順、検索ボタンの各要素が横一列に並ぶようなレイアウトになります。
- ソート基準 (
Bootstrapのグリッドシステム (col-md-*) は、画面の横幅を12分割して要素の幅を指定するものです。今回の変更では、新しい要素を追加しつつ、全体のレイアウトが崩れないように各要素の幅を調整しています。これにより、フォームの見た目が整理され、使いやすさが向上しています。
おわりに
この記事では、Spring Bootアプリケーションに並び替え(ソート)機能を実装する一連の流れを学習しました。具体的には、Controllerでソート条件をリクエストパラメータとして受け取り、Service層でSpring Data JPAのSortオブジェクトを動的に生成しました。そして、Repository層のメソッドにこのSortオブジェクトを渡すことで、データベースから指定された基準と順序でデータを取得できることを確認しました。また、HTMLフォームにソート基準と並び順の選択肢を追加し、ユーザーが画面から直感的に操作できるUIの実装も行いました。これらの実装を通じて、検索機能と連携した柔軟なデータ表示の実現方法を体系的に学ぶことができました。