[初心者のJava] 検索機能を試す

ということで、Spring Data JPAの簡単な検索機能を試してみようと思います。

Spring Data JPAではレポジトリに命名規則に従った名前のメソッドを定義するだけでメソッド名に対応した検索メソッドが自動的に作られる機能が用意されています。

これまで作ってきた試合一覧で節番号を指定すると指定された節の試合情報が表示されるようにしてみます。

まず、レポジトリに節番号で試合を検索するメソッドを定義します。

節番号に対応するエンティティのプロパティ (データベースのカラム) はsectionですので、findBySectionというようにfindByの後にプロパティを先頭大文字でつなげた名前のメソッドを定義することで、節番号を指定した検索を行うメソッドが利用できるようになります。

sectionプロパティの型はintですので、検索キーとしてint型の引数を指定し、戻り値はエンティティ型のListになります。

ということでレポジトリのコードは以下のようになります。

@Repository
public interface MatchRepository extends JpaRepository<Match, Integer> {
  public List<Match> findBySection(int section);

}

このようにメソッドを定義するだけでメソッド名に対応した実装をSpring Data JPAプラットフォームが用意してくれるので、基本的な検索ではSQLに関する知識は必要ありません。

次に試合一覧表示画面 matches.html に節番号を指定するフォームを追加します。

<form action="/matches" method="get">
  <select name="section">
    <option value="">すべての節</option>

    <option th:each="sec: ${#numbers.sequence(1, 34)}" th:value="${sec}" 

      th:text="|第${sec}節|">

    </option>

  </select>

  <input type="submit" value="表示">

</form>

節指定試合一覧に専用のURLを用意してもいいのですが、今回は全試合一覧と同じ /matches に対するGET要求で節指定の試合一覧も表示できるようにし、sectionパラメータで表示する節を指定できるようにしています。

節を指定するselect要素には#numbers.sequenceを使用して1から34までの数字を選択できるようにすることと合わせて、すべての節の試合を表示するための選択肢を設けて、すべての節が選択された際には空文字列 "" をsectionパラメータに設定するようにしました。

最後に/matchesを処理するコントローラのコードを作成します。

最初に作成した/matchesを処理するメソッドにsectionパラメータを受け取るための引数を用意します。

@GetMapping(value = "/matches")
public String matches(
    @RequestParam(name = "section", defaultValue = "-1") int section,

    Model model) {

  List<Match> list;

  if (section >= 1 && section <= 34) {

    list = matchRepository.findBySection(section);

  } else {

    list = matchRepository.findAll();

  }

  model.addAttribute("matches", list);

  return "matches";

}

@RequestParam(name="section", defaultValue="-1") int sectionが今回追加した部分で、@RequestParamアノテーションのname="section"属性でsectionパラメータをint sectionに設定することを指定しています。

ただし、/matchesはリクエストパラメータなしでアクセスされる場合や、先のHTMLファイルに記述したようにsectionの値が空文字列となっている場合もありますので、sectionパラメータがない場合、およびsectionパラメータの値が整数値として不正な場合には-1を引数に設定するようdefaultValue="-1"という属性を合わせて指定しています。

メソッド本体のif (section >= 1 && section <= 34)でsectionパラメータの値が所定の範囲内かを判断し、範囲内であれば先ほどレポジトリに作成したfindBySectionメソッドを呼ぶことで指定された節の試合のみを一覧表示することができます。

以上で検索の追加は終わりですが、これだけでは少し簡単すぎるので、特定の月の試合の一覧と特定チームの試合一覧も作ってみます。

まず、チームごとに試合一覧ですが、こちらは簡単でホームチームが特定チームかまたはアウェイチームが特定チームかを判断すればいいので、レポジトリに作成するメソッドはList<Match> findByHomeOrAway(String home, String away)となります。

この検索は論理和「または」の検索になるので、エンティティフィールドを指定するHomeとAwayの間にOrをはさんでカラムhomeが一致するかまたはawayが一致するレコードを取得する指定となっています。

月ごとの検索ですが、日付の月だけを取り出して検索する機能は用意されていないので、月の1日と最終日を使ってその間の日を検索します。

これに使うメソッドはList <Match> findByDateBetween(LocalDate from, LocalDate to)となります。

