0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Spring入門】第6章 JPA 入門① Entity

0
Last updated at Posted at 2026-09-29

第3章で作った社員テーブルと部署テーブルを、Java のプログラムから扱えるようにする準備として、テーブルに対応するクラス(Entity)を作ります。この章では、SQL を書かずにデータベースを扱うための JPA の考え方と、テーブルとクラスの対応のさせ方を学びます。データを取り出すのは第7章です。

この章で学ぶこと

  • JPA と O/R マッピングの考え方
  • テーブルに対応する Entity クラスの作り方
  • 社員と部署の関係(多対一)を @ManyToOne で表す方法

6.1 JPA とは

データベースのデータを操作するには SQL を使います。第3章では、SQL Developer で SELECT や INSERT を実行しました。

Java のプログラムから SQL を実行することもできますが、「SQL を文字列で組み立てる」「実行結果を1列ずつ取り出して Java のオブジェクトに詰め替える」といった手間のかかる処理が必要になります。

そこで使われるのが JPA(Jakarta Persistence)です。JPA を使うと、Java のオブジェクトを操作するだけで、テーブルのデータを検索・登録・変更・削除 できます。必要な SQL は裏側で自動的に作られ、実行されます。

O/R マッピング

JPA の土台にあるのが O/R マッピング(Object-Relational Mapping)という考え方です。データベースのテーブルと Java のクラスを、次のように対応づけます。

データベース Java この教材の例
テーブル クラス EMPLOYEE テーブル ↔ Employee クラス
行(1件のデータ) オブジェクト(インスタンス) 雷禅 海皇さんの行 ↔ Employee オブジェクト1つ
列 フィールド NAME 列 ↔ name フィールド

ch06-1.png
図6-1 O/R マッピングのイメージ

JPA・Hibernate・Spring Data JPA の関係

この章では、似た名前の技術がいくつか登場します。関係を整理しておきましょう。

名前 説明 詳細
JPA データベースのテーブルと Java のクラスを対応させるための仕様 @Entity や @Id などのアノテーションの仕様を決めている
Hibernate JPA の仕様を実装したライブラリ 上記のアノテーションをもとに、Hibernate が SQL 文を作って実行する
Spring Data JPA JPA を Spring フレームワークに組み込んだ仕組み Repository インターフェースを書くだけで、検索・登録・変更・削除ができる

私たちが直接書くのは、@Entity などのアノテーションと、JPA の Repository インターフェースです。SQL 文は Hibernate が内部で生成して実行するので、自分で書く必要はありません。

6.2 Entity クラスを作る

テーブルに対応する Java のクラスを Entity(エンティティ)といいます。部署テーブル用の Department と、社員テーブル用の Employee の2つを作ります。

まず、第1章・第3章で確認したテーブルの定義をもう一度見ておきましょう。

テーブル 列名 型 Java のフィールド Java の型
DEPARTMENT DEPT_ID NUMBER(4) deptId Integer
DEPT_NAME VARCHAR2(20 CHAR) deptName String
EMPLOYEE EMP_ID NUMBER(6) empId Integer
NAME VARCHAR2(20 CHAR) name String
GENDER NUMBER(1) gender Integer
AGE NUMBER(3) age Integer
BIRTH_DATE DATE birthDate Date
DEPT_ID NUMBER(4) department Department
ADDRESS VARCHAR2(10 CHAR) address String

【知識メモ】名前の付け方のルール

データベースの列名は大文字とアンダースコア(DEPT_ID)、Java のフィールド名は先頭が小文字で単語の区切りを大文字にする書き方(deptId)にするのが一般的です。後者をキャメルケースといいます。

entity パッケージを作る

  1. パッケージ・エクスプローラーで employee-app の src/main/java の中にある、起点となる com.example.employeeapp パッケージを右クリックします。
  2. 新規 → パッケージ を選びます。

new-package-menu.png
図6-2 起点のパッケージを右クリック → 新規 → パッケージ

  1. 「名前」には、右クリックした com.example.employeeapp が最初から入っています。末尾に .entity を付け足して com.example.employeeapp.entity にし、完了 をクリックします。

