SpringBoot + Thymeleaf + MySQLで一括更新を実現する


## 概要
* 以下動画の機能を実現する

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148710/0fbf7a78-fca2-6a96-6a2b-2e264b3ad880.gif)


## 機能説明
* 同じ画面の中でボタンをクリックすると閲覧モードと編集モードが切り替える
* 更新ボタンを押下すると複数件のデータを同時に更新する


## 開発環境
* Windows 10
* Spring Tool Suite 4 Version: 4.8.1.RELEASE
* JavaSE-11
* MySql 5.8
* Thymeleaf 3.0.12 

## 事前準備
Mysqlにcountryテーブルを作成する

```sql:country
CREATE TABLE `country` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `countryKbn` varchar(10) NOT NULL,
  `countryName` varchar(10) NOT NULL,
  `createUser` varchar(50) DEFAULT NULL,
  `createDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `updateUser` varchar(50) DEFAULT NULL,
  `updateDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8
```

## アプリの作成

Entity作成

```java:Country.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Country {
	private String countryKbn;
    private String countryName;
    private String createUser;
    private LocalDateTime createDateTime;
    private String updateUser;
    private LocalDateTime updateDateTime;
}
```

コントロールクラスの作成

```java:userController.java

@Controller
@RequestMapping
public class CountryController {

	@Autowired
	CountryService countryService;
    
	/**
	 * 国区分一覧画面初期表示
	 * 
	 * @param model
	 * @param countryForm
	 * @return 国区分一覧画面
	 */
	@GetMapping("/country")
	private String index(Model model, @ModelAttribute("form") CountryForm countryForm) {
        // 登録済みの国情報をDBから検索してmodelに設定して画面へ渡す
		List<Country> countryList = countryService.getAllCountry();
		countryForm.setCountryList(countryList);
		model.addAttribute("countryForm", countryForm);
		return "index";
	}
}
```


HTML画面作成

```html:index.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">

<head th:replace="BRGD :: head(国区分一覧)"></head>
<meta charset="utf-8">
<script>
	$(function() {
		if($('#mode').val() == "update"){
			console.log("AAAA");
			$('#contents-body-edit').hide();
			$('#contents-body-update').show();
			$('#contents-footer-edit').hide();
			$('#contents-footer-update').show();
		}
		
		//編集ボタンのイベント
		$('#edit').click(function() {
			$('#contents-body-edit').hide();
			$('#contents-body-update').show();
			$('#contents-footer-edit').hide();
			$('#contents-footer-update').show();
		});
		//更新ボタンのイベント
		$('#update').click(function() {
			if (window.confirm("国区分を更新します。\nよろしいですか？")) {
				cancel();
				document.forms["from"].submit();
			}
		});
	});

	//キャンセルボタンのイベント
	function cancel() {
		$('#contents-body-edit').show();
		$('#contents-body-update').hide();
		$('#contents-footer-edit').show();
		$('#contents-footer-update').hide();
	}
</script>

<body>
	<!--ここは thymeleafの th:replaceを利用して別ファイルで書いてcss/jsファイルを埋め込んでいる、この記事では省略-->

	<!-- main start -->
	<div class="main">
		<div class="container-fluid">
			<!-- from start-->
			<form name="form" th:action="@{/country}" th:method="post" th:object="${countryForm}" class="needs-validation" novalidate>

				<input type="hidden" id="mode" name="mode" th:value="${mode}">
				<!-- contents start-->
				<div class="contents">
					<div class="contents-header">国区分一覧</div>
					<!-- contents-body-edit -->
					<div class="contents-body" id="contents-body-edit">
						<table class="table table-striped table-hover table-bordered">
							<thead>
								<tr>
									<th style="width: 5%;">No.</th>
									<th style="width: 20%;">国区分</th>
									<th style="width: 20%;">国名</th>
									<th>作成者</th>
									<th>作成日時</th>
									<th>更新者</th>
									<th>更新日時</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="country,stat:${countryForm.countryList}" th:object="${country}">
									<td th:text="${stat.count}"></td>
									<td th:text="*{countryKbn}"></td>
									<td th:text="*{countryName}"></td>
									<td th:text="*{createUser}"></td>
									<td th:text="*{createDateTime}"></td>
									<td th:text="*{updateUser}"></td>
									<td th:text="*{updateDateTime}"></td>
								</tr>
							</tbody>
						</table>
					</div>
					<!-- /.contents-body-edit -->
					<!-- contents-body-update -->
					<div class="contents-body" id="contents-body-update" style="display: none;">
						<table class="table table-striped table-hover table-bordered">
							<thead>
								<tr>
									<th style="width: 5%;">No.</th>
									<th style="width: 20%;">国区分</th>
									<th style="width: 20%;">国名</th>
									<th>作成者</th>
									<th>作成日時</th>
									<th>更新者</th>
									<th>更新日時</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="country,stat : *{countryList}">
									<td th:text="${stat.count}"></td>
									<td><input type="text" class="form-control" th:field="*{countryList[__${stat.index}__].countryKbn}" readonly="readonly"></td>
									<td><input type="text" class="form-control" th:field="*{countryList[__${stat.index}__].countryName}"></td>
									<td th:text="*{countryList[__${stat.index}__].createUser}"></td>
									<td th:text="*{countryList[__${stat.index}__].createDateTime}"></td>
									<td th:text="*{countryList[__${stat.index}__].updateUser}"></td>
									<td th:text="*{countryList[__${stat.index}__].updateDateTime}"></td>
								</tr>
							</tbody>
						</table>
					</div>
					<!-- /.contents-body-update -->
					<!-- contents-footer-edit -->
					<div class="contents-footer" id="contents-footer-edit">
						<button type="button" class="btn btn-primary" id="edit">編集</button>
					</div>
					<!-- /.contents-footer-edit -->
					<!-- contents-footer-update -->
					<div class="contents-footer" id="contents-footer-update" style="display: none;">
						<button type="submit" id="update" name="update" class="btn btn-primary" onclick="_submit()">更新</button>
						<button type="submit" id="cancel" name="cancel" class="btn btn-secondary" onclick="cancel()">キャンセル</button>
					</div>
					<!-- /.contents-footer-update -->

				</div>
				<!-- /.contents -->
			</form>
			<!-- /.from -->
		</div>
		<!-- container-fluid -->
	</div>
	<!-- main end-->
	<!-- Optional JavaScript -->
	<!-- jQuery first, then Popper.js, then Bootstrap JS -->
	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>

</html>

```

