今回もSpring Bootで、勉強も兼ねてアプリ作成しました。
随時機能追加する予定です。
######2020/06/06:削除機能追加
#使用環境
・Windows10 (64bit)
・spring-boot:2.2.6
・Eclipse:4.9.0
・H2
・Bootstrap
#完成図
簡単な問い合わせアプリです。
#Entity
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.springframework.format.annotation.DateTimeFormat;
@Entity
public class Inquiry {
@Id
@GeneratedValue
private int id;
private String name;
private String email;
private String contents;
@DateTimeFormat(pattern="yyyy-MM-dd")
private LocalDateTime created;
public Inquiry() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getContents() {
return contents;
}
public void setContents(String contents) {
this.contents = contents;
}
public LocalDateTime getCreated() {
return created;
}
public void setCreated(LocalDateTime created) {
this.created = created;
}
}
#Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface InquiryDao extends JpaRepository<Inquiry, Integer> {
}
#Service
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class InquiryService {
@Autowired
InquiryDao inquiryDao;
//保存処理
public Inquiry save(Inquiry inquiry) {
return inquiryDao.saveAndFlush(inquiry);
}
//検索処理
public List<Inquiry> find() {
return inquiryDao.findAll();
}
//削除処理
public void delete(int id) {
inquiryDao.deleteById(id);
}
}
#Form
入力した値を保持するためにFormクラスを用意します。
バリデーションチェックもここで設定します。
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
public class InquiryForm {
@Size(min=1, max=20, message="名前の入力チェックエラー")
private String name;
@NotEmpty(message="未入力です")
private String email;
@NotEmpty(message="未入力です")
private String contents;
public InquiryForm() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getContents() {
return contents;
}
public void setContents(String contents) {
this.contents = contents;
}
}
#Controller
import java.time.LocalDateTime;
import java.util.List;
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.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/inquiry")
public class InquiryController {
@Autowired
InquiryService inquiryService;
//一覧画面処理
@GetMapping()
public String index(Model model) {
//全件検索処理
List<Inquiry> list = inquiryService.find();
model.addAttribute("list", list);
model.addAttribute("title", "一覧画面");
return "index_boot";
}
//問い合わせ入力処理
@GetMapping("form")
public String form(@ModelAttribute("inquiryForm") InquiryForm inquiryForm, Model model, @ModelAttribute("complete") String complete) {
model.addAttribute("title", "問い合わせフォーム");
return "form_boot";
}
//確認ページから「戻る」ボタン押下で飛んできた処理
@PostMapping("form")
public String formGoBack(@ModelAttribute("inquiryForm") InquiryForm inquiryForm, Model model) {
model.addAttribute("title", "問い合わせフォーム");
return "form_boot";
}
/*入力内容を打ち込んで「確認ページ」押下した際の処理
* @Validatedでフォームクラスの入力チェックを実施して、
* チェック結果をBindingResultに格納する
*/
@PostMapping("confirm")
public String confirm(@ModelAttribute("inquiryForm") @Validated InquiryForm inquiryForm, BindingResult res, Model model) {
//BindingResultの結果、エラーならエラーメッセージを出力する
if(res.hasErrors()) {
model.addAttribute("title", "問い合わせフォーム");
return "form_boot";
}
model.addAttribute("title", "確認ページ");
return "confirm_boot";
}
//「保存」ボタン押下した際の処理
@PostMapping("complete")
public String complete(@ModelAttribute("inquiryForm") @Validated InquiryForm inquiryForm, BindingResult res, Model model, RedirectAttributes redirectAttributes) {
if(res.hasErrors()) {
return "form_boot";
}
//InquiryFormの入れ物からInquiryに詰めなおす作業
Inquiry inquiry = new Inquiry();
inquiry.setName(inquiryForm.getName());
inquiry.setEmail(inquiryForm.getEmail());
inquiry.setContents(inquiryForm.getContents());
inquiry.setCreated(LocalDateTime.now());
//保存処理
inquiryService.save(inquiry);
redirectAttributes.addFlashAttribute("complete", "保存完了しました");
return "redirect:/inquiry/form";
}
//削除処理
@GetMapping("delete")
public String delete(Model model) {
List<Inquiry> deleteList = inquiryService.find();
model.addAttribute("title", "削除ページ");
model.addAttribute("list", deleteList);
return "delete_boot";
}
//URLのidを@PathVariableで受け取って、引数として利用する
@GetMapping("dell/{id}")
public String deletes(@PathVariable("id") int id, Model model) {
inquiryService.delete(id);
return "redirect:/inquiry";
}
}
#index_boot.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Jekyll v4.0.1">
<title th:text="${title}">Starter Template · Bootstrap</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.5/examples/starter-template/">
<!-- Bootstrap core CSS -->
<link href="/docs/4.5/dist/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<!-- Favicons -->
<link rel="apple-touch-icon" href="/docs/4.5/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/docs/4.5/assets/img/favicons/manifest.json">
<link rel="mask-icon" href="/docs/4.5/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c">
<link rel="icon" href="/docs/4.5/assets/img/favicons/favicon.ico">
<meta name="msapplication-config" content="/docs/4.5/assets/img/favicons/browserconfig.xml">
<meta name="theme-color" content="#563d7c">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<!-- Custom styles for this template -->
<link href="starter-template.css" rel="stylesheet" th:href="@{/css/starter-template.css}">
</head>
<body>
<div th:replace="~{header::headerA}"></div>
<main role="main" class="container">
<div class="starter-template">
<h1 th:text="${title}"></h1>
<p class="lead">問い合わせ一覧画面になります</p>
</div>
<!-- テーブルの一覧項目 -->
<table class="table table-striped">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">氏名</th>
<th scope="col">Eメール</th>
<th scope="col">内容</th>
<th scope="col">日付</th>
</tr>
</thead>
<tbody>
<!--th:eachで値を繰り返し出力する -->
<tr th:each="list:${list}">
<th scope="row" th:text="${list.id}">1</th>
<td th:text="${list.name}">
<td th:text="${list.email}">
<td th:text="${list.contents}">
<td th:text="${list.created}">
</tr>
</tbody>
</table>
</main><!-- /.container -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="/docs/4.5/assets/js/vendor/jquery.slim.min.js"><\/script>')</script><script src="/docs/4.5/dist/js/bootstrap.bundle.min.js" th:src="@{/js/bootstrap.bundle.min.js}" integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd" crossorigin="anonymous"></script></body>
</html>
#form_boot.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../../../favicon.ico">
<title>Starter Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="starter-template.css" th:href="@{/css/starter-template.css}" rel="stylesheet">
</head>
<body>
<div th:replace="~{header::headerA}"></div>
<main role="main" class="container">
<div class="starter-template">
<h1 th:text="${title}">Bootstrap starter template</h1>
<p class="lead">問い合わせ内容をここで入力できます</p>
</div>
<div th:unless="${#strings.isEmpty(complete)}" >
<div th:text="${complete}" class="alert alert-success" role="alert">
A simple success alert?check it out!
</div>
</div>
<!-- 「戻る」ボタンが押下された時、この入力画面で入れた値を保持するために
th:valueを使用する -->
<!--th:ifの結果trueの場合、th:errorsが適用される(各項目に設定したバリデーションが出力される) -->
<form method="post" action="#" th:action="@{/inquiry/confirm}" th:object="${inquiryForm}">
<div class="form-group">
<label for="name">Name</label>
<input type="text" name="name" class="form-control" id="name" th:value="*{name}">
</div>
<div class="text-danger mb-4" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" name="email" class="form-control" id="email" th:value="*{email}">
</div>
<div class="text-danger mb-4" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
<div class="form-group">
<label for="contents">Inquiry</label>
<textarea name="contents" class="form-control" id="detail" rows="3" th:field="*{contents}"></textarea>
</div>
<div class="text-danger mb-4" th:if="${#fields.hasErrors('contents')}" th:errors="*{contents}"></div>
<button type="submit" class="btn btn-primary">確認</button>
</form>
</main><!-- /.container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="@{/js/jquery-slim.min.js}"><\/script>')</script>
<script src="../../assets/js/vendor/popper.min.js" th:src="@{/js/popper.min.js}"></script>
<script src="../../dist/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</body>
#confirm_boot.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../../../favicon.ico">
<title>Starter Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="starter-template.css" th:href="@{/css/starter-template.css}" rel="stylesheet">
</head>
<body>
<div th:replace="~{header::headerA}"></div>
<main role="main" class="container">
<div class="starter-template">
<h1 th:text="${title}">Bootstrap starter template</h1>
<p class="lead">この画面で入力内容のご確認が可能です。</p>
</div>
<!--inquiryFormに詰めている各値を取り出す -->
<div th:object="${inquiryForm}">
<div class="mb-5">
<ul class="list-group">
<li th:text="*{name}" class="list-group-item"></li>
<li th:text="*{email}" class="list-group-item"></li>
<li th:text="*{contents}"class="list-group-item"></li>
</ul>
</div>
<!--hiddenを使用すると、ボタンを押下することで、値を送信することができる -->
<form method="post" th:action="@{/inquiry/form}">
<input type="hidden" name="name" th:value="*{name}">
<input type="hidden" name="email" th:value="*{email}">
<input type="hidden" name="contents" th:value="*{contents}">
<button type="submit" class="btn btn-primary">戻る</button>
</form>
<form method="post" th:action="@{/inquiry/complete}" th:object="${inquiryForm}">
<input type="hidden" name="name" th:value="*{name}">
<input type="hidden" name="email" th:value="*{email}">
<input type="hidden" name="contents" th:value="*{contents}">
<button type="submit" class="btn btn-primary">投稿</button>
</form>
</div>
</main><!-- /.container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="@{/js/jquery-slim.min.js}"><\/script>')</script>
<script src="../../assets/js/vendor/popper.min.js" th:src="@{/js/popper.min.js}"></script>
<script src="../../dist/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</body>
</html>
#delete_boot.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../../../favicon.ico">
<title>Starter Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="starter-template.css" th:href="@{/css/starter-template.css}" rel="stylesheet">
</head>
<body>
<div th:replace="~{header::headerA}"></div>
<main role="main" class="container">
<div class="starter-template">
<h1 th:text="${title}">Bootstrap starter template</h1>
<p class="lead">削除したい項目を選択してください</p>
</div>
<div th:unless="${#strings.isEmpty(complete)}" >
<div th:text="${complete}" class="alert alert-success" role="alert">
A simple success alert?check it out!
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">氏名</th>
<th scope="col">Eメール</th>
<th scope="col">内容</th>
<th scope="col">日付</th>
<th scope="col">削除</th>
</tr>
</thead>
<tbody>
<!--th:eachで値を繰り返し出力する -->
<tr th:each="list:${list}">
<th scope="row" th:text="${list.id}"></th>
<td th:text="${list.name}">
<td th:text="${list.email}">
<td th:text="${list.contents}">
<td th:text="${list.created}">
<td><a th:href="@{/inquiry/dell/{id}(id=${list.id})}">削除</a>
</tr>
</tbody>
</table>
</main><!-- /.container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="@{/js/jquery-slim.min.js}"><\/script>')</script>
<script src="../../assets/js/vendor/popper.min.js" th:src="@{/js/popper.min.js}"></script>
<script src="../../dist/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</body>
#処理パターン
ヘッダーの「問い合わせフォーム」押下してフォーム画面に遷移します。
氏名、メール、内容を入力して、「確認」ボタン押下します。
確認画面に遷移しますが、「戻る」ボタン押下して、前の画面に戻ります。
内容等を変更して、再度「確認」ボタンを押下します。
確認画面で「投稿」ボタンを押下すると、内容が保存されます。
##削除パターン
納豆太の「削除」ボタン押下します。
すると、削除されていることが確認できます。
#参考
Spring入門