こんにちは、かつコーチです。
前回はEntityの定義方法を解説しましたが、Entityを作っただけではデータの読み書きはできません。
実際にデータベースを操作するにはRepositoryというインターフェースを使います。
「インターフェースなのに実装を書かなくていいの?」と驚く人が多いポイントなので、今回はその仕組みを丁寧に見ていきます。
Repositoryインターフェースとは
JpaRepositoryを継承するだけで動く仕組み
Repositoryとは、Entityに対するデータベース操作(検索・保存・削除など)をまとめたインターフェースです。
Spring Data JPAでは、JpaRepositoryというインターフェースを継承するだけで、基本的なCRUD操作が自動的に使えるようになります。
package com.example.demo.repository;
import com.example.demo.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {
}
たったこれだけの記述で、save()・findById()・findAll()・delete()といったメソッドが使えるようになります。
JpaRepository<Book, Long>のBookは対象のEntity、Longは主キーの型を表します。
なぜ実装クラスを書かなくていいのか
Spring Data JPAは、アプリケーション起動時にこのインターフェースを見つけ、内部で自動的に実装クラス(プロキシ)を生成します。
プロキシとは、本来の処理の前後に別の処理を挟み込むための代理オブジェクトのことで、Spring Data JPAではメソッド名やアノテーションからSQLを組み立てる役割を担っています。
私たちはインターフェースを定義するだけで、実装の中身を意識せずにデータベース操作を呼び出せる、というのがSpring Data JPAの大きなメリットです。
Repositoryの使い方
Serviceクラスからの呼び出し
RepositoryはService層から@Autowired(またはコンストラクタインジェクション)で受け取って使います。
package com.example.demo.service;
import com.example.demo.entity.Book;
import com.example.demo.repository.BookRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public List<Book> findAllBooks() {
return bookRepository.findAll();
}
public Book findBookById(Long id) {
return bookRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("該当する書籍が見つかりません: " + id));
}
}
findById()の戻り値はOptional<Book>である点に注意してください。
データが存在しない可能性があるため、Optionalでラップされて返ってきます。
標準で使える主なメソッド
| メソッド | 内容 |
|---|---|
save(entity) | 新規保存・更新(主キーの有無で自動判定) |
findById(id) | 主キーで1件検索(Optionalで返る) |
findAll() | 全件検索 |
deleteById(id) | 主キーで1件削除 |
count() | 件数取得 |
existsById(id) | 存在確認 |
つまずきやすいポイント
Optionalを無視してNullPointerException
❌ Before:Optionalをそのまま使ってしまう
Book book = bookRepository.findById(id).get();
// idが存在しない場合、NoSuchElementExceptionが発生
.get()を無条件で呼ぶと、データが存在しない場合に例外が発生し、エラーメッセージだけでは原因が分かりにくいスタックトレースになります。
筆者も検証環境でIDを打ち間違えて、このNoSuchElementExceptionの意味が分からず調査に時間を使ってしまった経験があります。
✅ After:orElseThrowで意図を明確にする
Book book = bookRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("該当する書籍が見つかりません: " + id));
こうしておくと、どのIDで何が起きたのかが例外メッセージから一目で分かり、後続のエラーハンドリングにも繋げやすくなります。
応用・一歩先の使い方
継承できるRepositoryの種類
JpaRepository以外にも、用途に応じたRepositoryが用意されています。
CrudRepository:基本のCRUDのみ(JpaRepositoryはこれを拡張したもの)PagingAndSortingRepository:ページネーション・ソート機能を追加JpaRepository:JPA固有の機能(一括保存など)も含めた最上位インターフェース
基本的には最も機能が豊富なJpaRepositoryを継承しておけば、後から機能が足りなくなることは少ないです。
まとめ
この記事のポイント
- Repositoryは
JpaRepositoryを継承するだけで基本的なCRUDが使えるようになる - 実装クラスはSpring Data JPAが自動生成するため、書く必要がない
findById()の戻り値はOptionalなので、orElseThrow()などで安全に扱う- 用途に応じて
CrudRepositoryやPagingAndSortingRepositoryも選べる
次に読むべき記事
- Spring Data JPAの基本とEntityの定義
- CRUD操作の実装
- クエリメソッドの命名規則
タグ: Spring Boot, 初心者向け, データベース