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?

【DevNav Handbook】JPA Entityの基本:usersテーブルをAppUserとして表現する

0
Last updated at Posted at 2026-06-09

はじめに

この記事は DevNav Handbook の記事です。

今回は、PostgreSQLに作成した users テーブルを、Spring Boot側で AppUser Entityとして表現します。

Entityは、Spring BootアプリケーションとDBテーブルをつなぐ重要なクラスです。

ただ、@Entity@Table@Column@NoArgsConstructor@Data まわりは、最初はかなり混乱しやすいです。

この記事では、users テーブルを例に、JPA Entityの基本を整理します。

コードを細かく読むというより、DBの1行をJava側でどう扱えるようにするのか、そして なぜEntityで@Dataを安易に使わないのか を理解することを重視します。


冒頭理解度チェック

以下を説明できる人は、この記事を読まなくても大丈夫です。

@Entity と @Table(name = "users") の違いを説明できる

DBの firebase_uid カラムと Javaの firebaseUid フィールドがどう対応するか説明できる

JPA Entityに引数なしコンストラクタが必要な理由を説明できる

@NoArgsConstructor(access = AccessLevel.PROTECTED) の意味を説明できる

Entityで @Data を安易に使わない理由を説明できる

public AppUser(...) のような値ありコンストラクタを置く理由を説明できる

この記事の後半に、上記チェック項目の答え合わせも入れています。

実装コードをすべて読まなくても、理解度チェックの答え合わせを見るだけで、今回のポイントは復習できます。


この記事のゴール

この記事のゴールは以下です。

PostgreSQLの users テーブルを
Spring Boot側の AppUser Entity として表現する

今回扱う内容は以下です。

@Entity
@Table
@Id
@GeneratedValue
@Column
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
値ありコンストラクタ
@Dataを避ける理由

今回の記事では、まだ以下は扱いません。

Repository
Controller
GET /api/users
Firebase Token検証
Spring Security連携

Entityの理解だけに集中します。


前提:usersテーブル

今回、PostgreSQL側には以下の users テーブルがある前提です。

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    firebase_uid VARCHAR(128) UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    display_name VARCHAR(100),
    role VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

このテーブルを、Java側では AppUser というEntityで扱います。


今回作るAppUser Entity

今回作るEntityは以下です。

package com.example.devnav.user;

/**
 * アプリ側で利用するユーザー情報を表すEntity。
 */
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "users")
public class AppUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "firebase_uid", nullable = false, unique = true)
    private String firebaseUid;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(name = "display_name")
    private String displayName;

    @Column(nullable = false)
    private String role;

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    /**
     * アプリ側の処理からAppUserを新規作成するためのコンストラクタ。
     *
     * @param firebaseUid Firebase Authenticationで発行されたユーザーUID
     * @param email メールアドレス
     * @param displayName 画面表示用のユーザー名
     * @param role アプリ側で利用する権限
     */
    public AppUser(String firebaseUid, String email, String displayName, String role) {
        this.firebaseUid = firebaseUid;
        this.email = email;
        this.displayName = displayName;
        this.role = role;
    }
}

今回のポイントは、DBの users テーブルの1行を、Java側では AppUser オブジェクトとして扱えるようにすることです。

usersテーブルの1行
↓
AppUser Entity

EntityはDBテーブルとJavaクラスをつなぐ

@Entity は、このクラスをJPA管理対象にするためのアノテーションです。

@Entity
public class AppUser {
}

これを付けることで、JPAは AppUser をDBテーブルと対応するクラスとして扱えるようになります。

一方、@Table(name = "users") は、このEntityがどのテーブルに対応するかを明示するために使います。

@Table(name = "users")

今回のDBテーブル名は users です。

そのため、AppUser クラスに @Table(name = "users") を付けています。

AppUserクラス
↓
usersテーブル

@Entity はJPA管理対象にする指定です。

@Table(name = "users") は対応するテーブル名を指定するものです。

