【Spring Boot入門】#7 更新機能を作成する

## はじめめに
自身の知識のアウトプットも兼ねて、新人研修用に作成した記事となります。Spring Bootを学び始めた方を対象とした内容になっています。

詳細画面の作成については、前回の記事を参照ください。

https://qiita.com/ioaio/items/0096af612698cc6547da

## 概要
企業情報一覧画面の「編集」リンクから更新画面へ遷移し、特定の企業に関する情報を更新できるようにします。

#### 完成イメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3917178/30a27eb8-3217-c144-a67d-ce3cb3d58f55.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3917178/3d0fe37a-0f87-0796-e2ef-d07b92aa92e1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3917178/7995987c-c561-2b8f-d577-a14caf03eaf9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3917178/4bbbda34-c91b-d95b-6393-ccc14166c573.png)

## パッケージ構成
赤枠で囲ったHTMLファイルを今回作成していきます。また、青枠で囲ったクラスを編集していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3917178/0cf9a4c3-a522-8f14-d917-f9380cd4c992.png)

## 作成手順
**1. 更新画面の作成**
先ずは、「編集」リンク押下後に表示される「企業情報更新フォーム」の作成からしていきます。

**2. 更新機能の作成**
次に、編集した内容で対象の企業情報が更新されるように更新機能を作成していきます。

## Formクラスの修正
CompanyFormに企業IDのフィールドを追加しておきます。

`配置先：src > main > java > com > example > demo > controller > Form > CompanForm.java`

```java:CompanyForm.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CompanyForm {
	/* 追加 */
	private Long companyId;	// 企業ID
}
```

## Serviceクラスの修正①
更新画面を表示する為の処理を追加していきます。
データベースから企業IDをキーに引っ張ってきた企業情報を画面に表示します。

登録処理を作成した際に、**設立日**に関して **文字列型 → 日付型** の変換をしましたが、今度は逆に **日付型 → 文字列型** へ変換してあげる必要があります。

`配置先：src > main > java > com > example > demo > service > FormatService.java`

```java:FormatService.java
@Service
public class FormatService {

	// Date → String
	public String dateToString(Date date) {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		String strDate = dateFormat.format(date);
		return strDate;
	}
}
```
`dateToString()`は、引数で渡された`Date`オブジェクトを`yyyy-MM-dd`の形式の文字列に変換するメソッドです。`SimpleDateFormat`クラスを使って、指定した形式で日付をフォーマットしています。

次に実際に登録済みの情報を画面に表示する為の処理を作成します。

`配置先：src > main > java > com > example > demo > service > CompanyService.java`

```java:CompanyService.java
@Service
public class CompanyService {

	// 企業情報更新フォームの準備（登録済み企業情報の取得）
	@Transactional
	public CompanyForm getEditCompany(Long companyId) {
        // データベースから情報を取得
		Optional<Company> companyOpt = repository.findById(companyId);
		Company entity = companyOpt.get();

        // CompanyFormオブジェクトを作成してプロパティを設定
		CompanyForm form = new CompanyForm();
		form.setCompanyId(companyId);
		form.setCompanyName(entity.getName());
		form.setEmployees(String.valueOf(entity.getEmployees()));
		form.setEst(format.dateToString(entity.getEstablishmentDate()));
		return form;
	}
}
```
#### メソッドのシグネチャー
`public CompanyForm getEditCompany(Long companyId)` は引数で受け取った `companyId` で指定された企業情報を `CompanyForm` に詰めて返すメソッドです。

`CompanyForm` は、ビュー側（企業情報更新フォーム）とのデータのやり取りを担当するものであり、Entity（`Company`）とは役割が別であることに注意しましょう。（簡単に言うと、`CompanyForm` は、`Company` エンティティの情報を編集フォーム用に変換したオブジェクトであるということです。）

#### データベースから情報を取得
`repository.findById(companyId)`で、指定された `companyId` に対応する 企業情報をデータベースから取得します。

`Optional.get()` で、該当する企業情報を取得し、`Company` エンティティに詰めてあげています。

