Edited at

Spring Boot 2 + Thymeleaf + MyBatis で CRUD アプリ作成


開発環境


  • Java11

  • Spring Boot2

  • H2

  • MyBatis3

  • Thymeleaf3

  • JQuery

  • Bootstrap

  • Intellij IDEA


開発環境の構築


  • 上記であらかじめインストールの必要があるのは以下となる。


    • Java11

    • IDE(ここではIntellijでの説明を行う。他のIDEでも可。)




プロジェクトの作成


  • Intellijから、ファイル新規プロジェクトで、Spring Initilizrを選択する。

image.png


  • Java11を選択する。(たぶんJava8でも大丈夫)

image.png


  • DevTools、Web、Thymeleaf、H2、MyBatisをチェックする。

image.png


  • プロジェクト名を入力して、完了。

image.png


ホットデプロイの設定1/2


  • 開発効率を上げるため、ソースを変更したときに自動的にビルドする、ホットデプロイの設定を行う。


    • 大きな変更がある場合や変更が反映されない場合は、サーバーの停止→開始をしたほうがいい。



  • Intellijの設定から、ビルド、実行、デプロイコンパイラー から、□自動的にプロジェクトをビルドするにチェックを入れる。

image.png


ホットデプロイの設定2/2



  • ctrl+shift+aレジストリと入力して、レジストリ...をクリック。(Intellijを日本語化していない場合は、registryと入力して、Registry...をクリック)

image.png


  • compiler. ... .running ☑にチェックを入れる。

image.png


pom.xmlの修正


  • tymeleafのレイアウト機能を使いたい場合は、以下のようにpom.xmlの<dependencies></dependencies>内にthymeleaf-layout-dialectを入れる。(ここでは入れることを前提に進める。)

  • デザインの見栄えを良くするために、BootstrapとJQueryを入れる。

  • pom.xmlを右クリック→再インポートを行う。


pom.xml

    <dependency>

<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>1.11.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7-1</version>
</dependency>


schema.sql, data.sql作成


  • schema.sql, data.sqlを作成するとプロジェクト実行開始時に自動的にDBにテストデータを投入してくれる。

  • プロジェクト名/src/main/resourcesの下に作成する。


schema.sql

CREATE TABLE IF NOT EXISTS user (

id INT PRIMARY KEY AUTO_INCREMENT,
name varchar (50) NOT NULL
);


data.sql

INSERT INTO user (name) VALUES ('tanaka');

INSERT INTO user (name) VALUES ('suzuki');
INSERT INTO user (name) VALUES ('sato');


entity作成


  • entityパッケージを作成して、その配下にUserクラスを作成する。

  • Userデータを保持するクラスを作成。MyBatisを使うためのアノテーションはいらない。

  • nameに必須入力のバリデーションを追加する。


com.example.demo.entity.User.java

package com.example.demo.entity;

import org.hibernate.validator.constraints.NotBlank;

public class User {

private int id;

@NotBlank(message = "{require_check}")
private String name;

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;
}

}



mapper.java , mapper.xml作成


  • mapperパッケージを作成して、その配下にUserMapperインターフェースを作成する。

  • アノテーションは、@Mapperをつける。このままだと、Autowiredができないので、@Repositoryもつける。


src/main/java/com.example.demo.mapper.UserMapper.java

package com.example.demo.mapper;

import com.example.demo.entity.User;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface UserMapper {

List<User> selectAll();

User select(int id);

int insert(User user);

int update(User user);

int delete(int id);
}



  • XMLでのマッピングファイルは、resouces配下にJavaと同じディレクトリ階層で作成すると自動的に読みに行ってくれる。


    • namespace: 上記(java)のパッケージ名

    • id: 上記(java)で指定したメソッド名

    • resultType: 返却する型




src/main/resouces/com/example/demo/mapper/UserMapper

