[Java] Creating inquiry application with Spring Boot

6 minute read

This time, I also created an application with Spring Boot for studying. We plan to add functions as needed.

2020/06/06: Addition of delete function

usage environment

・Windows10 (64bit) ・Spring-boot: 2.2.6 ・Eclipse: 4.9.0 ・H2 ・Bootstrap

Completion diagram

It is a simple inquiry app.

image.png

Entity

Inquiry.java


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

InquiryDao.java


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface InquiryDao extends JpaRepository<Inquiry, Integer> {

}

Service

InquiryService.java


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class InquiryService {

@Autowired
InquiryDao inquiryDao;

// Save processing
public Inquiry save(Inquiry inquiry) {

return inquiryDao.saveAndFlush(inquiry);
}

// Search processing
public List<Inquiry> find() {

return inquiryDao.findAll();
}

// Delete process
public void delete(int id) {

inquiryDao.deleteById(id);
}
}

Form

Prepare Form class to hold the entered value. Validation checks are also set here.

InquiryForm.java


import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

public class InquiryForm {

@Size(min=1, max=20, message="Error checking name input")
private String name;

@NotEmpty(message="No input")
private String email;

@NotEmpty(message="No input")
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

InquiryController.java


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;

// List screen processing
@GetMapping()
public String index(Model model) {

// Search all records
List<Inquiry> list = inquiryService.find();
model.addAttribute("list", list);
model.addAttribute("title", "list screen");
return "index_boot";
}

// Inquiry input processing
@GetMapping("form")
public String form(@ModelAttribute("inquiryForm") InquiryForm inquiryForm, Model model, @ModelAttribute("complete") String complete) {

model.addAttribute("title", "contact form");
return "form_boot";
}

//Process that was skipped by pressing the "Back" button from the confirmation page
@PostMapping("form")
public String formGoBack(@ModelAttribute("inquiryForm") InquiryForm inquiryForm, Model model) {

model.addAttribute("title", "contact form");
return "form_boot";
}

/* Process when inputting the input contents and pressing "Confirmation page"
* Perform form class input check with @Validated,
* Store the check result in BindingResult
*/
@PostMapping("confirm")
public String confirm(@ModelAttribute("inquiryForm") @Validated InquiryForm inquiryForm, BindingResult res, Model model) {

//If the result of BindingResult is an error, output an error message
if(res.hasErrors()) {
model.addAttribute("title", "contact form");
return "form_boot";
}
model.addAttribute("title", "confirmation page");
return "confirm_boot";
}

//Process when "Save" button is pressed
@PostMapping("complete")public String complete(@ModelAttribute("inquiryForm") @Validated InquiryForm inquiryForm, BindingResult res, Model model, RedirectAttributes redirectAttributes) {

if(res.hasErrors()) {
return "form_boot";
}

//Refilling Inquiry from the InquiryForm container
Inquiry inquiry = new Inquiry();
inquiry.setName(inquiryForm.getName());
inquiry.setEmail(inquiryForm.getEmail());
inquiry.setContents(inquiryForm.getContents());
inquiry.setCreated(LocalDateTime.now());

// Save processing
inquiryService.save(inquiry);

redirectAttributes.addFlashAttribute("complete", "Save completed");
return "redirect:/inquiry/form";
}

// Delete process
@GetMapping("delete")
public String delete(Model model) {

List<Inquiry> deleteList = inquiryService.find();
model.addAttribute("title", "delete page");
model.addAttribute("list", deleteList);
return "delete_boot";
}

//Receive the URL id with @PathVariable and use it as an argument
@GetMapping("dell/{id}")
public String deletes(@PathVariable("id") int id, Model model) {

inquiryService.delete(id);
return "redirect:/inquiry";
}

}

index_boot.html

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-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZd5McxFlCx "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">Inquiry list screen</p>
  </div>
<!-- Table list item -->
<table class="table table-striped">
  <thead>
    <tr>
      <th scope="col">id</th>
      <th scope="col">Name</th>
      <th scope="col">Email</th>
      <th scope="col">Contents</th>
      <th scope="col">date</th>
    </tr>
  </thead>
  <tbody>
  <!-- Output values repeatedly with 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-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVcANXGytlQ" anonymous"></script></body>
</html>

form_boot.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">You can enter your inquiry here</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>
<!-- To retain the value entered in this input screen when the "Back" button is pressed
use th:value -->
<!-- If the result of th:if is true, th:errors is applied (validation set for each item is output) -->
<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">Confirm</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

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"> You can check the input contents on this screen. </p>
      </div>

<!-- Retrieve each value packed in 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 can be used to send the value by pressing the button -->
<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">Back</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">Post</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.delete_boot.html<html xmlns:th="http://www.thymeleaf.org">

Starter Template for Bootstrap

Bootstrap starter template

削除したい項目を選択してください

id 氏名 Eメール 内容 日付 削除
削除 </tr> </tbody> </table> </main> </body> ``` # 処理パターン ヘッダーの「問い合わせフォーム」押下してフォーム画面に遷移します。 氏名、メール、内容を入力して、「確認」ボタン押下します。 確認画面に遷移しますが、「戻る」ボタン押下して、前の画面に戻ります。 内容等を変更して、再度「確認」ボタンを押下します。 確認画面で「投稿」ボタンを押下すると、内容が保存されます。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/ce9354c3-5f44-522c-9cfc-27a75af2b8fb.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/33771212-e6f7-4a31-1709-c37bd114c802.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/9cbe9fa3-d9af-89ef-93a2-dba3f309e5f5.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/a0911760-32a7-e7b8-b72f-bea0e3572611.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/15d95248-21fd-1def-fba2-c65380c56a4b.png) ## 削除パターン 納豆太の「削除」ボタン押下します。 すると、削除されていることが確認できます。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/ff44298e-5723-d0cd-a8d8-f6acc2a971b7.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/e56f16f9-d181-8335-ce9b-43f01f56f44f.png) # 参考 [Spring入門](https://www.udemy.com/course/java_spring_beginner/)