背景
Spring Boot初学者の私は、データがどのような流れでDBに保存されるのかの理解に
苦しんでいました。
今回はユーザー登録を例に、どのような流れでデータがDBに登録されるのかを説明します。
※コードは必要な箇所のみ記述しています。ご了承ください。
DBにデータが渡るまでのおおまかなステップ
① 画面の入力フォームでユーザー情報を入力
② Controller でユーザー情報を受け取る
③ Service にユーザー情報を渡す
④ ユーザー情報をEntity に変換する
⑤ ユーザー情報をDB に保存する
具体的な流れ
ここからは、上記のステップを実現するための作業手順を説明します。
・Modelを作成
MVCの「M」にあたる、Modelを作成していきます。
Modelは、画面(View)に表示するためのデータを入れる役割があります。
modelディレクトリ配下にUserForm.javaを作成します。
public class UserForm {
private Integer id;
@NotBlank(message = "名前は必須です") //バリデーション
private String name;
@NotBlank(message = "メールアドレスは必須です") //バリデーション
@Email(message = "メールアドレスの形式が正しくありません") //バリデーション
private String email;
@NotBlank(message = "パスワードは必須です") //バリデーション
@Size(min = 8, message = "パスワードは8文字以上で入力してください") //バリデーション
private String password;
private String profile;
}
・Entityを作成
Entity とはデータベースのテーブル構造を表したオブジェクトです。
Entity 配下にUserRegistration.javaを作成します。
@Entity
// UserRegistrationテーブル作成
public class UserRegistration {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主キーをDBのAUTO_INCREMENTで自動採番する(INSERT時にDBがIDを生成する)
private Integer id; //idカラム
private String name; //nameカラム
private String email; //emailカラム
private String password; //passwordカラム
private String profile; //profileカラム
}
・Repositoryを作成
Repositoryは、Entity(UserRegistration) と DBを繋ぐために必要です。
repositoryディレクトリ配下にインターフェースを作成します。
//Entity(UserRegistration) と DBを繋ぐインターフェース
public interface UserRepository extends JpaRepository<UserRegistration, Integer> {
boolean existsByEmail(String email); //入力したemailはDBにすでに存在するかを確認
}
Entity(UserRegistration) をDB に保存(save)したり、取得(findById, findAll)したり、
削除(delete)したりするための “DB操作専用クラス”となります。
Spring Data JPA がSQL を自動生成して実行してくれるので、例えば、データを保存したいときは以下の記述をするだけでSQLを実行し、保存してくれます。
userRepository.save(user);
→ INSERT SQL を自動生成
・Controllerを作成
MVCの「C」に当たる部分、Controllerを作成します。
POSTメソッドが実行されると、Controllerで情報を受け取り、その後Serivceへと情報が渡ります。
Serviceは、後述しますが、情報の処理を行うビジネスロジック部分と理解しておいてください。
@Controller
public class RegisterController {
@Autowired
private UserService userService;
// 登録フォームの表示
@GetMapping("/register")
public String showForm(Model model) {
model.addAttribute("userForm", new UserForm());
return "register";
}
//登録
@PostMapping("/register")
public String register(
// ★ HTMLフォーム(userForm)から送信された値を
// ★ UserForm の新しいインスタンスにバインドし、form という変数名で受け取る
// ★ @Valid:UserForm に書いたバリデーションを実行する
// ★ "userForm":Thymeleaf の th:object="${userForm}" と紐づく名前
@Valid @ModelAttribute("userForm") UserForm form,
BindingResult bindingResult,
RedirectAttributes redirectAttributes,
Model model
) {
// バリデーションエラー時に入力画面に戻す
if (bindingResult.hasErrors()) {
return "register";
}
try {
// ★ service に情報丸投げ
userService.register(form);
redirectAttributes.addFlashAttribute("message", "ユーザー登録が完了しました!");
return "redirect:/izakayas";
} catch (IllegalArgumentException e) {
redirectAttributes.addFlashAttribute("error", e.getMessage());
return "redirect:/register";
}
}
}
重要となる箇所を説明します。
以下は、GET /registerで空のフォームを渡している部分です。
この画面では userForm という名前で “空のフォームの入れ物” を作成しています。
//Modelを作成した際に定義したUserFormクラスから、new UserForm() で新しいオブジェクト(空のフォーム)を作り、それを "userForm" という名前で HTML に渡す
model.addAttribute("userForm", new UserForm());
流れを図にすると、以下になります。
[Controller GET]
model.addAttribute("userForm", new UserForm());
↓
Viewへ渡す
↓
[HTML register.html]
<form th:object="${userForm}">
↑ 表示のためには“空の入れ物” が存在する必要あり。htmlファイルは後ほど作成
次に POST /registerを使用し、入力されたデータをformに詰めていきます。
@ModelAttribute("userForm") UserForm form,
POST /register が呼ばれると、Spring は送信されたフォームの値(name, email, password など)を読み取り、新しいインスタンス "UserForm" に自動でバインドします。
@ModelAttribute("userForm") は、このバインドされた "UserForm" を"userForm" という名前で扱うための仕組みで、HTML 側のth:object="${userForm}"と紐づく役割を持ちます。
例えば、以下の情報を入力し、登録ボタンを押すと、POSTメソッドが動作し、Spring Boot 内部では以下の動作が実行されます。
ユーザー入力:
名前 → 田中
メール → aaa@gmail.com
パスワード → 1234
プロフィール → よろしく
POSTされると Spring がこうする👇
UserForm form = new UserForm();
form.setName("田中");
form.setEmail("aaa@gmail.com");
form.setPassword("1234");
form.setProfile("よろしく");
そして、以下の記述により、POSTされたデータはServiceによって処理されます。
// ★ service に情報丸投げ
userService.register(form);
流れを図にすると以下です。
1. HTML(name="name", "email" など) が POST される
2. @ModelAttribute("userForm") に従って UserForm のオブジェクトを作る
3. そのオブジェクトを "form" という変数名でServiceに渡す
GETメソッドとPOSTメソッド両方にAttribute という記述が出てきてややこしいですが、まとめるとこうです。
① GET の model.addAttribute は「画面表示用に空のオブジェクトを渡すだけ」
② POST の @ModelAttribute は「送信された値を詰めた新しいオブジェクトを生成して渡す」
ここまでで「ステップ② Controller でユーザー情報受け取る 」と「ステップ③ Service にユーザー情報を渡す」ための準備ができました。
・HTMLを作成
以下の記述をhtmlに記述し、入力フォームを作成します。
<div class="form-card">
<form th:action="@{/register}" th:object="${userForm}" method="post">
<table>
<tr>
<td><label>名前:</label></td>
<td><input type="text" th:field="*{name}" /></td>
</tr>
<tr>
<td><label>メールアドレス:</label></td>
<td><input type="text" th:field="*{email}" /></td>
</tr>
<tr>
<td><label>パスワード:</label></td>
<td><input type="text" th:field="*{password}" /></td>
</tr>
<tr>
<td><label>プロフィール:</label></td>
<td><input type="text" th:field="*{profile}" /></td>
</tr>
</table>
<input type="submit" value="登録する" />
</form>
</div>
ここで先ほど作成したuserFormオブジェクトを使用しています。
<!-- このフォームの入力値を userForm(Modelのオブジェクト)に紐づけて送信する -->
<form th:action="@{/register}" th:object="${userForm}" method="post">
<table>
<tr>
<td><label>名前:</label></td>
<!-- userForm.name に対応した入力欄(POST時にUserForm.nameに値が入る)-->
<td><input type="text" th:field="*{name}" /></td>
</tr>
これで入力フォームが完成です。登録ボタンを押すと、POSTメソッドが実行され、Spring が送信されたフォームデータを UserForm にバインドします。
Controller はこの UserForm を受け取り、バリデーションエラーがある場合、入力値とエラー情報が model に入り、同じ画面 (register.html) を表示し、エラー文を表示。
エラーがなければ、Controller → Service を呼び出す → Service で Entity を作成 → Repository.save() で DB に保存を実行します。(Serviceはこの後作成します!)
これで「ステップ① 画面の入力フォームでユーザー情報を入力」のための準備完了です。
・Serviceを作成
ビジネスロジック部分のServiceを作成していきます。
Service は「Controller と Repository の間に入り、ビジネスロジック(処理の流れ)を担当する部分」です。Controller は極力ロジックを持たず、Service に処理を委譲します。
package com.example.izakaya.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.izakaya.entity.UserRegistration;
import com.example.izakaya.model.UserForm;
import com.example.izakaya.repository.UserRepository;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
// ユーザー登録処理
public void register(UserForm form) {
// 重複チェック(email重複はエラー)
if (userRepository.existsByEmail(form.getEmail())) {
throw new IllegalArgumentException("このメールアドレスはすでに使われています");
}
// Form → Entity へ変換
UserRegistration user = new UserRegistration();
user.setName(form.getName());
user.setEmail(form.getEmail());
user.setPassword(passwordEncoder.encode(form.getPassword()));
user.setProfile(form.getProfile());
// 保存
userRepository.save(user);
}
}
以下では、DB内のemailを確認し、重複があればエラーが起きるように設定しています。
// 重複チェック(email重複はエラー)
if (userRepository.existsByEmail(form.getEmail())) {
throw new IllegalArgumentException("このメールアドレスはすでに使われています");
}
以下では、「ステップ④ ユーザー情報をEntity に変換する」ために、HTMLから送られてきた入力値をDBに保存できる形に変換しています。
// Form → Entity へ変換
UserRegistration user = new UserRegistration();
user.setName(form.getName());
user.setEmail(form.getEmail());
user.setPassword(passwordEncoder.encode(form.getPassword()));
user.setProfile(form.getProfile());
そして最後、「ステップ⑤ ユーザー情報をDB に保存する」ために、以下で変換した値をDBに保存します。
// 保存
userRepository.save(user);
以上の作業で、
① 画面の入力フォームでユーザー情報を入力
② Controller でユーザー情報受け取る
③ Service にユーザー情報を渡す
④ ユーザー情報をEntity に変換する
⑤ ユーザー情報をDB に保存する
が可能となります!