SQLテーブルに対応するEntityを作成する-SpringBoot入門⑤

SpringBoot(Todo):サムネイル画像5 Todoアプリ(SpringBoot)

前回は Spring Boot の概念的な話をしました。

今回は Entity を作成していきます。

SpringBootでTodo開発:システム構成図1
システム構成図

Entity とは?

DB テーブルの構造を示すファイルです。

DB データ取得後に、Spring Boot 側ではどんな型として扱うか定義する役割を担います。

基本的にはテーブル定義と同じ型にしますが、Spring Boot では表記が異なったり、そもそも存在しない型もあるので、その場合は似たような型を代わりに定義します。

  • tinyint(1) → Boolean
  • bigint → Long

などが例としてあります。

第3回「開発スタイルの解説」と同じことを言ってます。

なお、「Entity」と「エンティティ」で2つの記載方法がありますが、どちらも同じ意味です。

英語表記か日本語表記かの違いだけなので。

Entityファイル作成

SQL テーブルは全部で、

  1. authority
  2. category
  3. user
  4. todo
  5. todo_category

の5つです。

SpringBootでToDo開発5:テーブル一覧
前回までで5つ作成しています

これらファイルと対となるように、Entity も5つ作成していきます。

手順1

main フォルダ内の「TodoappApplication.java」と同階層に「repository」フォルダを作成し、その中で「entity」 フォルダを作成してください。

SpringBootでToDo開発5:entityフォルダを作成
entityフォルダを作成

手順2

DB テーブルと対になる Entity ファイルを下記名称で作成します。

  1. Authority.java
  2. Category.java
  3. Todo.java
  4. TodoCategory.java
  5. User.java

いずれも entity フォルダ内に配置してください。

SpringBootでToDo開発5:各 entity ファイルを作成
各 entity ファイルを作成

Java のファイル名は全てキャメルケースで記載します。

よく分からない方はこの記事が参考になったので確認してみるのがいいと思います。

コードはこれから記載するので白紙で問題ありませんが、最終的にはいずれの Entity も Class 型にします。

ファイル作成後に Class 型を自動生成できる選択肢が出現するので、それで生成した方が分かりやすいとは思いますが。

各Entityの型定義

これまでに作成した下記2つを見ながら Entity ファイルの中身を書いていきます。

  • 第3回で作成した SQL テーブル
  • 第2回で作成した以下の ER 図
2_userPK`user_id` bigint unsigned NOT NULL AUTO_INCREMENTFK`authority_id` int unsigned NOT NULL `name` varchar(16) NOT NULLUK`email` varchar(32) NOT NULL`password` varchar(32) NOT NULL`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP3_todoPK`todo_id` bigint unsigned NOT NULL AUTO_INCREMENTFK`user_id` bigint unsigned NOT NULLUK`title` varchar(32) NOT NULL`is_check` tinyint(1) NOT NULL`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP1_authorityPK`authority_id` int unsigned NOT NULL AUTO_INCREMENTUK`name` varchar(16) NOT NULL`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP1_cateoryPK`category_id` int unsigned NOT NULL AUTO_INCREMENTUK`name` varchar(16) NOT NULL`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP4_todo_categoryPK`todo_category_id` bigint unsigned NOT NULL AUTO_INCREMENTFK`todo_id` bigint unsigned NOT NULLFK`category_id` int unsigned NOT NULL`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

SQLファイル作成時と同じように、

  1. Authority.java
  2. Category.java
  3. User.java
  4. Todo.java
  5. 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 のみです。

Spring Boot では、Entity で変数として定義したカラムしか取得できません。


各アノテーション(@が付いてる箇所)に関しても少し解説します。

@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 に付与したことで追加された関数です。

この関数はキャメルケースで生成されます。

反対に、各変数に値を設定する @Setter もありますが、@AllArgsConstructor と役割が被るため、今回は採用していません。

個人的には、1回の宣言で全変数にまとめて代入できる @AllArgsConstructor の方が使いやすいと感じています。

@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 エンティティにデフォルトコンストラクタを追加することでエラーが発生しないように調整しています。

なお、このエラーは @AllArgsConstructor を付与するケースで発生します。

後ほど解説する @RequiredArgsConstructor を付与するケースでは、コンストラクタをエンティティ内に明記するため、@NoArgsConstructor は不要です。

@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;

テーブルの結合操作が必要なテーブルにのみ付与します。

user(多) テーブルから authority(1) テーブルを結合するため、user テーブルと対になる User エンティティには、このアノテーションが必要です。

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 エンティティには、このアノテーションを付与しています。

逆に、多側のテーブルから1側のテーブルを結合する場合、@OneToMany は不要です。

この場合は User エンティティのように @ManyToOne を多側に付与するだけです。

また、todoCategories 変数は本来ならリスト型にしたいのですが、DB データ取得時にエラーとなるため Set 型に変更しています。

比較的に知れ渡っている解決方法らしいので、詳細を知りたい人は↓で調べてみてください。

MultipleBagFetchException cannot simultaneously fetch multiple bags

@JsonManagedReference

マッピングしたエンティティ間の循環参照を防ぐために付与するアノテーションです。

この循環参照問題は、@OneToMany を用いたときに発生します。

そのため、下記の参考サイトと同様の対策を取ります。

基本的には、

  • @OneToMany側に@JsonManagedReference
  • @ManyToOne側に@JsonBackReference

を付与することで解決します。

この問題の解決策は他にも色々あります。

今回の解決法が絶対という訳でもないので、詳細は触れません。

「SpringBoot @OneToMany 循環参照」

で検索すれば該当記事が沢山ヒットするので、好きな策を選べばよいと思ってます。

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回使用しています。

  1. 多側(TodoCategory)から1側(Category)のテーブルを結合するので category 変数に @ManyToOne を付与。
  2. 先ほどの Todo エンティティから @OneToMany で参照されている todo 変数に @ManyToOne を付与。

まとめ

SQL テーブルに対応するエンティティを作成しました。

main/…/repository/entity

  1. フォルダ内の全ファイル

プロジェクト構成は GitHub にプッシュしています。

GitHub - o-zack-0390/SpringBootTodoServer: Todo データを処理するSpringプロジェクト
Todo データを処理するSpringプロジェクト. Contribute to o-zack-0390/SpringBootTodoServer development by creating an...

今回作成したエンティティは、

  • データベース操作を行う Repository
  • 業務ロジックを記載する Service
  • フロント側との窓口となる Controller

など、様々な場面で使用することになります。

次回はデータベース操作を行う Repository を作成します。

タイトルとURLをコピーしました