似ていますが、役割は違います。


DBカラムとJavaフィールドの対応

今回のEntityでは、DBのカラムとJavaのフィールドが以下のように対応します。

DBの id
→ Javaの id

DBの firebase_uid
→ Javaの firebaseUid

DBの email
→ Javaの email

DBの display_name
→ Javaの displayName

DBの role
→ Javaの role

DBの created_at
→ Javaの createdAt

Javaでは firebaseUid のようなキャメルケースを使います。

一方、DBでは firebase_uid のようなスネークケースを使うことが多いです。

この名前の違いを対応させるために、@Column(name = "firebase_uid") を使います。

@Column(name = "firebase_uid", nullable = false, unique = true)
private String firebaseUid;

これにより、JPAに以下を伝えています。

Javaの firebaseUid フィールドは
DBの firebase_uid カラムに対応する

なぜprotectedの空コンストラクタが必要なのか

今回のEntityには以下を付けています。

@NoArgsConstructor(access = AccessLevel.PROTECTED)

これは、Lombokによって以下のようなコンストラクタを自動生成する指定です。

protected AppUser() {
}

JPAはDBからデータを取得するときに、DBの1行をJavaのEntityオブジェクトとして組み立てます。

このとき、JPAはまず空のEntityを作る必要があります。

そのため、Entityには引数なしコンストラクタが必要です。

ただし、public AppUser() にすると、アプリ側のどこからでも空の AppUser を作れてしまいます。

AppUser user = new AppUser();

この時点では、emailrole などが入っていない不完全な状態です。

そのため、空のコンストラクタはJPA用に残しつつ、通常のアプリ処理からは呼びにくい protected にしておきます。

protected AppUser()
→ JPAがDBからEntityを作るための入口

public AppUser()
→ どこからでも空のEntityを作れてしまうため避けたい

DBからEntityを作るとは何か

例えば、DBの users テーブルに以下の1行があるとします。

id: 1
firebase_uid: abc123
email: admin@example.com
display_name: admin
role: ADMIN
created_at: 2026-06-09 14:00:00

Spring Boot側でRepositoryを使って取得すると、JPAはこのDBの1行を AppUser オブジェクトとして扱えるようにします。

DBの1行
↓
AppUserオブジェクト

もう少し具体的に言うと、以下の対応が行われます。

DBの id
→ AppUserの id

DBの firebase_uid
→ AppUserの firebaseUid

DBの email
→ AppUserの email

DBの display_name
→ AppUserの displayName

DBの role
→ AppUserの role

DBの created_at
→ AppUserの createdAt

このように、DBの値をJavaのフィールドに対応させて、Javaコードから扱える形にするのがJPAの役割です。


なぜ値ありコンストラクタを置くのか

Entityには、値ありコンストラクタも用意しています。

public AppUser(String firebaseUid, String email, String displayName, String role) {
    this.firebaseUid = firebaseUid;
    this.email = email;
    this.displayName = displayName;
    this.role = role;
}

これは、Spring Boot側で新しいユーザーを作るときに使います。

例えば、後で初回ログイン時にFirebaseの情報をもとにユーザーをDBへ保存する場合、以下のように使えます。

AppUser user = new AppUser(
    firebaseUid,
    email,
    displayName,
    "ADMIN"
);

appUserRepository.save(user);

つまり、このコンストラクタは以下のためにあります。

Spring Boot側で新しいAppUserを作れるようにするため

空のEntityではなく、必要な値が入った状態のEntityを作る入口です。


protected空コンストラクタと値ありコンストラクタの違い

整理すると、役割は以下です。

protected AppUser()
→ JPAがDBの値をAppUserとして扱うために必要

public AppUser(firebaseUid, email, displayName, role)
→ Spring Boot側で新しいユーザーを作るために使う

この2つは役割が違います。

JPA用とアプリ用で、Entityの生成ルートを分けているイメージです。


なぜEntityで@Dataを使わないのか