new-package-entity.png
図6-3 名前を com.example.employeeapp.entity にして「完了」

【ご注意】パッケージの場所に注意

Entity や Repository は、必ず com.example.employeeapp パッケージの 中(com.example.employeeapp.○○)に作ってください。Spring Boot は、メインクラス EmployeeAppApplication があるパッケージとその下のパッケージだけを探します。外に作ると、Spring に見つけてもらえずエラーになります。

Department クラスを作る

  1. 作成した com.example.employeeapp.entity パッケージを右クリックし、新規 → クラス を選びます。

new-class-menu.png
図6-4 entity パッケージを右クリック → 新規 → クラス

  1. 「パッケージ」に com.example.employeeapp.entity が自動で入っていることを確認し、「名前」に Department と入力して 完了 をクリックします。
    new-class-department.png
    図6-5 パッケージを確認し、名前に Department と入力

  2. エディターが開くので、次のようにフィールドとアノテーションを入力します(getter と setter はまだ書きません)。アノテーションやクラス名は、後で説明する 補完(Ctrl + Space)を使って入力します。

src/main/java/com/example/employeeapp/entity/Department.java(入力途中)
package com.example.employeeapp.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;

@Entity
@Table(name = "DEPARTMENT")
public class Department {

    /** 部署ID */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_DEPT")
    @SequenceGenerator(name = "SEQ_DEPT", sequenceName = "SEQ_DEPT", allocationSize = 1)
    private Integer deptId;

    /** 部署名 */
    @Column
    private String deptName;

}

department-fields.png
図6-6 フィールドとアノテーションを入力したところ(赤枠の行にカーソルを置いて、次の getter と setter の生成に進む)

【再確認】アノテーションやクラス名は、必ず補完(Ctrl + Space)で入力する

Eclipse には、入力の途中で候補を出して続きを入力してくれる 補完 の機能があります。

  • たとえば @Entity を入力するときは、@Ent まで打って Ctrl + Space を押します。候補が表示されるので、選んで Enter を押すと補完されます。候補が複数あるときは、jakarta.persistence のものを選んでください。
  • 補完で選ぶと、必要な import 文もファイルの先頭に自動で追加 されます。
  • アノテーションだけでなく、クラス名(Integer、Date など)や変数名も補完できます。

import が必要なアノテーションやクラス名は、手で全部打ち込まず、必ず補完を使ってください。全部打ち込むと import 文が追加されず、赤い波線(エラー)になります。

短いクラス名をつい全部打ち込んでしまった場合も、慌てなくて大丈夫です。打った語の末尾にカーソルを置いて Ctrl + Space を押し、候補から選び直せば、import 文が追加されます。

getter と setter を自動生成する

フィールドは private なので、外から値を読み書きするための getter と setter が必要です。Eclipse の機能で自動生成しましょう。

  1. エディターの中で、最後の } の直前の空行(図6-6 の赤枠の行)にカーソルを置きます。
  2. メニューの ソース → getter および setter の生成 を選びます(エディター内で右クリックしても同じメニューがあります)。

generate-menu.png
図6-7 ソース → getter および setter の生成

  1. 表示された画面で すべて選択 をクリックし、deptId と deptName にチェックが入ったことを確認します。
  2. 生成 をクリックします。

generate-dialog.png
図6-8 すべて選択 → チェックを確認 → 生成

  1. Ctrl + S で保存します。エディターのタブの「*」(未保存のしるし)が消えたことを確認しましょう。
    department-compare.png
    図6-9 getter と setter を生成した直後(上)と、保存したあと(下)

完成した Department クラスは次のとおりです。

src/main/java/com/example/employeeapp/entity/Department.java(完成)
package com.example.employeeapp.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;

@Entity
@Table(name = "DEPARTMENT")
public class Department {

    /** 部署ID */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_DEPT")
    @SequenceGenerator(name = "SEQ_DEPT", sequenceName = "SEQ_DEPT", allocationSize = 1)
    private Integer deptId;

    /** 部署名 */
    @Column
    private String deptName;

    public Integer getDeptId() {
        return deptId;
    }

    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }

}

