JPAとJDBC、あなたは正しく使い分けできていますか?
「Spring Bootのプロジェクトで、データベース操作をどう実装すればいいのかわからない…」 「JPAを使ってはいるけど、内部で何をしているのかイマイチ理解できていない…」
そんな悩みを抱えていませんか?
Spring Bootでは、データベースアクセスにおいて「JPA(Java Persistence API)」と「JDBC(Java Database Connectivity)」の2つが主に使われます。しかし、それぞれの特徴や用途を把握せずに使ってしまうと、後から保守性やパフォーマンスで苦労することも。
この記事では、JPAとJDBCの違い、使いどころ、JOINの使い方、nativeQueryの判断基準までを、実務目線で丁寧に解説します。初心者〜中級者の方が、今後のプロジェクトで迷わず選べるようになることがゴールです。
JPAとJDBCの基本と構造の違いを理解しよう
JPAとは?
JPAは、Javaの標準的なORM(Object Relational Mapping)仕様です。オブジェクトとDBのテーブルを1対1でマッピングすることで、SQLを直接書かずにデータ操作が可能になります。
@Entity
public class User {
@Id
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
}
User user = new User();
user.setName("Taro");
userRepository.save(user);
➡ JPAの主な利点:
-
SQLを書かずに開発できる
-
メンテナンス性が高い
-
複雑なリレーションを扱いやすい(JOINも定義で実現可能)
JPAでのJOINの使い方
@Query("SELECT u FROM User u JOIN u.department d WHERE d.name = :deptName")
List<User> findByDepartmentName(@Param("deptName") String deptName);
➡ テーブル結合もJPQL(Java Persistence Query Language)で直感的に記述可能。
また、JOINの結果として返されるListの中身の型に注意が必要です。例えば、複数エンティティを返すクエリではObject[]型になることがあります。
@Query("SELECT u.name, d.name FROM User u JOIN u.department d")
List<Object[]> findUserAndDepartmentNames();
➡ この場合、Object[0]にUser名、Object[1]にDepartment名が格納されていることを意識しましょう。
nativeQueryの活用
JPAでは複雑なSQLやパフォーマンスチューニングが必要な場面でnativeQueryを使えます。
@Query(value = "SELECT * FROM users u WHERE u.created_at >= :date", nativeQuery = true)
List<User> findUsersCreatedAfter(@Param("date") LocalDate date);
➡ 自由度の高いSQLを使いたい場合、nativeQueryは有効な選択肢になります。
JDBCとは?
JDBCは、Java標準の低レベルなDB接続APIです。SQLを明示的に書き、結果を手動でマッピングする必要があります。
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setLong(1, 1);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
String name = rs.getString("name");
// ...
}
➡ JDBCの主な利点:
-
パフォーマンスが高い
-
DB処理を細かく制御できる
-
シンプルな処理なら実装が早い
JDBCでBeanに詰める実装例
JDBCではSQLの実行結果をJavaBeanに手動でマッピングする必要があります。
// UserBean.java
public class UserBean {
private Long id;
private String name;
private String email;
// getter/setter
}
// JDBCを使ったBeanへのマッピング
List<UserBean> users = new ArrayList<>();
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT id, name, email FROM users");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
UserBean user = new UserBean();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
users.add(user);
}
return users;
➡ 項目が多いテーブルでも、明示的にマッピングすることでバグを防ぎやすく、テストもしやすくなります。
ありがちなつまずきと正しい選び方の判断基準
よくある誤解①「JPAは万能」
誤り:「JPAを使えばどんなシステムにも適している」
➡ 正解: JPAは便利ですが、大量データ処理や複雑なSQLではパフォーマンスが落ちる場合があります。
// Before(JPA)
List<User> users = userRepository.findAll(); // 全件取得(非効率)
// After(nativeQuery または JDBC)
@Query(value = "SELECT id, name FROM users LIMIT 100", nativeQuery = true)
List<Object[]> getLimitedUsers();
また、JOINを使った場合、N+1問題が発生することもあるため、fetch joinの活用が重要です。
N+1問題とは?
エンティティのリストを取得した際に、関連するエンティティを1件ずつ追加で取得することで、クエリ数が急増する問題です。
// N+1問題の例(自動で1件ずつ発行される)
List<User> users = userRepository.findAll();
for (User u : users) {
System.out.println(u.getDepartment().getName());
}
➡ この場合、Userの数+1件のSQLが発行され、パフォーマンスが著しく悪化します。
// 解決策:fetch join を使って一括取得
@Query("SELECT u FROM User u JOIN FETCH u.department")
List<User> findAllWithDepartments();
よくある誤解②「JDBCは古い・非推奨」
誤り:「JDBCはもう時代遅れ」
➡ 正解: JDBCは現在も実務でよく使われており、JPAの裏側でも使われています。特にバッチ処理やレガシーシステムでは重宝されます。
実務での選び方:こんな時はどっち?
【JPAを使うべきケース】
┗ オブジェクト指向で開発したい
┗ 複数のエンティティが関連している
┗ メンテナンス性を重視したい
┗ JOINを使ったリレーションを活用したい
【JDBCを使うべきケース】
┗ SQLを細かく最適化したい
┗ 大量データのバルク処理を行う
┗ native SQLで特殊な結合・集計が必要
┗ 処理の高速性を優先したい
また、併用することも実務ではよくあります。
-
通常の画面系処理はJPA
-
バッチや重たい処理はJDBC(JDBCTemplate)やnativeQuery
JPAとJDBCはどちらも現場で生きるスキル
JPAとJDBCは、どちらかが優れているという話ではありません。それぞれに強みと弱みがあり、プロジェクトの要件やパフォーマンス要件によって使い分けることが重要です。
今回のポイントまとめ:
-
JPAは保守性・生産性に優れるが、パフォーマンス課題がある
-
JDBCは高速・自由度が高いが、開発効率や保守性は劣る
-
JOINやnativeQueryはJPAでも柔軟に活用できる
-
JOIN結果のListでは型に注意(Object[]など)
-
N+1問題はfetch joinで回避可能
-
実務では併用パターンも多い
GitHubでJPAとJDBCの実装比較コードを試せるリポジトリを探すと、理解が深まります。
【外部リンク】
【内部リンク】
【Spring Boot実践ガイド】List型・Map型の違いとBeanクラスの基礎&応用
Spring Bootアプリ開発で頻発するWhitelabel Error Page|Thymeleafのテンプレートエラーと対策まとめ
Javaのfor文をStreamに書き換えるには?Spring Bootでの実践例付きでわかる!