<?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.UserMapper">
<select id="selectAll" resultType="com.example.demo.entity.User">
select * from user
</select>
<select id="select" resultType="com.example.demo.entity.User">
select * from user where id = #{id}
</select>
<insert id="insert">
insert into user (name) values (#{name})
</insert>
<update id="update">
update user set name = #{name} where id = #{id}
</update>
<delete id="delete">
delete from user where id = #{id}
</delete>
</mapper>


controller作成


  • 7つのアクションを追加する。

HTTPメソッド
URL
controllerのメソッド
説明

GET
/users
getUsers()
一覧画面の表示

GET
/users/1
getUser()
詳細画面の表示

GET
/users/new
getUserNew()
新規作成画面の表示

POST
/users
postUserCreate()
新規作成画面の挿入処理

GET
/users/1/edit
getUserEdit()
編集画面の表示

PUT
/users/1
putUserEdit()
編集画面の更新処理

DELETE
/users/1
deleteUser()
削除処理


com.example.demo.controller.UserController

package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import java.util.List;
import javax.validation.Valid;
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.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/users")
public class UserController {

private final UserMapper userMapper;

@Autowired
public UserController(UserMapper userMapper) {
this.userMapper = userMapper;
}

/**
* 一覧画面の表示
*/

@GetMapping
public String getUsers(Model model) {
List<User> users = userMapper.selectAll();
model.addAttribute("users", users);
return "users/list";
}

/**
* 詳細画面の表示
*/

@GetMapping("{id}")
public String getUser(@PathVariable int id, Model model) {
User user = userMapper.select(id);
model.addAttribute("user", user);
return "users/show";
}

/**
* 新規作成画面の表示
*/

@GetMapping("new")
public String getUserNew(Model model) {
User user = new User();
model.addAttribute("user", user);
return "users/new";
}

/**
* 新規作成画面の挿入処理
*/

@PostMapping
public String postUserCreate(@ModelAttribute @Valid User user,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users/new";
}
userMapper.insert(user);
return "redirect:/users";
}

/**
* 編集画面の表示
*/

@GetMapping("{id}/edit")
public String getUserEdit(@PathVariable int id, Model model) {
User user = userMapper.select(id);
model.addAttribute("user", user);
return "users/edit";
}

/**
* 編集画面の更新処理
*/

@PutMapping("{id}")
public String putUserEdit(@PathVariable int id, @ModelAttribute @Valid User user,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users/edit";
}
user.setId(id);
userMapper.update(user);
return "redirect:/users";
}

/**
* 削除処理
*/

@DeleteMapping("{id}")
public String deleteUser(@PathVariable int id, Model model) {
userMapper.delete(id);
return "redirect:/users";
}
}



バリデーションメッセージの設定(messages.properties、WebConfig.java作成)


  • バリデーションメッセージを設定するために、messages.propertiesを作成する。


src/main/resources/messages.properties

name=ユーザー名

require_check={0}は必須入力です


  • 上記プロパティファイルを使用するために、以下の設定Javaファイルを作成するというおまじない。


com.example.demo/WebConfig.java

package com.example.demo;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class WebConfig {

@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource bean = new ReloadableResourceBundleMessageSource();
bean.setBasename("classpath:messages");
bean.setDefaultEncoding("UTF-8");
return bean;
}

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setValidationMessageSource(messageSource());
return localValidatorFactoryBean;
}
}



画面の作成(layout,show,list,new,edit,form,css作成)


共通レイアウト(layout.html)



  • <div layout:fragment="contents"></div>内にコンテンツが表示される。


src/main/resources/templates/commons/layout.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" >

<head>
<meta charset="UTF-8">
<link th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css}" rel="stylesheet"/>
<script th:src="@{/webjars/jquery/1.11.1/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js}"></script>
<link th:href="@{/css/home.css}" rel="stylesheet"/>
<title>Home</title>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">SpringMyBatisDemo</a>
</div>
</div>
</nav>

<div class="container-fluid">
<div class="row">
<div class="col-sm-2 sidebar">
<ul class="nav nav-pills nav-stacked">
<li role="presentation"><a th:href="@{/users}">ユーザー管理</a></li>
</ul>
</div>
</div>
</div>

<div class="container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-offset-2 main">
<div layout:fragment="contents">

</div>
</div>
</div>
</div>

</body>
</html>



一覧画面(list.html)


  • layout:decorate="~{commons/layout}"でレイアウトが使用される。

  • <div layout:fragment="contents"></div>で囲まれた部分が、レイアウト内に表示される。


src/main/resources/templates/users/list.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{commons/layout}">