Employee クラスを作る

同じ手順で、com.example.employeeapp.entity パッケージを右クリックして Employee クラスを作り、次のように入力します。

src/main/java/com/example/employeeapp/entity/Employee.java(入力途中)
package com.example.employeeapp.entity;

import java.util.Date;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;

@Entity
@Table(name = "EMPLOYEE")
public class Employee {

    /** 社員ID */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_EMP")
    @SequenceGenerator(name = "SEQ_EMP", sequenceName = "SEQ_EMP", allocationSize = 1)
    private Integer empId;

    /** 氏名 */
    @Column
    private String name;

    /** 性別(1:男、2:女) */
    @Column
    private Integer gender;

    /** 年齢 */
    @Column
    private Integer age;

    /** 生年月日 */
    @Column
    private Date birthDate;

    /** 部署 */
    @ManyToOne
    @JoinColumn(name = "DEPT_ID", referencedColumnName = "deptId")
    private Department department;

    /** 住所(都道府県) */
    @Column
    private String address;

}

employee-top.png
図6-10 フィールドとアノテーションを入力したところ(赤枠の行にカーソルを置いて、getter と setter を生成する)

【ご注意】Date は java.util.Date を選ぶ

Date は、Date まで打って Ctrl + Space で補完し、候補から Date - java.util を選びます。候補には Date - java.sql など同じ名前のクラスも並ぶので、選び間違えないようにしましょう。似た名前のクラスが候補に出ることがあるので、選び間違えないようにしましょう。

続けて、Department と同じ手順で getter と setter を自動生成します。

  1. エディターの中で、最後の } の直前の空行(図6-10 の赤枠の行)にカーソルを置きます。
  2. メニューの ソース → getter および setter の生成 を選びます。
  3. 表示された画面で すべて選択 をクリックし、7つのフィールドすべてにチェックが入ったことを確認します。
  4. 生成 をクリックします。
  5. Ctrl + S で保存し、エディターのタブの「*」が消えたことを確認します。

employee-bottom.png
図6-11 Employee クラス(自動生成した getter と setter の部分)

完成した Employee クラスは次のとおりです。

src/main/java/com/example/employeeapp/entity/Employee.java(完成)
package com.example.employeeapp.entity;

import java.util.Date;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;

@Entity
@Table(name = "EMPLOYEE")
public class Employee {

    /** 社員ID */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_EMP")
    @SequenceGenerator(name = "SEQ_EMP", sequenceName = "SEQ_EMP", allocationSize = 1)
    private Integer empId;

    /** 氏名 */
    @Column
    private String name;

    /** 性別(1:男、2:女) */
    @Column
    private Integer gender;

    /** 年齢 */
    @Column
    private Integer age;

    /** 生年月日 */
    @Column
    private Date birthDate;

    /** 部署 */
    @ManyToOne
    @JoinColumn(name = "DEPT_ID", referencedColumnName = "deptId")
    private Department department;

    /** 住所(都道府県) */
    @Column
    private String address;

    public Integer getEmpId() {
        return empId;
    }

    public void setEmpId(Integer empId) {
        this.empId = empId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}

6.3 Entity の解説

アノテーションの意味

Entity クラスでは、アノテーションを使って「このクラスはどのテーブルに対応するか」「どのフィールドが主キーか」などを JPA に教えます。

アノテーション 付ける場所 意味
@Entity クラス このクラスが Entity(テーブルに対応するクラス)であることを表す
@Table(name = "…") クラス 対応するテーブル名を指定する
@Id フィールド このフィールドが主キーであることを表す
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "…") フィールド 主キーの値を、シーケンスから取り出して自動で採番することを表す。generator には、次の @SequenceGenerator の name を書く
@SequenceGenerator(name = "…", sequenceName = "…", allocationSize = 1) フィールド 使うシーケンスを指定する。sequenceName には、第3章で作ったシーケンスの名前を書く
@Column フィールド このフィールドがテーブルの列に対応することを表す。この教材では、主キー(@Id)のフィールドには付けない書き方にしている(JPA の決まりではなく、@Id に付けてもよい)
@ManyToOne フィールド 多対一の関係であることを表す(後述)
@JoinColumn(name = "…", referencedColumnName = "…") フィールド 関係するテーブルとつなぐための列(外部キー)と、つなぐ先の主キーを指定する(後述)