Betweenはその前で指定したカラムの値が2個の引数の値の間に包括的に含まれるレコードを取得する設定となります。

また、月の最初の日はLocalDate.of(2022, month, 1)で取得でき、月の最終日はLocalDate.of(2022, month, 1).with(TemporalAdjusters.LastDayOfMonth())で取得します。

これらを実装したコード例を以下に示します。

まず、レポジトリのコードです。

@Repository
public interface MatchRepository extends JpaRepository<Match, Integer> {
  public List<Match> findBySection(int section);

  public List<Match> findByDateBetween(LocalDate from, LocalDate to);

  public List<Match> findByHomeOrAway(String home, String away);

}

MatchRepository.javaはすでに説明した通りで、日付範囲で検索するfindByDateBetweenとfindByHomeOrAwayを追加しています。

@Repository
public interface TeamRepository extends JpaRepository<Team, Integer> {
  List<Team> findByAbbr(String abbr);

}

チーム名テーブルを扱うTeamRepositoryにはチーム略称 abbr で検索を行うfindByAbbrを用意しています。

次にコントローラのコードを示します。

@GetMapping(value = "/matches")
public String matches(
    @RequestParam(name = "section", defaultValue = "-1") int section,

    @RequestParam(name = "month", defaultValue = "-1") int month,

    @RequestParam(name = "team", defaultValue = "") String team,

    Model model) {

  List<Match> list;

  List<Team> targetTeams = teamRepository.findByAbbr(team);

  if (section >= 1 && section <= 34) {

    list = matchRepository.findBySection(section);

  } else if (month >= 1 && month <= 12) {

    list = matchRepository.findByDateBetween(LocalDate.of(2022, month, 1), 

      LocalDate.of(2022, month, 1).with(TemporalAdjusters.lastDayOfMonth()));

  } else if (targetTeams != null && targetTeams.size() > 0) {

    list = matchRepository.findByHomeOrAway(team, team);

  } else {

    list = matchRepository.findAll();

  }

  model.addAttribute("matches", list);

  List<Team> teams = teamRepository.findAll();

  model.addAttribute("teams", teams);

  return "matches";

}

まず、メソッド引数は先のsectionに加えて月を指定するmonthとチームを指定するteamを追加しています。

monthは数値なのでdefaultValue="-1"を、teamは文字列なのでdefaultValue=""を指定しています。

メソッド本体ではmatchRepository.findByAbbrで引数として指定されたチーム略称に一致するチームを検索しています。チーム略称が正当なものであればtargetTeamsにエンティティが設定され、teamパラメータが省略された場合の場合にはtargetTeamsには空のリストが返されるので、パラメータの正当性チェックと省略時対応が可能となります。

以降の処理はすでに説明した通りですので、省略します。

なお、コントローラを終了する前にteamRepository.findAll()でチーム一覧を取得していますが、これはテンプレートのチーム選択プルダウンで使用するためのものです。

最後にHTMLファイルの関連する部分を示します。

<a href="/matches">全試合一覧</a><br>
<form action="/matches" method="get">
  <select name="section">

    <option value="">すべての節</option>

    <option th:each="sec: ${#numbers.sequence(1, 34)}" th:value="${sec}" 

      th:text="|第${sec}節|">

    </option>

  </select>

  <input type="submit" value="表示">

</form>

<form action="/matches" method="get">

  <select name="month">

    <option value="">すべての月</option>

    <option th:each="month: ${#numbers.sequence(1, 12)}" 

      th:value="${month}" th:text="|${month}月|">

    </option>

  </select>

  <input type="submit" value="表示">

</form>

<form action="/matches" method="get">

  <select name="team">

    <option value="">すべてのチーム</option>

    <option th:each="team: ${teams}" th:value="${team.abbr}" th:text="${team.abbr}">

    </option>

  </select>

  <input type="submit" value="表示">

</form>

<a href="/edit_matches">試合一覧編集</a>

こちらもほぼこれまで説明してきた内容ですので、特に説明はありません。

以上で、エラーチェック等はほぼやっていませんが、とりあえずは動くものが作れました。

作成したコードはGitHubに保存していますので、興味のある方はご覧ください。




0コメント

  • 1000 / 1000