* 説明
・javascriptとの`show()、hide()`関数を使って画面中の`div`をコントロールして表示/非表示に切り替えている。
ここでポイントなのは編集モード時のthymeleafの書き方です。一括更新するためにサーバー側にデータを送るときは`list`型で
送る必要があります、更新後の`リスト`型のデータを作るには以下です。

```html
<tbody>
　<tr th:each="country,stat : *{countryList}">
　　<td th:text="${stat.count}"></td>
   <!--リストから値を一つ一つで取得して画面に表示している-->
 　<!--更新後の値も一つ一つでリストに保存している-->
　 <!--閲覧モードの時のオブジェクトから値と取得していると違うので注意-->
　　<td><input type="text" class="form-control" th:field="*{countryList[__${stat.index}__].countryKbn}" readonly="readonly"></td>
　　<td><input type="text" class="form-control" th:field="*{countryList[__${stat.index}__].countryName}"></td>
　</tr>
</tbody>
```

ここまで出来ましたらコントロールクラスに更新機能を追加すれば一括更新機能を実現できます

```java
	/**
	 * 国区分を更新する
	 * 
	 * @param model
	 * @param countryForm
	 * @param result
	 * @return
	 */
	@PostMapping(value = "country", params = "update")
	private String updateContry(Model model, @Validated @ModelAttribute("form") CountryForm countryForm,
			BindingResult result) {
		model.addAttribute("countryForm", countryForm);
		if (result.hasErrors()) {
			List<ObjectError> errorList = result.getAllErrors();
			boolean isError = errorList.size() > 0 ? true : false;
			model.addAttribute("isError", isError);
			model.addAttribute("errorList", errorList);
			model.addAttribute("mode", "update");
			return "index";
		}
		countryService.updateCountry(countryForm.getCountryList());
		return "redirect:/index";
	}
```

* 説明
 * 更新ボタンを押下したら上記のメソッドを呼び出す、画面で更新したリストは@ModelAttribute("form") CountryForm のcoutryListフィールドに入ります。
 * 受け取りました更新後のリストをサービスに渡してデータへ登録する
 * データベースで登録するときにテーブルをクリアしてから再登録をしている。 
 * デメリットとしてテーブルのデータが多いときこの方法は時間がかかると思います。
 * 削除してから更新時にエラーがあればロールバックするので削除処理と更新処理をした後にコミットするように設定する必要があります
 

## 終了