Lombokの @Data は便利ですが、Entityでは安易に使わない方が安全です。

@Data には以下が含まれます。

@Getter
@Setter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode

Entityで特に注意したいのは以下です。

@Setter
@ToString
@EqualsAndHashCode

@Setter があると、どこからでもEntityの値を書き換えられます。

user.setRole("ADMIN");
user.setEmail("changed@example.com");

もちろん必要な場合もありますが、無制限にSetterを生やすと、どこで値が変わったのか追いにくくなります。

Entityでは、必要な変更処理だけをメソッドとして用意する方が安全です。

例えば将来的には以下のような形です。

public void changeDisplayName(String displayName) {
    this.displayName = displayName;
}

また、@ToString はリレーションが増えたときに、関連先まで出力しようとしてログが膨らんだり、循環参照の原因になったりすることがあります。

@EqualsAndHashCode も、JPA EntityのID採番や永続化前後の状態と相性が悪くなることがあります。

そのため、まずは以下の方針で十分です。

JPA Entityでは @Data を雑に付けない
@Getterから始める
必要な変更メソッドだけ自分で用意する

冒頭理解度チェックの答え合わせ

ここで、冒頭理解度チェックの答えを整理します。

全部読まなくても、ここを見れば今回のポイントを復習できます。

@Entity と @Table(name = "users") の違い

@Entity
→ このクラスをJPA管理対象にする。

@Table(name = "users")
→ このEntityがDBのusersテーブルに対応することを明示する。
DBの firebase_uid カラムと Javaの firebaseUid フィールドの対応

DBでは firebase_uid。
Javaでは firebaseUid。

@Column(name = "firebase_uid") を付けることで、
Javaの firebaseUid と DBの firebase_uid を対応させる。
JPA Entityに引数なしコンストラクタが必要な理由

JPAがDBからデータを取得するとき、
まず空のEntityを作り、そこにDBの値を詰めていくため。

そのため、Entityには引数なしコンストラクタが必要。
@NoArgsConstructor(access = AccessLevel.PROTECTED) の意味

protectedな引数なしコンストラクタをLombokで生成する指定。

JPAには使わせる。
ただし、アプリ側から雑に空のEntityを作らせない。
Entityで @Data を安易に使わない理由

@Dataには @Setter、@ToString、@EqualsAndHashCode などが含まれる。

Setterでどこからでも値を書き換えられる。
ToStringはリレーションで大量出力や循環参照の原因になることがある。
EqualsAndHashCodeはJPA EntityのID採番や永続化前後の状態と相性が悪いことがある。

そのため、Entityではまず @Getter から始める。
public AppUser(...) のような値ありコンストラクタを置く理由

Spring Boot側で新しいAppUserを作るため。

JPA用のprotected空コンストラクタとは役割が違う。

protected AppUser()
→ JPAがDBからEntityを復元するため

public AppUser(...)
→ アプリ側で新しいEntityを作成するため

次回やること

次回は、この AppUser Entityを使って、Spring Bootから users テーブルを取得します。

次回のゴールは以下です。

AppUserRepositoryを作成する

UserControllerを作成する

GET /api/users でusersテーブルのデータを取得する

Entity単体では、まだDBアクセスはできません。

次回はRepositoryとControllerを作り、実際にSpring BootからPostgreSQLの users テーブルを取得できるようにします。


まとめ

今回は、PostgreSQLの users テーブルを、Spring Boot側の AppUser Entityとして表現しました。

Entityは、DBテーブルとJavaクラスを対応させる重要な層です。

今回のポイントは以下です。

@EntityでJPA管理対象にする

@Tableで対応するテーブル名を明示する

@ColumnでDBカラムとJavaフィールドを対応させる

JPA用にprotectedの空コンストラクタを用意する

アプリ側の新規作成には値ありコンストラクタを使う

Entityで@Dataを安易に使わない

まずはこのレベルで腹落ちしていれば、RepositoryやControllerに進めます。

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?