革命のブログ

フレボワークスの社員がブログを通じて情報発信します。

結局、JPAがいいという話

どうも、小野です。

今回は数あるORマッパーの中で個人的に、今までで一番使ってきたであろうJPA(Java Persistence API)の特徴、そしてメリット、デメリットについてご紹介します。 本記事ではJPAの詳しい使い方は説明しません。

JPAとは

Java標準技術の1つ(JSR338: Java Persistence API)です。

JPAは仕様、つまりインターフェースのみ定義されており、実装自体は「Hibernate」や「EclipseLink」などを利用することになります。 HibernateとEclipseLinkはJPAの仕様に従って実装はされていますが、それぞれ独自実装もあったりするので、どちらがいいかは公式リファレンスを参照したり、実際に使ってみることをお勧めします。

JPAの特徴

SQLを意識せずに、Javaオブジェクトでデータを操作することが可能です。もちろん、全てJavaオブジェクトで完結することはなく、SQLを書くことはあります。 JPAでDB操作する際にはいくつか方法があります。

  • SQL
  • JPQL
  • Criteria API

SQL

今まで使ってきたSQLです。これは説明する必要がありませんね。SQLは、Javaクラス内にも書けるし、外部ファイル(xml)にも書けるので、プロジェクトのルールに従って柔軟に対応が可能です。

JPQL

SQLに似ているJPA独自の言語です。 例えば、以下のようなSQLがあったとします。

SELECT u.id, u.name,u.email FROM user u

JPQLで表現するにはデータをマッピングするクラスが必要になります。

@Entity
public class User {
    private String id;
    private String name;
    private String email;
}

@Entityはマッピングする際に最低限必要です。 JPQLで表現すると以下のようになります。

SELECT u FROM User u

FROM句で指定されているのはテーブル名ではなく、クラス名になります。SELECT句にはクラス名のエイリアスを指定すると、全カラムを取得するようになります。もし取得する列を指定したい場合は、SQLの時と同じ形で指定します。

SELECT u.name FROM User u

別にSQLで書けるならJPQL覚える必要ないと思いますが、テーブル結合のマッピングなど実現が難しいことがあるため習得は必須です。

Criteria API

JavaプログラムでSQLを構築、実行するためのAPIです。

JPQL同様にCriteria APIを使って書いてみます。Userクラスはそのまま利用します。

// entityManagerは予めインスタンスを生成しておく必要がありますが、ここでは割愛します。
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> root = query.from(User.class);
List<User> users = entityManager.createQuery(query.select(root)).getResultList();

Criteria APIのメリットはタイプセーフであることです。SQL、JPQLはタイプミスしていてもコンパイルエラーにはなりませんが、Criteria APIの場合、実装時にエラーを検知することが可能です。 あとは、動的SQLを書く際に利用することが多いです。MybatisのようにSQL内に制御構文を書けないので、SQLやJPQLで書く際はプログラム内で文字列結合や制御構文を駆使する必要が出てきます。

メリット

実装の削減

SQLやマッピングの実装を極力なくすことにより、生産性向上やバグを減らすことが期待できます。 INSERT、UPADTE、DELETEはほぼ書く必要がなく、EntityManagerとエンティティクラスでの操作が可能です。

INSERT

User user = new User();
user.setId("1");
user.setName("太郎")
user.setEmail("taro@abc.com");

entityManager.persist(user);

UPADTE

User user = entityManager.find(User.class, "1");
user.setName("次郎")
user.setEmail("jiro@abc.com");

DELETE

User user = entityManager.find(User.class, "1");
entityManager.remove(user);

※前提としてトランザクション内での操作になります。

デメリット

複雑なSQLへの対応

複雑なSQLを書く場合にひと手間が必要になることがあります。 JPQLやCriteria APIで書こうとすると、実現が難しいかできても可読性が悪いコードになってしまいがちです。そのため、チューニングが必要なものは生のSQLで書く必要があります。 ただし、生のSQLの場合、N対1や1対Nなどのテーブル結合のマッピングができないので、自分でマッピング処理を実装することになります。

実装の流れとしては、 SQLの実行結果を保持するクラスを用意し、データ取得後、各テーブルに対応したクラスへマッピングを行います。

実行SQLがブラックボックス

特にJPQLやCriteria APIを利用する場合、頭の中でこんなSQLが実行されるだろうなと思っていても実際は全く違うSQLが実行されていることも少なくありません。想定外のSQL実行を回避するために開発時は実行されたSQLをログに出力するようにしてください。上記でも触れた1対Nのテーブル結合のあるクエリを実行した際に、N+1問題にぶつかることがあります。それによりシステムのパフォーマンスが落ちたり、最悪の場合停止することも考えられます。

N+1問題については、以下のサイトを参考にしてください。

qiita.com

さいごに

JPAは学習コストが高いと言われています。実際使ってみた私からみてもやはりそのように感じます。それも当然です。書き方が3つもあり、実際に実行されるSQLを意識してないと痛い目見ますので。正直、CriteriaAPIだけ覚えればJPQLは覚えなくても問題ありません。基本的にJPQLにできてCriteriaAPIにできないことはないです。

個人的には3パターンの実装手段があることで、ケースに応じて使い分けることができるのがメリットかなと思います。

この記事が選定時のネタとして参考になれば幸いです。