フィールドと列の対応

@Column には、列名(name)を書いていません。JPA は、フィールド名を列名に自動で読み替えるからです。deptId のような名前は、単語の区切り(大文字)にアンダースコアを入れて dept_id と読み替えます。Oracle では列名の大文字・小文字は区別されないので、DEPT_ID 列に対応します。

フィールド名 対応する列名
deptId DEPT_ID
deptName DEPT_NAME
birthDate BIRTH_DATE
name NAME

そのため、フィールド名は、列名と対応するように付けます。

部署の department フィールドだけは、@JoinColumn(name = "DEPT_ID", referencedColumnName = "deptId") で列名を書いています。フィールド名(department)と列名(DEPT_ID)が対応していないためです(後の「社員と部署の関係」で説明します)。

主キーの自動採番(シーケンス)

第3章では、主キーの番号を払い出すシーケンスを作りました。

第3章の CREATE SEQUENCE(抜粋)
CREATE SEQUENCE SEQ_EMP NOCACHE;

Entity 側の @GeneratedValue と @SequenceGenerator は、「社員を登録するときは、シーケンス SEQ_EMP から次の番号を取り出して社員IDにする」という指定です。社員を保存するとき、JPA はまず SEQ_EMP から次の番号を取り出し、その番号を社員IDにして INSERT します(第11章で、実際に実行される SQL を確認します)。

  • @SequenceGenerator の name は、この Entity の中で使う呼び名です。@GeneratedValue の generator に同じ名前を書いて結び付けます。この教材では、わかりやすいようにシーケンスの名前と同じにしています
  • sequenceName には、データベースのシーケンスの名前(SEQ_EMP)を書きます

この指定があるので、第10章以降で社員を新規登録するときは、社員IDを空(null)のまま保存すれば、次の番号が振られます。

【ご注意】allocationSize = 1 を必ず書く

allocationSize を省略すると 50 になり、JPA は「シーケンスは 50 ずつ増える」と考えて番号を扱います。第3章のシーケンスは 1 ずつ増えるので食い違い、起動時にエラーになったり、社員IDが大きく飛んだりします。必ず allocationSize = 1 を書いてください。

なぜ int ではなく Integer を使うのか

ID や年齢のフィールドには、int ではなく Integer を使っています。int は必ず何かの数値(初期値は 0)を持ちますが、Integer は 「値がない」ことを null で表せる からです。

たとえば新規登録する前の社員には、まだ社員IDがありません。このとき int だと 0 になってしまい、「社員ID が 0 の社員」なのか「まだ ID がない社員」なのか区別できません。Entity では、数値のフィールドに Integer などのラッパークラスを使うのが基本です。

日付は Date

Oracle の DATE 型の列(生年月日)は、Java の java.util.Date 型のフィールドに対応させます。java.util.Date は日付と時刻を表すクラスで、現場のシステムで広く使われています。生年月日のように日付だけを扱う場合、時刻の部分は 0時0分0秒 になります。変換は JPA が自動で行います。なお、新しく作るシステムでは、日付だけを表す java.time.LocalDate を使うことも増えています。

社員と部署の関係(@ManyToOne)

社員は必ずどこか1つの部署に所属します。一方、1つの部署には複数の社員が所属できます。このように「社員(多)が部署(一)に属する」関係を 多対一 といいます。

ch06-2.png
図6-12 社員と部署は「多対一」の関係

テーブルでは、社員テーブルの DEPT_ID 列に部署IDの数値が入っているだけでした。部署名を知るには、SQL で部署テーブルと 結合(JOIN) する必要があります。

SQL で社員と部署名を取り出す場合
SELECT e.EMP_ID, e.NAME, d.DEPT_NAME
  FROM EMPLOYEE e
  JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID;

Employee クラスでは、部署IDの数値(Integer deptId)ではなく、Department オブジェクトそのもの をフィールドに持たせました。

