1
3

More than 3 years have passed since last update.

Spring Bootで問い合わせアプリ作成

Last updated at Posted at 2020-05-30

今回もSpring Bootで、勉強も兼ねてアプリ作成しました。
随時機能追加する予定です。

2020/06/06:削除機能追加

使用環境

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

完成図

簡単な問い合わせアプリです。

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;

    //保存処理
    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クラスを用意します。
バリデーションチェックもここで設定します。

InquiryForm.java
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

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;

    //一覧画面処理
    @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

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

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

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.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>

処理パターン

ヘッダーの「問い合わせフォーム」押下してフォーム画面に遷移します。
氏名、メール、内容を入力して、「確認」ボタン押下します。
確認画面に遷移しますが、「戻る」ボタン押下して、前の画面に戻ります。
内容等を変更して、再度「確認」ボタンを押下します。
確認画面で「投稿」ボタンを押下すると、内容が保存されます。

image.png

image.png

image.png

image.png

image.png

削除パターン

納豆太の「削除」ボタン押下します。
すると、削除されていることが確認できます。
image.png

image.png

参考

Spring入門

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3