【SpringBoot】Webアプリ開発【一覧表示】

f:id:vist764:20201018234226p:plain:w400
f:id:vist764:20201018234249p:plain:w400

前提

以下記事をもとにプロジェクトの設定を行う
【SpringBoot】プロジェクトの設定 - アルパカノフン

1.画面を作成

f:id:vist764:20201018230632p:plain:w200

index.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">
 <link href="/css/bootstrap.min.css" rel="stylesheet">
  <title>メンバー一覧</title>
</head>
<body>
  <nav class="navbar navbar-inverse">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/members">メンバー管理</a>
      </div>
    </div>
    </nav>
  <div class="container">
    <div class="card card-primary mb-3">
        <div class="card-header">
          <h5 class="card-title">メンバーリスト<a href="/members/new" class="btn btn-success float-right">新規</a></h5>
        </div>
        <div class="card-body" th:if="!${members.size()}">
          <p>メンバーがいません。</p>
        </div>
        <table class="table table-striped" th:if="${members.size()}">
          <thead>
              <tr>
                <th style="width: 10%">ID</th>
                <th style="width: 30%">名前</th>
                <th style="width: 10%">年齢</th>
                <th style="width: 50%"></th>
              </tr>
          </thead>
          <tbody>
              <tr th:each="member:${members}" th:object="${member}">
                <td th:text="*{id}"></td>
                <td th:text="*{name}"></td>
                <td th:text="*{age}"></td>
                <td class="float-right">
                  <form th:action="@{/members/{id}(id=*{id})}" th:method="delete">
                    <a class="btn btn-primary" th:href="@{/members/{id}(id=*{id})}">詳細</a>
                    <a class="btn btn-primary" th:href="@{/members/{id}/edit(id=*{id})}">変更</a>
                <button class="btn btn-primary">削除</button>
              </form>
                </td>
              </tr>
          </tbody>
        </table>
      </div>
  </div>
  <script src="/js/jquery-3.5.1.min"></script>
  <script src="/js/bootstrap.min.js"></script>
</body>
</html>
new.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="/css/bootstrap.min.css" rel="stylesheet">
  <title>メンバー新規</title>
</head>
<body>
  <nav class="navbar navbar-inverse">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/members">メンバー管理</a>
      </div>
    </div>
    </nav>
  <div class="container">
    <div class="card card-primary mb-3">
          <div class="card-header">
            <h5 class="card-title">メンバー新規</h5>
          </div>
        <div class="card-body">
              <form th:method="post" th:action="@{/members}" th:object="${member}">
                <div class="form-group row">
                    <label class="col-md-2 control-label">名前</label>
                    <div class="col-md-10">
                      <input class="form-control" type="text" name="name" th:value="*{name}" />
                      <div class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-2 control-label">年齢</label>
                    <div class="col-md-10">
                      <input class="form-control" type="text" name="age" th:value="*{age}"/>
                      <div class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></div>
                    </div>
                </div>
                <div class="form-group row">
                    <div class="offset-md-2 col-md-10">
                      <button class="btn btn-primary">新規</button>
                    </div>
                </div>
            </form>
        </div>
      </div>
    </div>
  <script src="/js/jquery-3.5.1.min"></script>
  <script src="/js/bootstrap.min.js"></script>
</body>
</html>
show.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="/css/bootstrap.min.css" rel="stylesheet">
  <title>メンバー詳細</title>
</head>
<body>
  <nav class="navbar navbar-inverse">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/members">メンバー管理</a>
      </div>
    </div>
    </nav>
  <div class="container">
    <div class="card card-primary mb-3">
        <div class="card-header">
          <h5 class="card-title">メンバー詳細</h5>
        </div>
      <div class="card-body">
            <div th:object="${member}">
              <div class="form-group row">
                <label class="col-md-2">氏名</label>
            <div class="col-md-10" th:text="*{name}"></div>
                </div>
                <div class="form-group row">
                  <label class="col-md-2 control-label">年齢</label>
                  <div class="col-md-10 form-control-static" th:text="*{age}"></div>
                </div>
        </div>
      </div>
    </div>
    </div>
  <script src="/js/jquery-3.5.1.min"></script>
  <script src="/js/bootstrap.min.js"></script>
</body>
</html>
edit.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="/css/bootstrap.min.css" rel="stylesheet">
  <title>メンバー情報変更</title>
</head>
<body>
  <nav class="navbar navbar-inverse">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/members">メンバー管理</a>
      </div>
    </div>
    </nav>
  <div class="container">
    <div class="card card-primary mb-3">
        <div class="card-header">
          <h5 class="card-title">メンバー詳細</h5>
        </div>
      <div class="card-body">
        <form th:action="@{/members/{id}(id=*{id})}" th:method="put" th:object="${member}">
              <div class="form-group row">
                  <label class="col-md-2 control-label">名前</label>
                  <div class="col-md-10">
                    <input class="form-control" type="text" th:field="*{name}" />
                    <div class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
                  </div>
              </div>
              <div class="form-group row">
                  <label class="col-md-2 control-label">年齢</label>
                  <div class="col-md-10">
                    <input class="form-control" type="text" th:field="*{age}" />
                    <div class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></div>
                  </div>
              </div>
              <div class="form-group row">
                  <div class="offset-md-2 col-md-9">
                    <button class="btn btn-primary">更新</button>
                  </div>
                </div>
            </div>
          </form>
    </div>
  </div>
  <script src="/js/jquery-3.5.1.min"></script>
  <script src="/js/bootstrap.min.js"></script>