Employee.java(抜粋)
@ManyToOne                     // 社員(多)→ 部署(一)の関係
@JoinColumn(name = "DEPT_ID", referencedColumnName = "deptId")   // EMPLOYEE テーブルの DEPT_ID 列で、部署の deptId とつなぐ
private Department department;

@JoinColumn の2つの属性には、次のものを書きます。

属性 書くもの 意味
name 社員テーブル(EMPLOYEE)の外部キーの 列名:DEPT_ID 社員テーブルの、どの列を使って部署とつなぐか
referencedColumnName つなぐ先の Department の主キー:deptId その列の値を、部署のどの項目の値と突き合わせるか。省略すると、つなぐ先の主キーになる

つまり、「社員テーブルの DEPT_ID の値と、部署の deptId の値が同じもの」が、その社員の部署になります。この教材の書き方(@Column に列名を書かない)では、referencedColumnName には列名の DEPT_ID ではなく、フィールド名の deptId を書きます。列名を書くと、起動時にエラーになります。つなぐ先の @Column に列名を書いている Entity では、その列名を書きます。

こうしておくと、JPA が社員を取り出すときに、DEPT_ID を手がかりに部署テーブルとの結合を自動で行い、Department オブジェクトを詰めてくれます。そのため、Java のプログラムでは次のように書くだけで部署名が取れます。

String deptName = employee.getDepartment().getDeptName();   // "営業部"

ch06-3.png
図6-13 Employee オブジェクトが Department オブジェクトを持っている

【知識メモ】部署IDはどうやって取り出す?

社員の部署IDが必要なときは、employee.getDepartment().getDeptId() と書きます。第10章以降で新規登録や変更を作るときに使います。

6.4 よくあるエラー

Entity の書き間違いは、この章の時点ではエラーになりません。第7章のテストや、第8章以降でアプリを起動したときに、次のようなエラーとして現れます。そのときは、コンソールに表示されたメッセージを確認し、次の表を参考に直してください。

メッセージ(一部) 主な原因 対処
Not a managed type Entity として認識されていない クラスに @Entity が付いているか、com.example.employeeapp の下のパッケージに作ったかを確認する
ORA-00942: 表またはビューが存在しません テーブルが見つからない @Table のテーブル名のつづり、application.properties の接続ユーザー(empapp)を確認する
ORA-00904: 無効な識別子です 列が見つからない フィールド名が列名と対応しているか(deptId → DEPT_ID)、@JoinColumn の列名のつづりを確認する

6.5 まとめと確認問題

まとめ

  • JPA を使うと、SQL を書かずに Java のオブジェクトでテーブルを操作できる
  • テーブルに対応するクラスを Entity といい、@Entity・@Table・@Id・@Column などで対応を指定する。列名は、フィールド名から自動で読み替えられる(deptId → DEPT_ID)
  • 主キーの自動採番には、@GeneratedValue と @SequenceGenerator で第3章のシーケンスを指定する
  • 社員と部署のような多対一の関係は @ManyToOne と @JoinColumn で表し、employee.getDepartment().getDeptName() で部署名が取れる

確認問題

問1 O/R マッピングでは、テーブル・行・列は、Java のそれぞれ何に対応しますか。

解答を見る

テーブルは クラス、行は オブジェクト(インスタンス)、列は フィールド に対応します。

問2 Entity の ID のフィールドに、int ではなく Integer を使うのはなぜですか。

解答を見る

Integer は「値がない」ことを null で表せるからです。新規登録前の社員のように、まだ ID が決まっていない状態を表すことができます。int では 0 になってしまい、区別がつきません。

問3 Employee クラスで、部署を Integer deptId ではなく Department department として持たせると、どんな良いことがありますか。

解答を見る

JPA が部署テーブルとの結び付けを自動で行い、Department オブジェクトを詰めてくれるので、自分で結合の SQL を書かなくても employee.getDepartment().getDeptName() で部署名を取り出せます。


前へ

第5章 DI とアプリの構成

次へ

第7章 JPA 入門② Repository

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?