今回はJava SpringFWを用いて登録・更新・削除機能を実装してみました。
バリデーションについては、JPAではなくJDBCtemplateを使用しております。
以下はそれぞれに分けて解説をしておりますので、よければ参考にしてください!!
• 【更新】Spring JDBCで更新機能の実装(作成中)
• 【削除】Spring JDBCで削除機能の実装(作成中)
• 【まとめ】Spring JDBCで登録・更新・削除(作成中)
• 【エラー解消】Spring JDBCでエラーメッセージが消えない!?(作成中)
0, 目次
1, 完成品 2, 環境 3, 関連ソースコード ① Form と entity ② Controller ③ Service ④ Impl と Custom と Repository ⑤ HTML と JavaScript ⑥ messages.properties と ValidationMessages.properties ⑦ pom.xml4, 参考にしたサイト
1, 完成品
ユーザー登録までの流れ
本記事では、A5SQLを用いてDBにユーザーを登録していきます。 HTMLではリストを使って初期値としてユーザーを表示させています。 そのため、事前にテストデータとしてEmployeesテーブルを作成しました。 参考までに:[【SQL】テーブルとレコード(登録処理用)](https://qiita.com/nakasan773/items/598c15046e86e70dac47) ユーザーの登録や更新もこちらのテーブルを使っていきます!2, 環境
Windows10 sts-4.10.0.RELEASE A5SQL Mk-2 Version2.16.1〜前提として〜
・Spring boot(STS)が導入されていること
・SQLを用いたデータベースの簡単な知識(insert,updateなどのSQL文)
・デバックができること
3, 関連ソースコード
① Form と entity
package jp.co.nakasan.test.database.dbtest;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.stereotype.Component;
import lombok.Data;
@Data
@Component
public class EmployeesForm {
/**
* radio
*/
private String radio;
/**
* id
*/
private String id;
/**
* 名前
*/
// nullまたは空文字でないことをチェック
@NotBlank(message = "{notblank_check}")
// 最小値・最大値を指定(@size,@maxだと常に判定される)
@Length(min = 0, max = 100, message = "{length_check}")
private String name;
/**
* 年齢
*/
@NotBlank(message = "{notblank_check}")
@Length(min = 0, max = 3, message = "{length_check_age}")
private String age;
/**
* 誕生日
*/
@NotBlank(message = "{notblank_check}")
// 誕生日の正規表現を指定
@Pattern(regexp = "([1-2]{1}[0-9]{3}/[0-1]{1}[0-9]{1}/[0-3]{1}[0-9]{1}|[1-2]{1}[0-9]{3}-[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1})", message = "{pattern_check_birthday}")
private String birthday;
/**
* メールアドレス
*/
@Length(min = 0, max = 100, message = "{length_check}")
// メールの正規表現を指定
@Email
private String eMail;
/**
* 住所
*/
@Length(min = 0, max = 100, message = "{length_check}")
private String address;
/**
* グループ
*/
@Length(min = 0, max = 100, message = "{length_check}")
private String groupId;
}
今回は、BindingResultクラスを使用するため、@NotBlankのようにバリデーションをかけています。
message = "{notblank_check}" はエラーメッセージの日本語化に対応させるため、名前をつけています。
(あとでpropertiesファイルに紐付けます。)
また、@DateTimeFormatというアノテーションも使えるらしいのですが、フィールドの型をlocalDate(日付時刻型)にする必要があるとのこと。。今回は大人しく自作の正規表現でパターン化しました😂
package jp.co.nakasan.test.database.entity;
import java.io.Serializable;
import java.sql.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.sun.istack.NotNull;
import lombok.Data;
@SuppressWarnings("serial")
@Data // ゲッターとセッターが不要になる
@Entity // Entityクラスの宣言
@Table(name = "employees", schema = "データベース名") // テーブルと紐づけ
public class Employees implements Serializable{
/**
* 社員id
*/
@Id // プライマリキー
@Column(name = "id") // データベース上のカラムとのマッピングを指定
private Integer id;
/**
* 名前
*/
@Column(name = "name")
@NotNull
private String name;
/**
* 年齢
*/
@Column(name = "age")
@NotNull
private Integer age;
/**
* 生年月日
*/
@Column(name = "birthday")
@NotNull
private Date birthday;
/**
* e-mail
*/
@Column(name = "email")
private String email;
/**
* 住所
*/
@Column(name = "address")
private String address;
/**
* グループ
*/
@Column(name = "groupid")
private String groupId;
}
Entityクラスではデータベースの使用するテーブルとを紐づけています。
物理名ではbirthDayになっているが、マッピングするSpringPhysicalNamingStrategyクラスではクラス内のすべてを小文字変換しているので、大文字小文字を区別する必要があるようです。groupIdも同様です。
② Controller
package jp.co.nakasan.test.practicebase.dbtest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping(path = "/valiSpring")
public class EmployeesController {
@Autowired
private EmployeesForm form;
@Autowired
private EmployeesService service;
/**
* 初期画面
* @param model // 画面表示
* @return
*/
@RequestMapping(path = "", method = RequestMethod.GET)
public String index(Model model) {
model.addAttribute("List", service.getSelectedJudges());
model.addAttribute("employeesForm", form);
return "practice/valiSpring";
}
/**
* データの表示
* @param EmployeesForm
* @param model // 画面表示
* @param BindingResult // バリデーション
* @param proc // Jsのクリックイベント"select"または"button"
* @return
*/
@RequestMapping(path = "{proc}", method = RequestMethod.POST) // (form, bindingresult, model)の順でないとエラー
public String pushSend(@PathVariable String proc, @Validated EmployeesForm inputForm, BindingResult result, Model model)
{
// セレクトボックス押下時
if (proc.equals("select")) {
// ユーザー情報取得
this.form = service.getSelectedInfoData(inputForm);
model.addAttribute("employeesForm", this.form);
// 送信ボタン押下時 "null"
} else if (inputForm.getRadio() == null) {
// 入力値の表示(BindingResult対策)
this.form = service.keepForm(inputForm);
// 選択チェックエラー
model.addAttribute("messageList", service.getSelectedNull());
model.addAttribute("employeesForm", this.form);
// 送信ボタン押下時 "insert"
} else if ((inputForm.getRadio().equals("insert")) && (!(result.hasErrors()))) {
// ユーザー新規登録 & 入力チェックエラー
model.addAttribute("messageList", service.getSelectedInsert(inputForm));
// 送信ボタン押下時 "update"
} else if ((inputForm.getRadio().equals("update")) && (!(result.hasErrors()))) {
--省略--
// 送信ボタン押下時 "delete"
} else if (inputForm.getRadio().equals("dalete")) {
--省略--
}
model.addAttribute("List", service.getSelectedJudges());
return "practice/valiSpring";
}
}
path = "{proc}"で「select」と「button」のどちらか一方のパラメータを受け取れるようにしています。
こうすることで、ユーザーが画面内のどっちのボタンを押したのかを判定できるようにしています。(jsファイルで定義)
更新、削除処理については別記事で紹介してますので、本記事では割愛させて頂きますっ。🙇♂️
③ Service
package jp.co.nakasan.test.practicebase.dbtest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jp.co.nakasan.test.database.entity.Employees;
import jp.co.nakasan.test.database.repository.EmployeesRepository;
@Service
// データベースのトランザクション制御
@Transactional
public class EmployeesService {
@Autowired
EmployeesRepository repository;
/**
* 全ユーザー情報取得処理
* @param
* @return employees // 全ユーザーのデータ(id,名前,年齢,誕生日)
*/
public List<Employees> getSelectedJudges() {
// 全ユーザー情報取得用変数リスト
List<Employees> employees = null;
// 全ユーザー情報取得(id,名前のみ)
employees = repository.findEmployees();
return employees;
}
/**
* 入力値を初期化させない処理
* @param EmployeesForm
* @return form // 入力値をそのまま返す
*/
// バリデーションエラーメッセージの隔離メソッド
public EmployeesForm keepForm(EmployeesForm inputForm) {
// formの初期化(別formの生成)
EmployeesForm form = new EmployeesForm();
// 入力初期値の保存
form.setRadio(inputForm.getRadio());
form.setId("0");
form.setName(inputForm.getName());
form.setAge(inputForm.getAge());
form.setBirthday(inputForm.getBirthday());
form.setEMail(inputForm.getEMail());
form.setAddress(inputForm.getAddress());
form.setGroupId(inputForm.getGroupId());
return form;
}
/**
* ユーザー情報取得処理(1件)
* @param EmployeesForm
* @return
*/
public EmployeesForm getSelectedInfoData(EmployeesForm inputForm) {
Employees employeesList = null;
// formの初期化
EmployeesForm form = new EmployeesForm();
// 選択されたユーザーID
String selectedId = inputForm.getId();
// ユーザーを選択していない場合
if (selectedId.equals("0")) {
form.setId("0");
form.setName("");
form.setAge("");
form.setBirthday("");
form.setEMail("");
form.setAddress("");
form.setGroupId("");
} else {
// ユーザーidで情報取得
employeesList = repository.findInfoData(selectedId);
// フォーマットの指定
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
// Date型をString型に変換
String Birthday = sdf.format(employeesList.getBirthday());
form.setRadio(inputForm.getRadio());
form.setId(employeesList.getId().toString());
// nullを許可する
// toString()だとnullが入ってしまう
form.setName(Objects.toString(employeesList.getName(), ""));
form.setAge(Objects.toString(employeesList.getAge(), ""));
form.setBirthday(Objects.toString(Birthday, ""));
form.setEMail(Objects.toString(employeesList.getEmail(), ""));
form.setAddress(Objects.toString(employeesList.getAddress(), ""));
form.setGroupId(Objects.toString(employeesList.getGroupId(), ""));
}
return form;
}
/**
* ラジオボタン判定
* @param
* @return list // エラーメッセージリスト
*/
public List<String> getSelectedNull() {
List<String> list = new ArrayList<>();
list.add("新規登録、更新、削除のいずれかを選択してください。");
return list;
}
/**
* ユーザー新規登録処理
* @param EmployeesForm
* @return list // エラー&登録結果の判定リスト
*/
public List<String> getSelectedInsert(EmployeesForm inputForm) {
// insert用変数
Employees employees = new Employees();
// エラーメッセージ用リスト
List<String> list = new ArrayList<>();
// フォーマットを指定
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
// Date型の初期化
Date formatDate = null;
// yyyy/MM/ddにフォーマット変換
String formDate = inputForm.getBirthday().replace('-', '/');
try {
// String型からDate型に変換
formatDate = sdf.parse(formDate);
// java.util.Dateは日付型のクラス
// 日付型のクラスに変換
java.util.Date d1 = formatDate;
// java.sql.date は java.util.dateクラスのサブクラス
// サブクラスに変換(時分秒ミリ秒まで保存可)
java.sql.Date d3 = new java.sql.Date(d1.getTime());
employees.setName(inputForm.getName());
employees.setAge(Integer.parseInt(inputForm.getAge()));
employees.setBirthday(d3);
// 空文字じゃなかったら実行(空文字だったらnullとして登録)
if (!(inputForm.getEMail() == "")) {
employees.setEmail(inputForm.getEMail());
}
if (!(inputForm.getAddress() == "")) {
employees.setAddress(inputForm.getAddress());
}
if (!(inputForm.getGroupId() == "")) {
employees.setGroupId(inputForm.getGroupId());
}
// ユーザー登録処理
int rowNumber = repository.insertRecord(employees);
// デフォルトで"false"を設定
boolean result = false;
if (rowNumber > 0) {
// insert成功
result = true;
}
// ユーザー登録結果の判定
if (result == true) {
list.add("新規登録完了");
}
} catch(Exception e) {
list.add("データベースに問題がありました。入力しなおしてください。(登録処理)");
}
return list;
}
--更新処理--
--削除処理--
}
Serviceクラスでの主な処理は以下の通りです。
「全ユーザー情報取得処理」・・・セレクトボックスで各ユーザー選択時に使用
「入力値を初期化させない処理」・・・イベント時に入力値をそのままキープさせる
「ユーザー情報取得処理(1件)」・・・選択したユーザー(1件)のデータ全てを取得
「ラジオボタン判定処理」・・・エラーメッセージの作成
「ユーザー新規登録処理」・・・ユーザー登録処理
判定メッセージについては各メソッドごとにリストを生成し、出力しています。
④ Impl と Custom と Repository
package jp.co.nakasan.test.database.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import jp.co.nakasan.test.database.entity.Employees;
import jp.co.nakasan.test.database.repository.custom.EmployeesRepositoryCustom;
/**
* カスタムインターフェイスとJpaRepositoryを継承(extends)したもの。
* @author なかさん。
*
*/
@Repository
public interface EmployeesRepository extends JpaRepository<Employees, Integer>, EmployeesRepositoryCustom {
}
基本JPAリポジトリという便利なものでDBからデータなどの情報を取得できるわけですが、今回使用しているJdbcTemplateはJPAでは取得できないような状況にも対応できる(自分で好きにカスタムして取得できる)とのことで、つまりはJPAより自由度が高いということですね。時と場合により使い勝手が良さそうです。🤔
JdbcTemplateはのちにでてくるImplクラスに記述します。
そして、JPA以外はカスタムインターフェースの継承が必要です。
厳密には必要なわけではありませんが、JDBCのメソッドを都度列挙しなくて済むようになるので必然かと。
Repositoryはそれらの継承のためのファイルです。
継承イメージとしては
Repositoryクラス > JpaRepositry
Repositoryクラス > Customクラス(インターフェース) > Implクラス < JdbcTemplate
みたいな感じです。
package jp.co.nakasan.test.database.repository.custom;
import java.util.List;
import jp.co.nakasan.test.database.entity.Employees;
/**
* implの継承
* @author なかさん。
*
*/
public interface EmployeesRepositoryCustom {
/**
* @param
*/
public List<Employees> findEmployees();
/**
* @param id
*/
public Employees findInfoData(String id);
/**
* @param employees
*/
public int insertRecord(Employees employees);
}
カスタムクラスではimplクラスを継承します。
implで作成したメソッドをそのままコピペするだけで終了です。^^;
逆にカスタムに持ってこないと、せっかくimpl内で書いたメソッドがそのままの状態で放置プレイされてしまいます汗
package jp.co.nakasan.test.database.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import jp.co.nakasan.test.database.entity.Employees;
import jp.co.nakasan.test.database.repository.custom.EmployeesRepositoryCustom;
/**
* JDBCtemplateでレコード取得
* @author なかさん。
*
*/
@Component
public class EmployeesRepositoryImpl implements EmployeesRepositoryCustom {
@Autowired
JdbcTemplate jdbc;
/**
* レコードの取得(全ユーザーのid,名前のみ)
* @param
* @return
*/
public List<Employees> findEmployees() {
// 返却値 初期化
List<Employees> resultRec = null;
RowMapper<Employees> rowMapper = new BeanPropertyRowMapper<Employees>(Employees.class);
// --------------------
// クエリー生成
// --------------------
StringBuffer query = new StringBuffer();
query.append(" SELECT");
query.append(" e.id, ");
query.append(" e.name ");
query.append(" FROM");
query.append(" employees e ");
// データベースからデータをリストで取得
List<Employees> list = jdbc.query(query.toString(), rowMapper);
resultRec = list;
return resultRec;
}
/**
* レコードの取得(ユーザー1件のレコード全て)
* @param id
* @return
*/
public Employees findInfoData(String id) {
// 返却値 初期化
Employees resultRec = null;
RowMapper<Employees> rowMapper = new BeanPropertyRowMapper<Employees>(Employees.class);
// --------------------
// クエリー生成
// --------------------
StringBuffer query = new StringBuffer();
query.append(" SELECT");
query.append(" e.id, ");
query.append(" e.name, ");
query.append(" e.age, ");
query.append(" e.birthDay, ");
query.append(" e.email, ");
query.append(" e.address, ");
query.append(" e.`groupId` ");
query.append(" FROM");
query.append(" employees e ");
query.append(" WHERE");
query.append(" e.id = ? ");
// フォームのidとテーブルのidを紐づけるためリスト化
ArrayList<String> paramList = new ArrayList<String>();
paramList.add(id);
// データベースからデータをリストで取得
List<Employees> list = jdbc.query(query.toString(), rowMapper, paramList.toArray());
resultRec = list.get(0);
return resultRec;
}
/**
* ユーザー新規登録
* @param employees entityクラス
* @return
*/
public int insertRecord(Employees employees) {
// 返却値
int query;
// --------------------
// クエリー生成
// --------------------
query = jdbc.update("INSERT INTO"
+ " employees "
+ "(name,"
+ " age,"
+ " birthDay,"
+ " email,"
+ " address,"
+ " groupId)"
+ " VALUES(?, ?, ?, ?, ?, ?)",
employees.getName(),
employees.getAge(),
employees.getBirthday(),
employees.getEmail(),
employees.getAddress(),
employees.getGroupId());
return query;
}
--更新処理--
--削除処理--
}
Implクラスでの主な処理は以下の通りです。
・ レコードの取得(全ユーザーのid,名前のみ)
・ レコードの取得(ユーザー1件のレコード全て)
・ ユーザー新規登録
新規登録処理が成功すれば、返却値queryに1が
失敗すれば、返却値queryに0が代入されます。
この値はServiceクラスでboolean型に変換され、処理結果メッセージの判定として使います。
⑤ HTML と JavaScript
<body>
<div class="contents">
<form id="employeesForm" th:object="${employeesForm}" method="POST">
<main class="ml-3 mt-3" th:fragment="init" role="main">
IDは自動採番です。新規登録時,IDを選択しても反映されません。
<br>
再度「選択してください」を押すと入力値がクリアされます。
<!--判定メッセージを出す-->
<div class="0" style="color: green;">
<tr th:each="name : ${messageList}">
<br>
<td th:text="${name}">
</tr>
</div>
<!--エラーメッセージを出す-->
<div class="0" style="color: brown;" th:if="${#fields.hasErrors('*')}">
<tr th:each="err : ${#fields.errors('*')}">
<br>
<td th:text="${err}">
</tr>
</div>
<!--登録用のラジオボタンを表示-->
<p>
<div class="1">
<input type="radio" name="radio" th:value="insert" th:field="*{radio}">新規登録
<input type="radio" name="radio" th:value="update" th:field="*{radio}">更新
<input type="radio" name="radio" th:value="delete" th:field="*{radio}">削除
</div>
</p>
<!--IDのセレクトボタン-->
<div class="2">
<select name="employeesId" th:field="*{id}" th:value="*{id}">
<option value="0">--- 選択してください ---</option>
<option th:each="list : ${List}" th:value="${list.id}" th:selected="${list.id == id}" th:inline="text">[[${list.id}]] : [[${list.name}]]</option>
</select>
</div>
<!--削除時以外の必須項目-->
<div class="3 mt-3">
<label style="width: 90"> 名前 </label>
<input style="width: 120" name="name" th:field="*{name}" th:value="*{name}"/>
<label style="width: 170"> 年齢 </label>
<input style="width: 120" name="age" th:field="*{age}" th:value="*{age}"/>
<label style="width: 300"> 生年月日 </label>
<input style="width: 120" name="birthday" th:field="*{birthday}" th:value="*{birthday}"/>
</div>
<!--項目-->
<div class="4 mt-3">
<label style="width: 90"> e-mail </label>
<input style="width: 200" name="eMail" th:field="*{eMail}" th:value="*{eMail}"/>
<label style="width: 90"> 住所 </label>
<input style="width: 550" name="address" th:field="*{address}" th:value="*{address}"/>
</div>
<!--項目-->
<div class="5 mt-3">
<label style="width: 90"> グループ </label>
<input style="width: 70" name="groupId" th:field="*{groupId}" th:value="*{groupId}"/>
<button class="ml-4" name="send01" th:name="send">送信</button>
</div>
</main>
</form>
</div>
</body>
ここでもしBindingResultのバリデーションメッセージが表示されないとき、form名を疑ってください。
私が初めて実装したとき、form名はValiSpringFormで、エラーメッセージはformに「default messages={0}は必須項目です。」と入っているのにうまく表示できませんでしたが、結論としてValiSpringForm → valiSpringFormと変更するとうまくいきました!(頭文字Vを小文字に変更)
原因はBindingResultに入っているメッセージの中にform名が書いてあり、それがHTML側のform名と一致していなかったため表示されなかったみたいです。
おそらくクラス内でバリデーションチェックを行う際、頭文字が小文字へ強制的に変換されてしまうのでしょう。
なので、私の時はHTML,JavaScript,Controllerのform名とバリデーション結果のform名を一致させるとうまく表示できるようになりました!ぜひ参考までに。🙇♂️
var practice = (function() {
var global = {
}; // 名前空間オブジェクトを private オブジェクトで定義
/**
* ページロード時に実行される処理。
*/
window.onload = function() {
init();
};
function init() {
// セレクトボタン押下時
$('select[name="id"]').change(function() {
// 現在アクセスしているパス情報を取得
var rootPath = main.getDir(location, 0);
// 遷移先への処理
var form = $("#employeesForm");
// "select"パラメータを渡す
form.attr("action", rootPath + "/valiSpring/" + "select");
form.attr("target", "_self");
form.submit();
});
// 送信ボタン押下時
$('button[name="send"]').click(function() {
// 現在アクセスしているパス情報を取得
var rootPath = main.getDir(location, 0);
// 遷移先への処理
var form = $("#employeesForm");
// "buttom"パラメータを渡す
form.attr("action", rootPath + "/valiSpring/" + "button");
form.attr("target", "_self");
form.submit();
});
}
return global; // 名前空間オブジェクトを return する
})();
Controller側でpath="{proc}"というようにパラメータを受け取る準備ができています。
あとは、selectやbuttonのように任意のパラメータをコントローラに渡し、イベントの処理分けをするといった形です。
⑥ messages.properties と ValidationMessages.properties
--省略--
# フィールド名の日本語化
employeesForm.name = 名前
employeesForm.age = 年齢
employeesForm.birthday = 生年月日
employeesForm.eMail = メールアドレス
employeesForm.address = 住所
employeesForm.groupId = グループ名
ここではバリデーションの日本語化をしています。
後でこちらのファイルを開くと
employeesForm.name = \u540d\u524d
のように文字化けしてしまいますが、問題なく処理されますので気にしなくて大丈夫です!
# 必須項目チェック
notblank_check = {0}は必須項目です。
# 字数制限チェック
length_check = {0}は100字以内で入力してください。
length_check_age = 年齢は3桁以内で入力してください。
# 正規表現チェック
pattern_check_birthday = 生年月日はyyyy/MM/ddまたはyyyy-MM-ddの形で入力してください。
pattern_check_eMail = メールアドレスを正しく入力してください。
Formで{messages=...}と名前をつけたものと先程の日本語化したフィールド名を紐づけるため、resorceフォルダ直下に「ValidationMessages.properties」という名前でプロパティファイルを作成します。
⑦ pom.xml
下記は参考までに。<project>
--省略--
<!-- validationライブラリ -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
--省略--
</project>
以上でユーザー登録処理の完了です🌟
他にもここでは解説しきれなかった更新・削除処理も執筆しておりますので、よければご覧ください!
また、なにか気になる点や間違っているとこなどがあれば遠慮なくご指摘ください...!!
まだまだ勉強中ですが、本記事が何かの参考になればとても幸いです。