</body>
</html>

2.Entityを作成

Entityとはテーブルのデータを格納するオブジェクト用のクラス
ファイル名はDBのテーブル名に合わせること
項目名はDBの項目名に合わせること
※Lombokを使用
f:id:vist764:20200910145310p:plain:w200

package com.example.demo.domain;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import lombok.Data;

@Data
public class Member {
	private long id;

	@NotBlank
	private String name;

	@Pattern(regexp="[0-9]+")
	private String age;
}

3.Mybatis

インターフェースを作成

テーブルにアクセスするメソッドを記載する
f:id:vist764:20200910214611p:plain:w200

package com.example.demo.mapper;

import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.domain.Member;

@Mapper
public interface MemberMapper {
	List<Member> selectAll();
}
SQLを記載するXMLを作成

※要注意※パッケージ名(階層も)とファイル名はインタフェースと合わせること
f:id:vist764:20200910215301p:plain:w400
f:id:vist764:20200910215152p:plain:w200
namespase=インタフェースのパスを指定
resultType=Entityクラスのパスを指定

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.MemberMapper">
  <select id="findAll" resultType="com.example.demo.domain.Member">
    select * from member
  </select>

  <select id="findOne" resultType="com.example.demo.domain.Member">
    select * from member where id= #{id}
  </select>

  <insert id="save" useGeneratedKeys="true" keyProperty="id">
    insert into member(name, age) values(#{name}, #{age})
  </insert>

  <update id="update">
    update member set name=#{name}, age=#{age} where id= #{id}
  </update>

  <delete id="delete">
    delete from member where id = #{id}
  </delete>
</mapper>

4.サービスを作成

f:id:vist764:20201018232647p:plain:w200

package com.example.demo.service;

import java.util.List;

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

import com.example.demo.domain.Member;
import com.example.demo.mapper.MemberMapper;

@Service
public class MemberService {
	@Autowired
	private MemberMapper MemberMapper;

	@Transactional
	public List<Member> findAll() {
		return MemberMapper.findAll();
	}

	@Transactional
	public Member findOne(Long id) {
		return MemberMapper.findOne(id);
	}

	@Transactional
	public void save(Member Member) {
		MemberMapper.save(Member);
	}

	@Transactional
	public void update(Member Member) {
		MemberMapper.update(Member);
	}

	@Transactional
	public void delete(Long id) {
		MemberMapper.delete(id);
	}
}

5.コントローラを作成

f:id:vist764:20201018231405p:plain:w200

IndexController
package com.example.demo.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.service.MemberService;

@Controller
@RequestMapping("/members")
public class IndexController {

	@Autowired
	private MemberService memberService;

	// ボタン押下処理
	@GetMapping
	public String index(Model model) {
		model.addAttribute("members", memberService.findAll());
		return "index";
	}

	// 詳細ボタン押下処理
	@GetMapping("{id}")
	public String show(@PathVariable Long id, Model model) {
		model.addAttribute("member", memberService.findOne(id));
		return "show";
	}

	// 削除ボタン押下処理
	@DeleteMapping("{id}")
	public String delete(@PathVariable Long id) {
		memberService.delete(id);
		return "redirect:/members";
	}
}
NewController
package com.example.demo.web;

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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.domain.Member;
import com.example.demo.service.MemberService;

@Controller
@RequestMapping("members")
public class NewController {

	@Autowired
	private MemberService memberService;

	@GetMapping("new")
	public String newItem(Member member, Model model) {
		return "new";
	}

	@ModelAttribute
	Member setMember() {
		return new Member();
	}

	@PostMapping
	public String create(@Validated Member member, BindingResult result, Model model) {
		if (result.hasErrors()) {
			return "new";
		} else {
			memberService.save(member);
			return "redirect:/members";
		}
	}
}
EditController
package com.example.demo.web;

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.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.domain.Member;
import com.example.demo.service.MemberService;

@Controller
@RequestMapping("/members")
public class EditController {

	@Autowired
	private MemberService memberService;

	@GetMapping("{id}/edit")
	public String edit(@PathVariable Long id, @ModelAttribute("member") Member member, Model model) {
		model.addAttribute("member", memberService.findOne(id));
		return "edit";
	}

	@PutMapping("{id}")
	public String update(@PathVariable Long id, @ModelAttribute("member") @Validated Member member, BindingResult result, Model model) {
		if (result.hasErrors()) {
			model.addAttribute("member", member);
			return "edit";
		} else {
			member.setId(id);
			memberService.update(member);
			return "redirect:/members";
		}
	}
}