前回は Spring Boot の概念的な話をしました。
今回は Entity を作成していきます。

Entity とは?
DB テーブルの構造を示すファイルです。
DB データ取得後に、Spring Boot 側ではどんな型として扱うか定義する役割を担います。
基本的にはテーブル定義と同じ型にしますが、Spring Boot では表記が異なったり、そもそも存在しない型もあるので、その場合は似たような型を代わりに定義します。
- tinyint(1) → Boolean
- bigint → Long
などが例としてあります。
なお、「Entity」と「エンティティ」で2つの記載方法がありますが、どちらも同じ意味です。
英語表記か日本語表記かの違いだけなので。
Entityファイル作成
SQL テーブルは全部で、
- authority
- category
- user
- todo
- todo_category
の5つです。

これらファイルと対となるように、Entity も5つ作成していきます。
手順1
main フォルダ内の「TodoappApplication.java」と同階層に「repository」フォルダを作成し、その中で「entity」 フォルダを作成してください。

手順2
DB テーブルと対になる Entity ファイルを下記名称で作成します。
- Authority.java
- Category.java
- Todo.java
- TodoCategory.java
- User.java
いずれも entity フォルダ内に配置してください。

コードはこれから記載するので白紙で問題ありませんが、最終的にはいずれの Entity も Class 型にします。
ファイル作成後に Class 型を自動生成できる選択肢が出現するので、それで生成した方が分かりやすいとは思いますが。
各Entityの型定義
これまでに作成した下記2つを見ながら Entity ファイルの中身を書いていきます。
SQLファイル作成時と同じように、
- Authority.java
- Category.java
- User.java
- Todo.java
- TodoCategory.java
の順番で型定義を行います。
Authority
水色が第3回で作成した SQL テーブルです。
緑色が今回作成する Entity です。
-- 権限情報
CREATE TABLE `authority`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '権限ID',
`name` varchar(16) NOT NULL COMMENT '権限名',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
PRIMARY KEY (`id`),
UNIQUE (`name`)
) ENGINE=InnoDB
package com.ozack.todoapp.repository.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/* 権限情報 */
@Entity
@Table(name = "authority")
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Authority {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
}
権限の作成日・更新日は、Spring Boot では一切使用しないので除外しています。
(保守目的でAdminerから確認するくらいです)
そのため、データベースから取得できるカラムは、権限IDと権限名を表す id,name のみです。
各アノテーション(@が付いてる箇所)に関しても少し解説します。
@Entity
Entity を定義しているクラスであることを明示しています。
@Table
どの SQL テーブルと対にするか定義します。
今回は authority テーブルとマッピングするため下記で定義しています。
@Table(name = "authority")
@AllArgsConstructor
初回の環境構築時にインストールした lombok の機能です。
全ての変数に対する初期値を設定できるコンストラクタを使えるようになります。
例えば、こんな感じです。
/* AllArgsConstructor の機能を用いて各変数値を設定 */
Authority constructor = new Authority(
1L,
"authority-name-1"
);
Entity の引数を設定できるようになるため、頻繫に使用します。
@Getter
初回の環境構築時にインストールした lombok の機能です。
全ての変数に対して、値を取得するためのゲッターと呼ばれる関数が提供されます。
下記のような使い方をします。
/* AllArgsConstructor の機能を用いて各変数値を設定 */
Authority authority = new Authority(
1L,
"authority-name-1"
);
/* Getter の機能を用いて変数値を取得 */
authority.getId(); // id 変数の値(1L)を取得
authority.getName(); // name 変数の値("authority-name-1")を取得
.get変数名(); の箇所は、@Getter を Entity に付与したことで追加された関数です。
この関数はキャメルケースで生成されます。
@NoArgsConstructor
lombok の機能です。
引数を設定できないコンストラクタを使用できるようになりますが、一切使用しません。
わざわざ使わないアノテーションを付与している理由は⬇️等のエラーになるからです。
Caused by: org.hibernate.InstantiationException: No default constructor for entity 'com.ozack.todoapp.repository.entity.Category'
org.springframework.orm.jpa.JpaSystemException: HHH000143: Bytecode enhancement failed because no public, protected or package-private default constructor was found for entity: com.ozack.todoapp.repository.entity.Authority. Private constructors don't work with runtime proxies
このエラー、よく見ると Hibernate を経由しているのですが、コレがエンティティを生成する際にデフォルトコンストラクタ(引数なしのコンストラクタ)を要求しているらしいです。
そのため、@NoArgsConstructor を付与し、Category
エンティティにデフォルトコンストラクタを追加することでエラーが発生しないように調整しています。
@Id
主キーであると明示するために付与します。
なくても問題ない場合が多いですが、コードの視認性という観点では、付与されていた方が「主キーと対応している」ことが容易に判別でき、分かりやすいです。
今回の場合は、主キーと対応している id 変数に付与しています。
@Column
SQL テーブル内の、どのカラムと対にするのか定義します。
@Id
@Column(name = "id") // id カラムと対
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name") // name カラムと対
private String name;
authority テーブル内の、id, name カラムと対にするので、
- id変数とidカラム
- name変数とnameカラム
で、それぞれマッピングしています。
@GeneratedValue
主キー値の生成をデータベース側に丸投げします。
@GeneratedValue(strategy = GenerationType.IDENTITY)
今回は SQL テーブル側で AUTO_INCREMENT を指定し、データベース側で勝手に値を生成するように設計しているため、Spring Boot 側で主キーの値を生成する必要がありません。
Category
先ほどの Authority と全く同じ構成です。
同じ箇所の説明は不要だと思うので省略します。
-- カテゴリー情報
CREATE TABLE `category`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'カテゴリーID',
`name` varchar(16) NOT NULL COMMENT 'カテゴリー名',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
PRIMARY KEY (`id`),
UNIQUE (`name`)
) ENGINE=InnoDB
package com.ozack.todoapp.repository.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/* カテゴリー情報 */
@Entity
@Table(name = "category")
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Category {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
}
User
authority テーブルを参照できるように、ER図で示した多対1の関係になるようにします。
-- ユーザー情報
CREATE TABLE `user`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ユーザーID',
`authority_id` bigint unsigned NOT NULL COMMENT '権限ID',
`name` varchar(16) NOT NULL COMMENT 'ユーザー名',
`email` varchar(32) NOT NULL COMMENT 'メールアドレス',
`password` varchar(32) NOT NULL COMMENT 'パスワード',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
PRIMARY KEY (`id`),
FOREIGN KEY (`authority_id`) REFERENCES `authority`(`id`) ON DELETE CASCADE,
UNIQUE (`email`)
) ENGINE=InnoDB
package com.ozack.todoapp.repository.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Tolerate;
/* ユーザー情報 */
@Entity
@Table(name = "user")
@RequiredArgsConstructor
@Getter
public class User {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private final Long id;
@Column(name = "authority_id")
private final Long authorityId;
@Column(name = "name")
private final String name;
@Column(name = "email")
private final String email;
@Column(name = "password")
private final String password;
@ManyToOne(
targetEntity = Authority.class,
fetch = FetchType.LAZY)
@JoinColumn(
name = "authority_id",
insertable = false,
updatable = false)
private Authority authority;
@Tolerate
public User(){
this.id = null;
this.authorityId = null;
this.name = null;
this.email = null;
this.password = null;
}
}
@ManyToOne
何らかのテーブルと多対1の関係になっている場合、多側の変数に付与するアノテーションです。
今回は user(多) テーブル側から authority(1) テーブルを結合するので、User(多) と Authority(1) でマッピングします。
そのため、UserEntity には Authority エンティティを保持する下記変数を定義します。
private Authority authority;
そして、Authoriry エンティティと多対1の関係になるように、@ManyToOne を付与します。
@ManyToOne(
targetEntity = Authority.class,
fetch = FetchType.LAZY)
@JoinColumn(
name = "authority_id",
insertable = false,
updatable = false)
private Authority authority;
fetch = FetchType.LAZY は不要なオブジェクトを参照しない設定にできます。
仕組みが少し難しいのですが、基本的に LAZY しか使わないので慣れるまでは上記の構文を丸暗記でもいいと思います。
仕組み解説はこの記事が分かりやすく自分も見たりしてます。
@JoinColumn
テーブル結合に用いる外部キーを指定するアノテーションです。
user テーブルの外部キーで定義したとおりですが、
- user テーブルの authority_id カラム
- authority テーブルの id カラム
でマッピングするため、結合に用いるカラム名は authority_id を設定します。
@ManyToOne(
targetEntity = Authority.class)
@JoinColumn(
name = "authority_id",
insertable = false,
updatable = false)
private Authority authority;
なお、user テーブルから間接的に authority テーブルを登録・更新する必要はないので、insertable,updatable には false を設定しています。
@RequiredArgsConstructor
@AllArgsConstructorと酷似した機能を提供します。
ただ、こちらは指定した変数に対してのみ、初期値を設定できるようになります。
初期値を設定できる変数を指定するためには、@Tolerate なるものを用います。
@Tolerate
public User(){
this.id = null;
this.authorityId = null;
this.name = null;
this.email = null;
this.password = null;
}
そして、各該当変数に final 修飾子を設定します。
(説明に不要なコードは省いてます)
public class User {
private final Long id;
private final Long authorityId;
private final String name;
private final String email;
private final String password;
private Authority authority;
}
すると、下記のように User エンティティの各変数値を Service や Repository 等といった別ファイルからも設定できるようになります。
/* RequiredArgsConstructor の機能を用いて各変数値を設定 */
User constructor = new User(
1L,
1L,
"user-name",
"user-email",
"user-password"
);
主にデータの登録・更新を行う処理で頻繫に使用します。
一方で、authority 変数には初期値を設定できないようにしました。
理由は割と簡単で、User エンティティは user テーブルの Entity なので、user テーブルしか登録(更新)しません。
authority テーブルの登録(更新)には Authority エンティティを使えばいいのです。
一方、データの取得・削除の場合は、そもそも初期値を設定するなんてことを一切しません。
そのため、わざわざ User エンティティで Authority を設定する必要はないです。
Todo
@OneToMany があるので、このエンティティが一番難しいです。
-- Todo 情報
CREATE TABLE `todo`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'TodoID',
`user_id` bigint unsigned NOT NULL COMMENT 'ユーザーID',
`title` varchar(32) NOT NULL COMMENT 'やること内容',
`is_check` tinyint(1) NOT NULL COMMENT 'チェックの有無を示す真偽値',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
UNIQUE (`user_id`, `title`)
) ENGINE=InnoDB
package com.ozack.todoapp.repository.entity;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Tolerate;
/* Todo 情報 */
@Entity
@Table(name = "todo")
@RequiredArgsConstructor
@Getter
public class Todo {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private final Long id;
@Column(name = "user_id")
private final Long userId;
@Column(name = "title")
private final String title;
@Column(name = "is_check")
private final Boolean isCheck;
@OneToMany(
targetEntity = TodoCategory.class,
mappedBy = "todo",
fetch = FetchType.LAZY)
@JsonManagedReference
private Set<TodoCategory> todoCategories;
@Tolerate
public Todo(){
this.id = null;
this.userId = null;
this.title = null;
this.isCheck = null;
}
}
@OneToMany
何らかのテーブルと1対多の関係になっている場合、1側の変数に付与するアノテーションです。
mappedBy = “todo” は、TodoCategory エンティティの todo 変数によってマッピングされるという意味です。
public class Todo {
@OneToMany(
targetEntity = TodoCategory.class,
mappedBy = "todo",
fetch = FetchType.LAZY)
@JsonManagedReference
private Set<TodoCategory> todoCategories;
}
ちなみに、TodoCategory エンティティでは todo 変数を↓のように書いています。
public class TodoCategory {
@ManyToOne(
targetEntity = Todo.class,
fetch = FetchType.LAZY)
@JoinColumn(
name = "todo_id",
insertable = false,
updatable = false)
@JsonBackReference
private Todo todo;
}
ER図のとおりに、Todo(1) と TodoCategory(多) でマッピングしています。
@OneToMany は、1側のテーブルから多側のテーブルを結合する際に必要となります。
今回は todo(1) テーブル側から todo_category(多) テーブルを結合するつもりなので、todo テーブルと対になる Todo エンティティには、このアノテーションを付与しています。
また、todoCategories 変数は本来ならリスト型にしたいのですが、DB データ取得時にエラーとなるため Set 型に変更しています。
比較的に知れ渡っている解決方法らしいので、詳細を知りたい人は↓で調べてみてください。
MultipleBagFetchException cannot simultaneously fetch multiple bags
@JsonManagedReference
マッピングしたエンティティ間の循環参照を防ぐために付与するアノテーションです。
この循環参照問題は、@OneToMany を用いたときに発生します。
そのため、下記の参考サイトと同様の対策を取ります。
基本的には、
- @OneToMany側に@JsonManagedReference
- @ManyToOne側に@JsonBackReference
を付与することで解決します。
TodoCategory
少し長いですが、既に説明したアノテーションしか用いてないため説明することがあまりないです。
-- todo と category のマッピング情報
CREATE TABLE `todo_category`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'マッピングID',
`todo_id` bigint unsigned NOT NULL COMMENT 'TodoID',
`category_id` bigint unsigned NOT NULL COMMENT 'カテゴリーID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
PRIMARY KEY (`id`),
FOREIGN KEY (`todo_id`) REFERENCES `todo`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ON DELETE CASCADE,
UNIQUE (`todo_id`, `category_id`)
) ENGINE=InnoDB
package com.ozack.todoapp.repository.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Tolerate;
/* Todo と Category のマッピング情報 */
@Entity
@Table(name = "todo_category")
@RequiredArgsConstructor
@Getter
public class TodoCategory {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private final Long id;
@Column(name = "todo_id")
private final Long todoId;
@Column(name = "category_id")
private final Long categoryId;
@ManyToOne(
targetEntity = Todo.class,
fetch = FetchType.LAZY)
@JoinColumn(
name = "todo_id",
insertable = false,
updatable = false)
@JsonBackReference
private Todo todo;
@ManyToOne(
targetEntity = Category.class,
fetch = FetchType.LAZY)
@JoinColumn(
name = "category_id",
insertable = false,
updatable = false)
private Category category;
@Tolerate
public TodoCategory(){
this.id = null;
this.todoId = null;
this.categoryId = null;
}
}
下記の2箇所が多少ややこしいところだと思いますが、それ以外は非常に簡単です。
@JsonBackReference
先ほど説明した @JsonManagedReference と全く同じ理由で付与しています。
@ManyToOne
User エンティティで既に説明していますが、ここでは若干ややこしくなってるので再度掲載します。
ER図のとおりに Todo と Category 2つのテーブルに対して多対1の関係を持たせるので、@ManyToOne を2回使用しています。
- 多側(TodoCategory)から1側(Category)のテーブルを結合するので category 変数に @ManyToOne を付与。
- 先ほどの Todo エンティティから @OneToMany で参照されている todo 変数に @ManyToOne を付与。
まとめ
SQL テーブルに対応するエンティティを作成しました。
main/…/repository/entity
- フォルダ内の全ファイル
プロジェクト構成は GitHub にプッシュしています。
今回作成したエンティティは、
- データベース操作を行う Repository
- 業務ロジックを記載する Service
- フロント側との窓口となる Controller
など、様々な場面で使用することになります。
次回はデータベース操作を行う Repository を作成します。