第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 フィールド |
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 パッケージを作る
- パッケージ・エクスプローラーで
employee-appのsrc/main/javaの中にある、起点となるcom.example.employeeappパッケージを右クリックします。 - 新規 → パッケージ を選びます。

図6-2 起点のパッケージを右クリック → 新規 → パッケージ
- 「名前」には、右クリックした
com.example.employeeappが最初から入っています。末尾に.entityを付け足してcom.example.employeeapp.entityにし、完了 をクリックします。

図6-3 名前を com.example.employeeapp.entity にして「完了」
【ご注意】パッケージの場所に注意
Entity や Repository は、必ず com.example.employeeapp パッケージの 中(com.example.employeeapp.○○)に作ってください。Spring Boot は、メインクラス EmployeeAppApplication があるパッケージとその下のパッケージだけを探します。外に作ると、Spring に見つけてもらえずエラーになります。
Department クラスを作る
- 作成した
com.example.employeeapp.entityパッケージを右クリックし、新規 → クラス を選びます。

図6-4 entity パッケージを右クリック → 新規 → クラス
-
「パッケージ」に
com.example.employeeapp.entityが自動で入っていることを確認し、「名前」にDepartmentと入力して 完了 をクリックします。

図6-5 パッケージを確認し、名前に Department と入力 -
エディターが開くので、次のようにフィールドとアノテーションを入力します(getter と setter はまだ書きません)。アノテーションやクラス名は、後で説明する 補完(Ctrl + Space)を使って入力します。
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;
}

図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 の機能で自動生成しましょう。
- エディターの中で、最後の
}の直前の空行(図6-6 の赤枠の行)にカーソルを置きます。 - メニューの ソース → getter および setter の生成 を選びます(エディター内で右クリックしても同じメニューがあります)。

図6-7 ソース → getter および setter の生成
- 表示された画面で すべて選択 をクリックし、
deptIdとdeptNameにチェックが入ったことを確認します。 - 生成 をクリックします。
完成した Department クラスは次のとおりです。
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 クラスを作り、次のように入力します。
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;
}

図6-10 フィールドとアノテーションを入力したところ(赤枠の行にカーソルを置いて、getter と setter を生成する)
【ご注意】Date は java.util.Date を選ぶ
Date は、Date まで打って Ctrl + Space で補完し、候補から Date - java.util を選びます。候補には Date - java.sql など同じ名前のクラスも並ぶので、選び間違えないようにしましょう。似た名前のクラスが候補に出ることがあるので、選び間違えないようにしましょう。
続けて、Department と同じ手順で getter と setter を自動生成します。
- エディターの中で、最後の
}の直前の空行(図6-10 の赤枠の行)にカーソルを置きます。 - メニューの ソース → getter および setter の生成 を選びます。
- 表示された画面で すべて選択 をクリックし、7つのフィールドすべてにチェックが入ったことを確認します。
- 生成 をクリックします。
- Ctrl + S で保存し、エディターのタブの「*」が消えたことを確認します。

図6-11 Employee クラス(自動生成した getter と setter の部分)
完成した Employee クラスは次のとおりです。
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章では、主キーの番号を払い出すシーケンスを作りました。
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つの部署には複数の社員が所属できます。このように「社員(多)が部署(一)に属する」関係を 多対一 といいます。
テーブルでは、社員テーブルの DEPT_ID 列に部署IDの数値が入っているだけでした。部署名を知るには、SQL で部署テーブルと 結合(JOIN) する必要があります。
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 オブジェクトそのもの をフィールドに持たせました。
@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(); // "営業部"

図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() で部署名を取り出せます。