<head>
<meta charset="UTF-8">
<link th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css}" rel="stylesheet"/>
<script th:src="@{/webjars/jquery/1.11.1/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js}"></script>
<link th:href="@{/css/home.css}" rel="stylesheet"/>
<title>Home</title>
</head>
<body>
<div layout:fragment="contents">
<div class="page-header">
<h1>ユーザー一覧</h1>
</div>
<table class="table table-bordered table-hover table-striped">
<tr>
<th class="info col-sm-2">ユーザーID</th>
<th class="info col-sm-2">ユーザー名</th>
<th class="info col-sm-2"></th>
</tr>
<tr th:each="user:${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td>
<a class="btn btn-primary" th:href="@{'/users/' + ${user.id}}">詳細</a>
<a class="btn btn-primary" th:href="@{'/users/' + ${user.id} + '/edit'}">編集</a>
</td>
</tr>
</table>
<label class="text-info" th:text="${result}">結果表示</label><br/>
<a class="btn btn-primary" th:href="${'/users/new'}">新規作成</a>
</div>

</body>
</html>



詳細画面(show.html)


  • layout:decorate="~{commons/layout}"でレイアウトが使用される。

  • <div layout:fragment="contents"></div>で囲まれた部分が、レイアウト内に表示される。

  • <div th:include="users/form :: form_contents"></div>に共通フォームが表示される。


src/main/resources/templates/users/show.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{commons/layout}">

<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<div layout:fragment="contents">
<div class="row">
<div class="col-sm-5">
<div class="page-header">
<div>
<h1>ユーザー詳細</h1>
</div>
<table class="table table-bordered table-hover">
<tr>
<th class="active col-sm-3">ユーザー名</th>
<td>
<div class="form-group" th:object="${user}">
<label class="media-body" th:text="*{name}">
</label>
</div>
</td>
</tr>
</table>
<form th:action="@{/users}" th:method="get">
<button class="btn btn-primary btn-lg pull-right" type="submit">一覧</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>



編集画面(edit.html)


  • layout:decorate="~{commons/layout}"でレイアウトが使用される。

  • <div layout:fragment="contents"></div>で囲まれた部分が、レイアウト内に表示される。

  • <div th:include="users/form :: form_contents"></div>に共通フォームが表示される。


src/main/resources/templates/users/edit.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{commons/layout}">

<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<div layout:fragment="contents">
<div class="row">
<div class="col-sm-5">
<div class="page-header">
<div>
<h1>ユーザー詳細</h1>
</div>
<form th:action="@{/users/{id}(id=*{id})}" th:method="put" th:object="${user}">

<div th:include="users/form :: form_contents"></div>

<button class="btn btn-primary btn-lg pull-right" type="submit">更新</button>
</form>
<form th:action="@{/users/{id}(id=*{id})}" th:method="delete" th:object="${user}">
<button class="btn btn-danger btn-lg" type="submit">削除</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>



新規画面(new.html)


  • layout:decorate="~{commons/layout}"でレイアウトが使用される。

  • <div layout:fragment="contents"></div>で囲まれた部分が、レイアウト内に表示される。

  • <div th:include="users/form :: form_contents"></div>に共通フォームが表示される。


src/main/resources/templates/users/new.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{commons/layout}">

<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<div layout:fragment="contents">
<div class="row">
<div class="col-sm-5">
<div class="page-header">
<div>
<h1>ユーザー新規作成</h1>
</div>
<form method="post" th:action="@{/users}" th:object="${user}">

<div th:include="users/form :: form_contents"></div>

<button class="btn btn-primary btn-lg pull-right" type="submit" name="update">作成</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>



共通フォーム


  • <div th:fragment="form_contents"></div>内が共通フォームで、<div th:include="users/form :: form_contents"></div>で埋め込むことができる。


src/main/resources/templates/users/form.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">

<head>
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<div th:fragment="form_contents">
<table class="table table-bordered table-hover">
<tr>
<th class="active col-sm-3">ユーザー名</th>
<td>
<div class="form-group" th:classappend="${#fields.hasErrors('name')} ? 'has-error'">
<label>
<input type="text" class="form-control" th:field="*{name}"/>
</label>
<span class="text-danger" th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name error</span>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>



css


src/main/resources/static/css/home.css

body {

padding-top: 50px;
}

.sidebar {
position: fixed;
display: block;
top: 50px;
bottom: 0;
background-color: #F4F5F7;
}

.main {
padding-top: 50px;
padding-left: 20px;
position: fixed;
display: block;
top: 0;
bottom: 0;
}

.page-header {
margin-top: 0;
}


image.png


  • 編集画面

image.png


  • 未入力エラー表示

image.png