JPA/Hibernateは捨てよう
(stemlaur.com)要約
JPA/Hibernateは、JavaコードでもはやSQLを書く必要がないという理由から広く使われるフレームワークになりました。しかし、私は新しいプロジェクトではこれを使うべきではないと主張したいと思います。
理由
非常に長い公式ドキュメント
公式ドキュメントをPDFにすると、なんと406ページにもなり、『指輪物語』(231ページ)やSQL標準文書(288ページ)よりも長いのです。データベースクエリを身につけるために修士課程へ進む必要はありません。
可変性
- あるエンティティが1つの要素を必要とするとしても、引数なしコンストラクタを強制されます。
- エンティティクラスに
final、abstractキーワードを付けても、継承を防ぐことはできません。 - Reflection/Introspectionは、OOPのカプセル化原則を無視します。
- 誰かが悪意あるコードを仕込めば、データを完全に消し去ることができます。
遅延ローディングとキャッシュ
@Lazyアノテーションは、初心者にとって最悪の技術です。しかし、ドメイン設計がHibernateに合わない、あるいはクエリを書けない場合には、これを避けられません。- キャッシュの仕組みは理解しにくいです。しかも理解したとしても、クエリ結果ではなくエンティティそのものをキャッシュに保存しなければなりません。
メモリとデータベースの同期(Flush)
Flushという技術は、メモリに保存されたオブジェクトとデータベースを同期します。これは2つの問題を引き起こします。
- Flushが動くと、メモリ内での編集が確定してしまうため、Hibernate以外の永続化ツールは考えようもなくなり、
- Flush中に競合が発生すると、コードとは無関係なStack Traceエラーが発生することがあります。
1つのテーブルから特定のカラムだけ取得する
あるエンティティの1カラムだけにアクセスしたいなら、SQLのやり方は次のように単純です。
select url from image
where id = 'F462E8D9-9DF7-4A58-9112-EDE0434B4ACE';
しかしHibernateは、必ずエンティティの全カラムを取得します。これを避けるには複雑な手順を踏まなければなりません。
カラムの制約(Constraint)の定義
あるカラムの制約を定義するために、以下のように複数のアノテーションを付ける必要があります。
...
@NotNull
@NotEmpty
@Email
private String email;
...
この方式は、次のような問題を引き起こします。
- この条件に対して単体テストができません。
- Flush処理をしている最中では、この処理過程を把握するには遅すぎます。
- 発生しうる例外は一般的で、役に立ちません。
- ビジネスルールを技術的なルールとしてしか扱いません。
戦略上の問題
- フレームワークの更新は最悪で、後方互換性を無視し、無条件の依存を生みます。これを作る会社は、自社フレームワークを使うのが当然だと思い込ませ、独占しようとします。この悪循環は止めなければなりません。
- 概念実証(Proof of Concept)をわざわざフレームワーク経由でしか得ようとしないのは、自分の視野を狭める結果しか生まず、特にJPA/Hibernateではなおさらです。ほんの少しも許容すべきではありません。
どうすればよいでしょうか?
SQLを書きましょう
SQLだけであらゆることが可能です。すべてのプログラマが知っていて、クエリは直感的で、フレームワークも必要ありません。
管理者がHibernateを使えと言うのですが?
退職するか、コードをフレームワークから分離する作業をしましょう。
すでに使っているなら…
- デフォルトコンストラクタとセッターの公開レベルを
publicにしないでください。 - SQLが生成するIDの代わりに、UUIDのような文字列を使ってください。
XXXRepositoryという名前ではなく、XXXDaoという名前を使ってください。@SequenceGeneratorアノテーションを使わないでください。- ドメインクラスとDAOクラスを
interfaceで分離してください。 - *多対多の関係(
@OneToManyなど)は使わず、いっそエンティティマッピングを避けてください。
結論
JPA/Hibernateは捨てましょう。
- ビジネス上の問題を解決するための、より短くより良いドキュメントがあります。
- 設計を速すぎるやり方だけに固執しないでください。
- あなたのコードを保守する次の開発者に寛容さを示してください。
皆さんは何を使っていますか?
- JPA/Hibernate
- 別のORM技術
22件のコメント
JPA/Hibernateを捨てようという意見には反対です。
「非常に長い公式ドキュメント」の部分
SQLも最初に学ぶときは難しいものです。複雑なJOIN、サブクエリ、プロシージャ関数などを完璧に理解するのは簡単でしょうか?
JPAは最初に中核となる概念だけ理解して始めても十分です。より深い内容は必要になったときに調べればよいです。
そしてLLMもあります。
「可変性とReflectionの問題」
これはフレームワークの動作方式を理解していないことから来る懸念です。
実務でこれによる実質的な問題が発生するケースはほとんどありません。
むしろReflectionによってオブジェクトマッピングが自動化され、生産性が大きく向上します。
「遅延ローディングおよびキャッシュ」
@Lazyが「最悪の技術」だと? N+1問題を解決し、性能を最適化するのに非常に有用な機能です。
キャッシュメカニズムはむしろ性能向上に大いに役立ちます。
「1つのテーブルの特定カラムだけを取得する」
JPQLやProjectionを使えば、必要なカラムだけを簡単に照会できます。
そしてQueryDSLと一緒に使えばよいです。
ORMの目的はSQLを完全に置き換えることではなく、開発者がビジネスロジックにより集中できるよう支援することだと思います..
私はORM懐疑派ではあるものの、十分な代案は提示できていないように思います。
ORMを多用する方向に進むと、本当にきりがなくなり、上で言及されているように、SQLのドキュメントよりもさらに広い範囲のドキュメントの中でもがいた末に干からびてしまうかもしれません。
最近は個人プロジェクトでORMを使わずに開発しているのですが、再利用性を考慮した設計をしていると、結局は自分でORMを作っているような方向に開発が進んでしまうこともあるんですよね。笑
フレームワークを使うことで、同じフレームワークを利用する他の開発者たちと共通のパラダイムを持てるという点は、こうした「何かを使うな」という類いの記事では、いつも無視されているように思います。
テーブル数が多く、カラムも多い場合は(たとえばテーブルが50個、各テーブルに100以上のカラムがあるような場合)、SQLをそのまま書くと本当に地獄絵図になるんですよね。
ただ、小さなサービスを作るときにJPA/Hibernateを使うのは、かなりの無駄だと思います。
やはりこういう意見はケースバイケースな気がします。
(ここで例に挙げられているのも、カラムが3〜4個のものですし……)
上の記事の最後の質問は、少し修正したほうがよさそうです。
Java界隈では、1. ORM vs 2. Non-ORM として整理できるでしょう。
1、2 のどちらにも長所と短所がはっきりあるため、上の記事のように極端な結論を出すのは適切ではありません。
私たちの場合、
ORM である JPA/Hibernate/QueryDSL を使いながら、同時に MyBatis も使っています。
ORM を使ってできるだけ生産性を高めつつ、
ORM ではカバーしにくいクエリには MyBatis を使います。
そして上で 1、2 のどちらを選ぶにしても、SQL はしっかり理解しておく必要があります。
私も修正したいのですが、サイトにそういう機能がないので…
そもそもORMが流行するようになった理由を見ないふりしている気がします。
学習コストは多少かかりますが、慣れれば生産性が向上するのは確かですよね。
SQLは簡単そうに見えても、SQLを一つひとつ手で書くときのあの疲れは……。しかもテーブルが変更されると関連クエリも全部一つひとつ直さなければならないので、SQLの保守も決して楽ではありません。小さくてシンプルな分だけ作業量が増えるので、だからこそ生産性の話がずっとついて回るわけです。
さらに、SQLで発生するエラーはランタイムで表面化するので捕まえにくいですし、SQLインジェクションのような攻撃への防御を一つひとつやっていくと、結局クエリを生成するコードが追加されていって(たいていは簡単なテンプレートのような形で始めて……)、進めていくうちに結局ORMっぽいものがまた出てくるはずです。だったら最初からORMを使うほうがいいのでは……?
数日前に上がっていた記事を思い出しますね。
https://ja.news.hada.io/topic?id=17955
同意します。
ORMを使う理由やその利点を十分に理解していないケースが、しばしばあるように思います。
加えて、ORMを通じて実際に実行されるSQLを分析したり理解しようとする人も、あまり多くないようです。
単なる利便性を超えて、SQLの最適化やデータベースの動作を深く理解する助けにもなるという点が、もっと知られるとよいと思います。
使うべき、使ってはいけないといった両極端に偏る必要はないと思います(笑)
私は生産性が必要ならORMを活用しつつ、
ORMでカバーできない複雑なクエリ、あるいはさらに最適化が必要なクエリは生のクエリで処理します。
ORMかraw queryかは、どんな状況で何をどう作るのかに応じて適切に選べばいいと思います。
一般的に、DBの正規化がしっかりできていて、大きな join をする必要のないデータなら、そういう考え方もありだと思いますが、
DBの正規化の段階から始めて、すべてをきちんと DBA を通じて管理できないのであれば、ORM も良い選択肢になり得ると思います。特に、join を通じて取得する側で relationship として扱うことで生まれる利点は、なぜ ORM を使うのかを示す非常に良い例だと思います。
もちろん、フレームワークが開発者の成長を制限することや、そのためにフレームワークへの依存度を下げようという意見には同意しますが、
無条件に ORM を使わないようにしようという意見には、そう簡単には同意できません。
すべての企業に DBA がいて、DDD や TDD のようなきちんとした方法論で開発されている、という前提を置いているように思えるので、
実際の現場であのように進めたら、コードがどれだけひどいことになるのか分かりません。
Pythonでバックエンドを作るとき、毎回使うのはSQLAlchemyやDjango ORMあたりです。
SQLを直接書くのもORMを使うのも、慣れてしまうと大きな違いはないように感じていて、あまり深く考えていませんでした。ORMを使わないほうがいい、という意見もあったんですね。
フレームワークへの依存度を下げようという意見には賛成です。Django ORMしか使えず、SQLを扱えないという状態ではいけませんよね……
うーん、私は反対です。現在、テーブルが3,000個ほどあるサービスを運用していますが、ドメインがあまりにも複雑なので、クエリを1つ作るだけでも基本的に数十行は書くことになっていました。ここで動的クエリまで考えると、本当に頭が痛くなります。複雑なだけにバグも多く発生し、保守も難しかったです。私は、複雑なドメインでは ORM のほうがより有利だと思います。
私の場合、正規化されていないDBを保守した経験があるのですが、
そのとき動的クエリをORMを使わずに通常のSQLで書いていたため、
かえってコードが読みづらくなってしまうケースがありました。
複雑なドメインだけでなく、正規化が不十分なドメインでも十分に導入する余地はあると思います。
おお、悪く受け取る必要はなさそうですね
私も個人的には、SQLをそのまま使うことを勧めたいです。JS界隈ではPrismaのようなツールがよく使われていますが、SQLはそこまで難しい言語でもありませんし、データベースI/Oのためにあまりにも不要な抽象化を求めるように感じて、少し敬遠してしまいます。
js/ts 系の ORM にはやけに完成度の低い製品が多いので、そのせいもある気がします
Jdbcみたいなもので十分なんでしょうか? 以前一緒に働いていた方が「JPAは遅いから別のものを使え」と言っていたのを思い出しました。
伝説の中の話みたいです
フレームワークではなく基本に立ち返るのがトレンドなのかなと思ったりします。
HTMX、SQL などなど..
車輪の再発明をしなければならないという欠点はありますがね
利点もありますよね..
2. MyBatis(ORMではありませんが、まあ)
ORMではなくDAOに変えるべきでしたね