:::note warn
今回は分かり易いように、`Optional.get()`で、該当する企業情報を取得していますが、企業が見つからない場合に`NoSuchElementException`が発生する可能性があります。その為、本来は下記のように `Optional` に対する適切なエラーハンドリングを行うのがベターです。

```java:改善前
Optional<Company> companyOpt = repository.findById(companyId);
		Company entity = companyOpt.get();
```
```java:改善後
import java.util.NoSuchElementException;

// Companyが見つからない場合は例外をスローする
Company entity = repository.findById(companyId)
				.orElseThrow(() -> new NoSuchElementException("企業が見つかりませんでした。ID: " + companyId));
```
`Optional.orElseThrow(...)`を使用して、データが見つからない場合に`NoSuchElementException`をスローします。
:::

#### CompanyFormオブジェクトの作成とデータの設定
`CompanyForm` インスタンスを生成し、取得した `Company` エンティティのデータをフォーム用にセットしています。

従業員数と設立日に関しては、それぞれ文字列型に変換してセットしてあげます。

#### フォームデータの返却
最後に、`CompanyForm`オブジェクトが返され（`return form`）、更新画面でこのオブジェクトのデータが使用されます。

## Controllerクラスの修正①
企業情報を編集するためのフォーム画面を表示する処理作成します。

`配置先：src > main > java > com > example > demo > controller > CompanyController.java`

```java:CompanyController.java
@Controller
public class CompanyController {
	
	// 企業情報更新フォームを表示
	@GetMapping("company/edit")
	public String editCompany(@RequestParam Long companyId, Model model) {
		model.addAttribute("companyForm", companyService.getEditCompany(companyId));
		return "edit";
	}
}
```
#### @￰GetMapping("company/edit")
`GET`リクエストで `/company/edit` のURLにアクセスすると、このメソッドが実行されます。

例えば、`/company/edit?companyId=1` のようにアクセスすると、`companyId=1` というクエリパラメータが渡され、企業ID が 1 の情報を編集フォームに表示します。

#### @￰RequestParam Long companyId
`@RequestParam` アノテーションを使って、クエリパラメータとして渡された`companyId`を取得しています。ここでは `Long`型で受け取った企業IDを基に、特定の企業情報を取得します。

#### 企業情報の取得とモデルへの追加
先ほど作成した、`CompanyService` クラスの `getEditCompany` メソッドを呼び出して、指定された `companyId` に対応する企業情報を `CompanyForm` 形式で取得します。

`model.addAttribute("companyForm", ...)` で、取得した `CompanyForm` オブジェクトを `companyForm` という名前でモデルに追加し、ビューに渡します。

#### ビューの返却
`return edit` で `edit.html` に遷移します。
このビューで、`companyForm` オブジェクトの情報がフォーム入力フィールドに表示され、ユーザーが編集できるようになります。

## Viewの作成
edit.htmlを作成します。
ここでは、コントローラから渡されたcompanyFormオブジェクトの情報をフォーム入力フィールドに表示します。

編集対象の企業情報を表示・編集し、エラーがある場合にはフィールドごとにメッセージを表示して、ユーザーが適切にデータを入力できるようにしています。

`配置先：src > main > resources > templates > edit.html`

```html:edit.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>更新画面</title>
</head>
<body>
	<h1>企業情報更新フォーム</h1>
	<a th:href="@{/company/lists}">一覧画面へ戻る</a>
	<form th:action="@{/company/edit}" th:object="${companyForm}" method="post">
		<table>
			<tr>
				<td><input type="hidden" th:field="*{companyId}" /></td>
			</tr>
			<tr>
				<td><label for="company">会社名：</label></td>
				<td><input type="text" th:field="*{companyName}" /></td>
				<td th:if="${#fields.hasErrors('companyName')}" th:errors="*{companyName}">Name Error</td>
			</tr>
			<tr>
				<td><label for="emp">従業員数：</label></td>
				<td><input tyape="text" th:field="*{employees}" /></td>
				<td th:if="${#fields.hasErrors('employees')}" th:errors="*{employees}">Number of Employees Error</td>
			</tr>
			<tr>
				<td><label>設立日：</label></td>
				<td><input type="date" th:field="*{est}" /></td>
				<td th:if="${#fields.hasErrors('est')}" th:errors="*{est}">Establishment Date Error</td>
			</tr>
			<tr>
				<td><button type="submit" value="更新する">更新する</button></td>
			</tr>
		</table>
	</form>
</body>
</html>
```
#### < form th:action="@{/company/edit}" method="post">
`/company/edit` に `POST`リクエストを送信し、企業情報を更新するためのエンドポイントを指定しています。

