はじめに
自身の知識のアウトプットも兼ねて、新人研修用に作成した記事となります。Spring Bootを学び始めた方を対象とした内容になっています。
新規登録画面の作成と表示については、前回の記事を参照ください。
概要
企業情報の登録機能を作成していきます。
クライアントから渡ってきた企業情報をDBに追加し、最後に登録完了画面を返す機能を作成します。
完成イメージ
パッケージ構成
赤枠で囲ったパッケージ、クラス、HTMLファイルを今回作成していきます。また、青枠で囲ったクラスを編集していきます。
Entityクラスの作成
このクラスは、データベースのテーブルをそのままミラーリングしたオブジェクトです。要は、データベースのテーブルのカラムと連携させる変数(フィールド)を持つクラスです。
ここでは、Formクラスがクライアントとのデータの受け渡しを担当するのに対し、Entityクラスはデータベースとのデータの受け渡しを担当します。
配置先:src > main > java > com > example > demo > entity > Company.java
package com.example.demo.entity;
import java.util.Date;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long companyId; // 企業ID
@NotBlank
private String name; // 会社名
@NotNull
private int employees; // 従業員数
@Temporal(TemporalType.DATE)
private Date establishmentDate; // 設立日
}
@Entity
このクラスがJPAで利用するEntityクラスであることを宣言します。
データベースの1行と対になっていることを示します。
@Id
主キーを指定します。
@GeneratedValue(strategy = GenerationType.IDENTITY)
この記述により自動採番してくれます。MySQLの場合はAuto Increment(AI)として設定されます。
@NotBlank
必須チェックのためのアノテーションです。Null、空文字、半角スペースが許可されないことを示します。
@NotNull
必須チェックのためのアノテーションです。NotNullであることを示します。
@Temporal
日付型のオブジェクトの型を指定します。ここでは日付の部分のみ指定しています。(TemporalType.DATE
)
Entityクラスを作成することで、下記のように接続情報がデータベースの自動更新を行う設定になっていれば、アプリケーション起動時に自動でEntityの定義に基づいてテーブルを作成してくれます。
# Spring-JPA: DBのテーブルを自動作成してくれる機能
spring.jpa.hibernate.ddl-auto=update
試しにアプリケーションを起動してみると...
sample
データベースにEntityクラスで指定した定義に基づいてcompany
というテーブルが自動で作成されました。
補足:
今回はEntityクラスの定義からテーブルを自動で作成しましたが、@Table
アノテーションや@Column
アノテーションを使うことで、テーブル名やカラム名、Null制約などを明示的に指定することもできます。
@Entity
@Data
@Table(name="tablename")
public class Company {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "id")
private Long companyId; // 企業ID
@NotEmpty
@Column(name = "company_name", length = 50, nullable = false)
private String name; // 会社名
@NotNull
@Column(name = "employees", length = 10, nullable = false)
private int employees; // 従業員数
@Temporal(TemporalType.DATE)
@Column(nullable = true)
private Date establishmentDate; // 設立日
}
@Table
を省略した場合はクラス名を大文字にした名前のテーブルにマッピングされされ、@Column
を省略した場合はプロパティ名を大文字にした名前のカラムへマッピングされます。(上述の通り)
@Table
や @Column
の name属性はキャメルケース(camelCase)ではなく、スネークケース(snake_case)で書きましょう。
もしスネークケースで書く場合は、application.properties
に以下の1行を追加する必要があります。
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
この設定がないと、キャメルケースでカラム名やテーブル名を明示しても、裏で勝手にスネークケースに変換されるてしまう為、エラーの原因となります。
Repositoryクラスの作成
JpaRepositoryというインターフェイスを作成します。
これまでのクラス作成と違い、拡張インターフェイスの指定があるので注意しましょう。
配置先:src > main > java > com > example > demo > repository > CompanyRepository.java
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.demo.entity.Company;
public interface CompanyRepository extends JpaRepository<Company, Long> {
}
JpaRepositoryは型とID型を指定する必要があります。
T
は型なのでCompanyクラスを、ID
はLongを指定します。
Serviceクラスの作成
ビジネスロジックを実装します。このクラスは、コントローラーから呼び出されデータを処理します。
ここでは、コントローラーから渡ってきたFormのデータ(ブラウザからユーザーが入力した企業情報)をデータベースへ登録する為に、FormとEntity間のデータの変換を行いRepositoryクラスを呼び出します。
FormとEntity間のデータ変換の方法はいくつかありますが、今回は手動でマッピングします。明示的な変換ロジックをサービス層で記述します。
配置先:src > main > java > com > example > demo > service > CompanyService.java
package com.example.demo.service;
import java.text.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.controller.form.CompanyForm;
import com.example.demo.entity.Company;
import com.example.demo.repository.CompanyRepository;
import jakarta.transaction.Transactional;
@Service
public class CompanyService {
@Autowired
CompanyRepository repository;
@Autowired
FormatService format;
// 企業情報の新規登録
@Transactional
public Company createCompany(CompanyForm form) {
Company entity = new Company();
entity.setName(form.getCompanyName());
entity.setEmployees(Integer.parseInt(form.getEmployees()));
try {
entity.setEstablishmentDate(format.strToDate(form.getEst()));
} catch (ParseException e) {
System.err.println("Failed to convert format to date:" + e.getMessage());
e.printStackTrace();
}
return repository.save(entity);
}
}
@AutoWired
これを付与することで、DIコンテナから対象のオブジェクトを取り出します。
DIコンテナには、@Component や @Controller、@Service、@Repositoryなどが付与されたクラスのインスタンスが登録され、コンテナ内で自動生成されます。簡単に言うと、使いたいクラスをインスタンス化をしてくれるアノテーションです。
ここではCompanyRepository
クラスをrepository
というインスタンス名で、FormatService
クラスをformat
というインスタンス名で使えるように指定しています。
@Transactional
@Transactional
を使用すると、メソッドが呼び出されると同時にトランザクションが開始され、メソッドの実行が成功した場合にはコミットされ、例外が発生した場合にはロールバックされます。これにより、データの整合性が保たれます。
repository.save(entity);
Form から Entity へデータの変換が出来たら、repositoryがデフォルトで持つsave()
メソッドを利用して、EntityのデータをデータベースにINSERTします。save()はentityオブジェクトを引数にとり,それをDBに永続化します。
FormatService
FormとEntity間でデータを変換する際、今回で言うと設立日
のようなFormとEntityで型が違うデータの場合には少し工夫が必要です。
ここでは FormatService
というデータ型を変換する為のサービスクラスを作成し、文字列型 → 日付型 へ変換するロジックを定義します。
配置先:src > main > java > com > example > demo > service > FormatService.java
package com.example.demo.service;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Service;
@Service
public class FormatService {
// String → Date
public Date strToDate(String strDate) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = dateFormat.parse(strDate);
return date;
}
}
Viewの作成
thanks.htmlを作成します。
配置先:src > main > resources > templates > thanks.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>企業情報登録完了画面</title>
</head>
<body>
<main>
<h1>登録完了</h1>
<p>企業情報のご登録が完了いたしました。</p>
</main>
</body>
</html>
Controllerクラスの修正
CompanyController.javaに登録処理のメソッドを追加します。
配置先:src > main > java > com > example > demo > controller > CompanyController.java
@Controller
public class CompanyController {
@Autowired
CompanyService companyService;
// 登録フォームの表示
// 企業情報の新規登録
@PostMapping("company/register")
public String addCompany(@ModelAttribute CompanyForm companyForm, Model model) {
companyService.createCompany(companyForm);
return "thanks";
}
}
@PostMapping(path)
POSTリクエストのみをマッピングします。
@PostMappingアノテーションをつけることで、"/company/register"というURLにマッピングしています。
@ModelAttribute
Spring MVCにおいて、HTTPリクエストから受け取ったデータをモデルオブジェクトにバインドするためのアノテーションです。このアノテーションを使用することで、コントローラーのメソッドに引数として渡されたオブジェクトに、リクエストパラメータを自動的にマッピングできます。
今回は、クライアントからPOST送信されたデータをまとめてCompanyFormオブジェクトとして受け取っています。
companyService.createCompany(フォームオブジェクト);
CompanyService
クラスのcreateCompany
メソッドを呼び出しています。引数にはクライアントから渡ってきたcompanyForm
オブジェクトを渡しています。
データの流れ
登録処理のデータの流れは以下の通りです。
- ユーザーが入力フォームに企業情報を入力して送信
-
CompanyController
がフォームデータを受け取る -
CompanyService
に登録処理を依頼 -
CompanyService
が必要な業務処理を行い(FormとEntity間のデータ変換)、CompanyRepository
に保存を指示 -
CompanyRepository
がデータベースに保存 - 結果を順次上の層に返し、最終的にユーザーに結果を表示
次回はバリデーションチェックを実装していきます。
参考サイト