はじめに
SpringBoot
で開発を行う際、DBとのデータ連携でentity
とRepository
を使用し、CRUD操作を行っていると思います。
Entity
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity // このクラスがEntityであることを示す
public class User {
@Id // 主キーであることを示す
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// GetterとSetter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// findByEmailというメソッド名を定義するだけで、
// "SELECT * FROM user WHERE email = ?" というSQLが自動生成される
User findByEmail(String email);
}
SpringからSpringBootへ移行して、SQLを書かずにDBとのやり取りができたことにとてもびっくりしたのを覚えています。
ただその時は、経験が浅く、いろいろわからないことだらけだったため、なぜSQLを書かずにDBとやり取りができるのか、をしっかりと調べることをしませんでした。。。。ヨクナイ
ということで、今回JPA(Java Persistence API)やORMについて、まとめてみたいと思います。
ORM(Object-Relational Mapping)とは??
オブジェクトとデータベース間のマッピングを行う技術や概念の総称。
JPA (Java Persistence API)とは?
JPAは、データベースを扱うための約束事を定めたものです。ルールブックみたいなもの。
「Javaのクラスに@Entityと書いたら、それはデータベースのテーブルだよ」「主キーは@Idと書いてね」といった、プログラマーが守るべき共通のルールが決められています。
JPAはそれ自体が動くものではなく、あくまで「どうやって作ればうまく動くか」という設計図のようなものです。
Hibernate とは?
Hibernate
は、そのJPAのルールブックを完璧に理解し、実際に動くように作られた具体的なツールです。 優秀な職人のようなもの。
プログラマーが書いた@Entityアノテーションを見て、「これはCREATE TABLE文に変換すればいいな」と判断し、自動でSQL文を作成して実行してくれます。
つまり
-
ORMは「技術」
-
JPAは、その技術をJavaで標準化するための「仕様」
-
Hibernateなどは、その仕様(JPA)に準拠した「実装」
これにより、開発者はJPAのAPIを使ってコードを書けば、裏側で動作するORMフレームワークをHibernate
からEclipseLink
に切り替えても、コードをほとんど変更せずに済むというメリットがあります。
Repositoryを実行すると
裏側でHibernate
が動いてSQL文を生成し、実行してくれます。
これは、Spring Data JPA
とHibernate
の連携によって実現される非常に便利な仕組みです。
-
Repositoryの定義: 開発者は、データアクセスに必要なメソッド(
findById()
,save()
など)を定義したRepository
インターフェースを作成します。この時点では、具体的な実装は記述しません。 -
Repositoryの実行: アプリケーションのビジネスロジック(サービス層など)から、この
Repository
インターフェースのメソッドを呼び出します。 -
Hibernateの動作:
Spring Data JPA
は、呼び出されたメソッド名や引数などから、その操作に合ったSQL文をHibernate
に生成させます。 -
データベースへのアクセス: 生成されたSQL文が
JDBC
(Java Database Connectivity
)を介してデータベースに送信され、実行されます。 -
結果の返却: データベースから返された結果が、再び
Hibernate
によってJavaのEntity
オブジェクトに変換され、Repository
を通じてアプリケーションに返されます。
この仕組みのおかげで、開発者は煩雑なSQL文を直接書く必要がなくなり、オブジェクト指向のコードに集中できるようになります。
具体的な実行の流れ(下記にソース例あり)
-
ブラウザから
/user?email=test@example.com
というリクエストが送られる。 -
UserController
がリクエストを受け取る。 -
UserController
はUserService
のfindUserByEmail
メソッドを呼び出す。 -
UserService
はUserRepository
のfindByEmail
メソッドを呼び出す。 -
Spring Data JPA
とHibernate
が連携し、SELECT * FROM user WHERE email = 'test@example.com'
というSQL文を自動生成して実行する。 -
データベースから取得したデータが
User
オブジェクトに変換されて返される。 -
UserController
は、そのUser
オブジェクトをmodel.addAttribute
を使ってModel
に格納する。 -
user-details.html(View)
にデータが渡され、ユーザー情報が表示される。
このように、開発者はSQLを一行も書かずにデータベース操作が実現できるのが、Spring Boot
のRepository
とHibernate
(ORM)の連携の大きなメリットです
具体的なソース例
Entity(データベースのテーブルに対応するクラス)
これは、データベースのusers
テーブルに対応するUser
クラスです。これがORMが扱うオブジェクト(Entity
)です。
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity // このクラスがEntityであることを示す
public class User {
@Id // 主キーであることを示す
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// GetterとSetter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
Repository(データアクセスの窓口)
これは、User Entity
を操作するためのRepository
インターフェースです。Spring Data JPA
が、このメソッド名から自動でSQLを生成します。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// findByEmailというメソッド名を定義するだけで、
// "SELECT * FROM user WHERE email = ?" というSQLが自動生成される
User findByEmail(String email);
}
Service(ビジネスロジック層)
Service
クラスは、Repository
を使ってデータアクセスを行います。ここでは、UserRepository
がインジェクション(注入)されています。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findUserByEmail(String email) {
// Repositoryのメソッドを呼び出すだけで、SQLは意識しない
return userRepository.findByEmail(email);
}
}
Controller(リクエストの処理)
Controller
は、Service
を呼び出してビジネスロジックを実行し、その結果をModel
に格納してView
に渡します。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user")
public String getUser(@RequestParam String email, Model model) {
// Serviceを呼び出す
User user = userService.findUserByEmail(email);
// 結果をModelに格納
model.addAttribute("user", user);
// Thymeleafテンプレートに渡す
return "user-details";
}
}