#### th:object="${companyForm}"
コントローラーから渡ってきた `companyForm`オブジェクトをフォームのモデルとしてバインドします。

#### < input type="hidden" th:field="*{companyId}" />
`companyId` は企業の識別子で、編集対象の企業を特定するために必要です。
フォームに隠しフィールドとしてセットし、ユーザーには見えない形で送信されます。

#### フィールドに値をバインドする
`th:field` でフィールドに値をバインドし、エラーがある場合はエラーメッセージを表示します。`th:if="${#fields.hasErrors('フィールド名')}"` が `true` の場合のみエラーメッセージを表示します。

#### 編集リンクの修正
index.htmlを修正します。
「編集」リンクにダミーリンクを設定していたのを、edit.html に遷移できるように修正します。

`配置先：src > main > resources > templates > index.html`

```html:index.html
            <td>
                <a th:href="@{/company/detail/{id}(id=${company.companyId})}">詳細</a>
                <!-- ここを修正 -->
                <a th:href="@{/company/edit(companyId=${company.companyId})}">編集</a>
                <!-- 一旦ダミーリンクを設定 -->
                <a th:href="@{#}">削除</a>
            </td>
```
#### 各部分の説明
`@{...}` は、ThymeleafでURLを生成するための構文です。ここでは、`/company/edit` のエンドポイントにアクセスするURLを生成しています。

`(companyId=${company.companyId}) `で、動的に `companyId` パラメータの値を指定します。`${company.companyId}` は企業IDを取得してその値に置き換えられます。

例えば `companyId` が `1` の企業の場合、生成されるリンクは次のようになります。
```html:html
<a href="/company/edit?companyId=1">編集</a>
```

ここまで実装できたら、実際に一覧画面から「編集」リンクを押して「更新画面」が想定通りに表示されるか確認してみましょう。

次に更新機能の作成に進みます。

## Serviceクラスの修正②
`CompanyForm` オブジェクトの内容を元に企業情報を更新し、その更新内容をデータベースに保存するメソッドを作成していきます。

`配置先：src > main > java > com > example > demo > service > CompanyService.java`

```java:CompanyService.java
@Service
public class CompanyService {

    // 企業情報更新処理
	@Transactional
	public Company updateCompany(CompanyForm form) {
		Company entity = new Company();
		entity.setCompanyId(form.getCompanyId());
		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);
	}	
}
```
考え方としては、登録処理の時と同様です。

Spring Data JPA の `save`メソッドは、エンティティが既に存在していれば更新処理、存在していなければ新規作成処理を行います。

もう少し具体的に言うと、更新対象のキー（今回の場合は `CompanyID`）をチェックして、存在していれば `UPDATE`、存在していなければ `INSERT` します。

今回の場合、新規登録時とは異なり、更新対象のキーである `CompanyID` が存在している為、対象データがUPDATEされます。（`UPDATE company SET (カラム名1) = (値1) … WHERE company_id = 企業ID;` が実行されるイメージ）

## Controllerクラスの修正②
企業情報の更新処理を行うためのコントローラメソッドを作成します。

`配置先：src > main > java > com > example > demo > controller > CompanyController.java`

```java:CompanyController.java
@Controller
public class CompanyController {
	
	// 企業情報更新処理
	@PostMapping("company/edit")
	public String updateCompany(@ModelAttribute @Validated CompanyForm companyForm,
			BindingResult bindingResult) {
		
		if(bindingResult.hasErrors()) {
			return "edit";
		}
		
		companyService.updateCompany(companyForm);
		return "redirect:/company/lists";
	}
}
```
こちらも考え方は登録処理の際と同様で、フォームから送信されたデータを受け取り、エラーチェックを行った後、問題がなければデータを更新して一覧画面にリダイレクトしています。

これで更新機能は完成です。次回は削除機能を実装していきます。