Java開発3年目くらいの時、新しい現場でJPAってなんだよってなったのでその時に書いていた記事の再掲です。
JPAって誰?
今回取り上げているJPAっていうのは、
「Java Persistence (JSR 338)」という、
Java EE標準のO/R MappingおよびDAOの技術仕様らしいです。
これだけじゃピンとこないので噛み砕くと、
Java EEというのは、Javaでの開発に必要なものをセットにしたもので、
必要な物っていうのは、便利なAPIなどのことです。
(ざっくりとしたJava EE概要知りたい人用の参考。https://agency-star.co.jp/column/java-ee))
そして今回取り上げるJPAはその便利なセットの中の一つで、
簡単に言うと、少ない記述量でDBにアクセスしてくれるもののことです。
この記事では、ここがわかってたらJPAの実装なんとなくわかる。
というところを目標にしていきます。
ちなみにJPAとよく一緒に使われるJavaのSpringBootを使っている前提で書いていきます。
JPAとの向き合い方
JPAを利用する時に用意するクラスは以下の4つです。
- エンティティクラス
- サービスクラス
- リポジトリクラス
- コントローラークラス
この4つのクラスが連携し合って、簡単にDBにアクセスすることを実現してくれます。
それぞれ以下で解説します。
エンティティクラス
DBから取得したデータを格納するクラス。
実際にはこいつを永続化しているだけでいい感じにテーブルとして機能してくれます。
難しいことを考えたくない人は、このクラスがテーブルになるとだけ覚えておきましょう。
-
@Entity
をつけて定義する。 -
主キーには
@Id
をつける。 -
@Table
をクラスに付けてテーブル名を指定できる。 -
@Column
をフィールドに付けてカラム名を指定できる(結合用カラムの名前を明示的につけたい場合は@JoinColumn
)
(@GeneratedValue
とか複合キーとかの扱いは今回はやめておきます。)
JPA には原則として空のコンストラクタが必要になるらしいです。(サンプルはなくても動くので今回は割愛)
// 以下全てimport省略
@Entity
@Table(name = "JPA_EXAMPLE") // これはテーブル名を指定
public *class* JpaExampleEntity {
// ここがテーブルの値としての定義になっている
@Id
@Column(name = "EXAMPLE_ID") // カラム名を指定
private Integer id; // こいつが主キー
@Column(name = "VALUE", nullable = true) // カラム名とnull制約を指定
private String value;
// 以下アクセサ
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setValue(String s) {
this.value = s;
}
public String getValue() {
return value;
}
}
リポジトリクラス
DBにアクセスするクラス。
@Repository
をつけて、かつJpaRepositoryを継承する必要がある。
このJpaRepository
を継承することで、
決まったメソッドを呼び出せばいい感じに、INSERTやらDELETEやらをやってくれる。
JpaRepository
のジェネリクス(JpaRepository<T>の<T>
の部分のこと)には、対象となるエンティティクラスと、その主キーの型を指定する。
@Repository
public interface JpaExampleRepository extends JpaRepository<JpaExampleEntity, Integer> {
}
JpaRepositoryにはさっき軽く触れた決まったメソッドを持っていて、
データをINSERT/UPDATEするsaveメソッド(データがすでにあるならUPDATE、それ以外ならINSERT)、
取得するfindメソッド、
削除するdeleteメソッドがあります。
呼び出すだけなので超便利です。
上に書いたfindメソッドは呼び出すだけで全部持ってきてくれますが、
特定のカラムを条件としてデータを取得したい時もあると思います。
そんな時でも安心な方法ももちろんあります。
メソッド名を「findBy」で始め、
エンティティクラス内のプロパティ名(=フィールド名)を付けると、
そのプロパティ(カラム)を条件とする検索にできます。
public interface JpaExampleRepository extends JpaRepository<JpaExampleEntity, Integer> {
// valueの値で検索して、ヒットしたものをリストに詰めて返してくれる
public List<JpaExampleEntity> findByValue(String value);
}
検索結果が0の場合は、空のリストが返ってくる感じです。
あと、これは覚える必要のない情報ですが、
実行時にはSimpleJpaRepositoryというクラスが使われているらしいです。
(JpaRepositoryはインターフェースだし、@Repositoryで定義するものもインターフェースですしね)
リポジトリクラスに関してだと、
実際の開発現場では、このリポジトリクラスに実装を書くのではなく、
JpaExampleRepositoryImplみたいなクラスを用意し、
別の場所で実装を定義して、サービスクラスから見えなくすることで隠蔽していたりします。
サービスクラス
上記のリポジトリクラスを使ってDBアクセスの業務的な実装を行う。
@Serviceをつけてクラスを定義。
また、リポジトリクラスを呼び出すメソッドには、@Transactionalをつけて、トランザクション管理をする。
(トランザクションって何って人はこちらhttps://wa3.i-3-i.info/word142.html)
トランザクション管理されたメソッドでは、
非チェック例外(RuntimeException系)がスローされるとロールバックされます。
ちなみにこれもリポジトリクラスと同じで、実際の現場では、JpaExampleServiceImplみたいな感じで隠蔽していたりすることもあります。
当時の私には初見殺しでした。
コントローラークラス
これがクライアントからのリクエストとかを受け付けて、対象のサービスクラスを呼び出してくれる。
@RestControllerをつけてクラスを定義する。
@RequestMapping()を駆使して、HTTP リクエストを受け付ける。
@RequestBodyはリクエストのボディを指定したオブジェクトにマッピングしてくれる。
(今回は@RequestBodyを使って、リクエストボディにデータがある想定でエンティティを取ってきていますが、実際のプロジェクトによってこの箇所の実装内容は多岐に渡ると思います)
@RestController
@RequestMapping("example")
public *class* JpaExampleController {
@Autowired
JpaExampleService jpaExampleService;
@RequestMapping(method = RequestMethod.POST) // POSTの時はこっち。他にも個別でパス指定とかもできる
void save(@RequestBody JpaExampleEntity jpaExampleEntity) {
jpaExampleService.save(jpaExampleEntity);
}
@RequestMapping(method = RequestMethod.GET) // GETの時はこっち
// うんちゃらかんちゃら
}
}
以上でざっくりとしたJPAの使われ方はこんな感じです。
機能としては他にもたくさんありますが、ソースを追いかける基礎知識としてはこれくらいあればあとは適宜でいけそうだと思います。
ぜひ参考にしてみてください。
以下参考にしたサイト
http://terasolunaorg.github.io/guideline/public_review/Overview/ApplicationLayering.html
https://www.ne.jp/asahi/hishidama/home/tech/java/spring/boot/jpa/example.html
https://medium-company.com/spring-boot-jpa-物理削除/