JetBrains Toolbox App をインストールする
1.プロジェクト作成
① ブラウザを開き、(https://start.spring.io/) を入力し、開く。
② 下記を入力し、Dependenciesで下記を選択し、『GENERATE』ボタンを押す。
- Project: Gradle
- Language: Java
- SpringBoot: 3.1.0
- Group: com.example
- Name : its
- Artifact: its
- Description: Issue Tracking System
- Package name: com.example
Dependencies
- Spring Web
- Thymeleaf
- Spring dev tools
2.トップページを表示する
Webアプリケーションを実装するとは
- リクエストとControllerを紐付ける
- Controllerでレスポンスを生成する
①src > main > java > com > example > <プロジェクト名> 直下にwebフォルダを作成し、IndexController.javaファイルを作成する。
package com.example.its.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody
@Controller
public class IndexController {
@GetMapping("/")
@ResponseBody
public String index() {
return "<h1>Hello, World!!</h1>";
}
}
3.テンプレートエンジンを利用する
テンプレートエンジンとは、テンプレートとデータから動的に文字を生成する仕組みのこと
①spring initializrに『Thymeleaf』を追加して、『EXPLORE』を押し、下記のファイルにコードを加える。
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
②src > main > resource > templete にindex.htmlファイルを作成する。
<!DOCTYPE >
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>Assignment management app</h1>
</body>
</html>
③IndexController.javaファイルを編集する。
package com.example.its.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
4.Spring Dev Toolsを導入して開発効率を上げる
①spring initializrに『Spring dev Tools』を追加して、『EXPLORE』を押し、下記のファイルにコードを加える。
developmentOnly 'org.springframework.boot:spring-boot-devtools'
②main/resouces/application.propertiesに下記のコードを加える。
spring.thymeleaf.prefix=file:src/main/resources/templates/
5.modelとcontrollerを作成する
①src > main > java > com > example > <プロジェクト名> 直下に
domainissueフォルダを作成し、IssueEntityファイルを作成する。
package com.example.its.domainissue;
public class IssueEntity {
private long id;
private String summary;
private String description;
public IssueEntity(long id, String summary, String description) {
this.id = id;
this.summary = summary;
this.description = description;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getSummary() {
return summary;
}
public void setSummry(String summary) {
this.summary = summary;
}
public String getDescription() {
return summary;
}
public void setDescription(String description) {
this.description = description;
}
}
②webにissueフォルダを作成し、IssueController.javaファイルを作成する。
package com.example.its.web.issue;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.thymeleaf.engine.AttributeName;
import java.util.List;
import com.example.its.domainissue.IssueEntity;
@Controller
public class IssueController {
// GET /issues
@GetMapping("/issues")
public String showList(Model model) {
var issueList:List<IssueEntity> = List.of(
new IssueEntity(1, "概要1", "説明1"),
new IssueEntity(2, "概要2", "説明2"),
new IssueEntity(3, "概要3", "説明3")
);
model.addAllAttribute(attributeName:"issueList",issueList);
return "issues/list";
}
}
6.VIEWを作成する
①src > main > resource > templeteに issuesフォルダにlist.htmlファイルを作成する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題一覧 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題一覧</h1>
<table>
<thead>
<tr>
<th>#</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<!-- Javaから渡された複数件のissueを1件ずつ取る -->
<tr th:each="issue : ${issueList}">
<th th:text="${issueList.id}">(id)</th>
<td th:text="${issueList}.summry">(summary)</td>
</tr>
</tbody>
</table>
</body>
</html>
7.ページ間を移動できる用にリンクを貼る
①templete/index.htmlファイルを編集する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理アプリケーション</h1>
<ul>
<li><a href="./issues/list.html" th:href="@{/issues}">課題一覧</a></li>
</ul>
</body>
</html>
②templete/issues/list.htmlファイルを編集する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題一覧 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題一覧</h1>
<a href="../index.html" th:href="@/">トップページ</a>
<table>
<thead>
<tr>
<th>#</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<!-- Javaから渡された複数件のissueを1件ずつ取る -->
<tr th:each="issue : ${issueList}">
<th th:text="${issueList.id}">(id)</th>
<td th:text="${issueList}.summry">(summary)</td>
</tr>
</tbody>
</table>
</body>
</html>
8.Lombokを使ってリファクタリングをする
※ Lombokとは、コンストラクターやゲッター、セッターのコードを自動生成してくれるライブラリーのこと
①下記のコードを加える。
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
//dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
②@AllArgsConstructorと@Dataをdomainissue/IssueEntityに記入する。
package com.example.its.domainissue;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class IssueEntity {
private long id;
private String summary;
private String description;
}
9.サービス層を作ってビジネスロジックを分離する
①issueフォルダに、IssueServiceファイルを作成する。
package com.example.its.web.issue;
import com.example.its.domainissue.IssueEntity;
import java.util.List;
public class IssueService {
public List<IssueEntity> findAll() {
return List.of(
new IssueEntity(1, "概要1", "説明1"),
new IssueEntity(2, "概要2", "説明2"),
new IssueEntity(3, "概要3", "説明3"));
}
}
②IssueController.javaファイルを編集する。
package com.example.its.web.issue;
import com.example.its.web.issue.IssueService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IssueController {
private final IssueService issueService = new IssueService();
@GetMapping("/issues")
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
}
10.dependency injectionを使う
DIとは?
DI:Dependency Injection(依存性の注入)
依存性の注入:使いたいオブジェクトを直接newせず、外部から渡してもらうようにするというデザインパターン
『直接newせず外部からもらう』とは?
//DI無
@Controller
public class IssueController { //newしている
private final IssueService IssueService = new IssueService();
}
//DI有
@Controller
public class IssueController {
private final IssueService IssueService;
//コンストラクタを呼び出す側でnewしてもらう
public IssueController (IssueService IssueService){
this.IssueService = IssueService;
}
}
①issue/IssueServiceに、@Serviceを加える。
package com.example.its.web.issue;
import com.example.its.domainissue.IssueEntity;
import java.util.List;
import org.springframework.stereotype.Service;
//ビジネスロジック・アルゴリズム等が書かれた処理を提供する
@Service
public class IssueService {
public List<IssueEntity> findAll() {
return List.of(
new IssueEntity(1, "概要1", "説明1"),
new IssueEntity(2, "概要2", "説明2"),
new IssueEntity(3, "概要3", "説明3"));
}
}
②issue/IssueService.javaに、引数有のコンストラクターを用意する。
package com.example.its.web.issue;
import com.example.its.web.issue.IssueService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
@RequiredArgsConstructor
public class IssueController {
private final IssueService issueService;
@GetMapping("/issues")
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
}
11.H2データベースとMyBatisのセットアップをする
①spring initializrに『H2 Database』を追加して、『MyBatis』を押し、下記のファイルにコードを加える。
dependencies {
//
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
runtimeOnly 'com.h2database:h2'
}
② resources/application.propertiesを編集する。
spring.thymeleaf.prefix=file:src/main/resources/templates/
spring.datasource.data-source-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:its;DB_CLOSE_ON_EXIT=TRUE;MODE=MySQL
spring.datasource.username=sa
spring.datasource.password=
③ src > main > resource に schema.sql と data.sqlでテーブル作成と初期データ投入する。
create table issues (
id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
summary VARCHAR(256) NOT NULl,
description VARCHAR(256) NOT NULl
);
insert into issues (summary, description) values ('バグA', 'バグがあります');
insert into issues (summary, description) values ('機能要望B', 'Bに追加機能がほしいです');
insert into issues (summary, description) values ('画面Cが遅い', '早くしてほしいです');
12. データアクセス層を作ってデータベースにselect文を実行する
①domainissueフォルダを作成し、IssueReositoryファイルを作成する。
package com.example.its.domainissue;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface IssueRepository {
@Select("Select * from issue")
List<IssueEntity> findAll();
}
②IssueServiceファイルを編集する。
package com.example.its.domainissue;
import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class IssueService {
praivate final IssueRepository IssueRepository;
public List<IssueEntity> findAll() {
return IssueRepository.finalAll();
}
}
13.課題作成のための入力フォームを作る
①HTML画面を作成する為、templete/issues/creationForm.htmlファイルを作成する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理</h1>
<form>
<div>
<label for="summaryInput">概要</label>
<input type="text" id="summaryInput" />
</div>
<div>
<label for="descriptionInput">説明</label>
<textarea type="text" id="descriptionInput" row="10"></textarea>
</div>
<div>
<button type="submit">作成</button>
</div>
</form>
</body>
</html>
②issueControllerの中にハンドラーメソッドを作成する。
package com.example.its.web.issue;
import com.example.its.domainissue.IssueService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm() {
return "issues/creationForm";
}
}
③一覧画面に作成画面のリンクを表示する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題一覧 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題一覧</h1>
<a href="../index.html" th:href="@/">トップページ</a>
<a href="./creationtForm.html" th:href="@/issues/creationtForm">作成</a>
<table>
<thead>
<tr>
<th>#</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<!-- Javaから渡された複数件のissueを1件ずつ取る -->
<tr th:each="issue : ${issueList}">
<th th:text="${issueList.id}">(id)</th>
<td th:text="${issueList}.summry">(summary)</td>
</tr>
</tbody>
</table>
</body>
</html>
14. 課題作成のリクエストを受けるハンドラーメソッドを実装する
①イシューコントローラーの中にメソッドを作る。
package com.example.its.web.issue;
import com.example.its.domainissue.IssueService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm() {
return "issues/creationForm";
}
@PostMapping("creationForm")
public String create(IssueForm form, Model model) {
return showList(model);
}
}
②issuesフォルダに、IssueFormファイルを作成する。
package com.example.its.web.issue;
import lombok.Data;
@Data
public class issueForm {
private String summary;
private String description;
}
③issues/creationForm.htmlを編集する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理</h1>
<form action="#" th:action="@{/issues}" th:method="post">
<div>
<label for="summaryInput">概要</label>
<input type="text" id="summaryInput" />
</div>
<div>
<label for="descriptionInput">説明</label>
<textarea type="text" id="descriptionInput" row="10"></textarea>
</div>
<div>
<button type="submit">作成</button>
</div>
</form>
</body>
</html>
15.Viewからデータを受け取り、受け取ったデータをDBに保存する
①issues/creationForm.htmlを編集する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理</h1>
<form
action="#"
th:action="@{/issues}"
th:method="post"
th:object="${issueForm}"
>
<div>
<label for="summaryInput">概要</label>
<input type="text" id="summaryInput" th:field="*{summary}" />
</div>
<div>
<label for="descriptionInput">説明</label>
<textarea
type="text"
id="descriptionInput"
row="10"
th:field="*{description}"
></textarea>
</div>
<div>
<button type="submit">作成</button>
</div>
</form>
</body>
</html>
②issue/IssueController.javaを編集する。
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm(@ModelAttribute issueForm form) {
return "issues/creationForm";
}
@PostMapping
public String create(issueForm form, Model model) {
issueService.create(form.getSummary(), form.getDescription());
return showList(model);
}
}
③issue/IssueServiceファイルを編集する。
package com.example.its.domainissue;
import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class IssueService {
praivate final IssueRepository IssueRepository;
public List<IssueEntity> findAll() {
IssueRepository.insert(summary, description);
}
}
④issue/IssueRepositoryファイルを編集する。
package com.example.its.domainissue;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface IssueRepository {
@Select("Select * from issues")
List<IssueEntity> findAll();
@Insert("insert into issue(summary, description) values(#{summary},#{description})")
void insert(String summary, String description);
}
16. 登録処理にトランザクションを張る
※ トランザクションとは、複数の処理を1まとまりにして扱う考え方。
※ ロールバックとは、途中で処理が失敗したときに、それまでの変更なかったことにするという機能。
package com.example.its.domainissue;
import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class IssueService {
praivate
final IssueRepository IssueRepository;
public List<IssueEntity> findAll() {
return IssueRepository.findAll();
}
@Transactional
public void create(String summary, String description) {
IssueRepository.insert(summary, description);
}
}
17. 2重サブミット対策をする
※2重サブミットとは、二重で実行されるとシステムとして困る操作を二重ですること
※2重サブフィットが起こる原因は、ブラウザーのリロード処理が直前のリクエストを再実行するから
①issue/IssueController.javaを編集する。
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm(@ModelAttribute issueForm form) {
return "issues/creationForm";
}
@PostMapping
public String create(issueForm form, Model model) {
issueService.create(form.getSummary(), form.getDescription());
return "redirect:/issues";
}
}
18. バリデーションを実装する
①spring initializrに『Validation』を追加して、『EXPLORE』を押し、下記のファイルにコードを加える。
dependencies {
//
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
②issues/IssueForm.javaを編集する。
package com.example.its.web.issue;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Data
public class issueForm {
@NotBlankBlank
@Size(max = 256)
private String summary;
@NotBlankBlank
@Size(max = 256)
private String description;
}
③issue/IssueController.javaを編集する。
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm(@ModelAttribute issueForm form) {
return "issues/creationForm";
}
@PostMapping
public String create(@Validated issueForm form, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
return showCreationForm(form;)
}
issueService.create(form.getSummary(), form.getDescription());
return "redirect:/issues";
}
}
④issues/creationForm.htmlを編集する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理</h1>
<form
action="#"
th:action="@{/issues}"
th:method="post"
th:object="${issueForm}"
>
<div>
<label for="summaryInput">概要</label>
<input type="text" id="summaryInput" th:field="*{summary}" />
<p th:if="${#fields.hasErrors('summary')}" th:errors="*{summary}">
(error)
</p>
</div>
<div>
<label for="descriptionInput">説明</label>
<textarea
type="text"
id="descriptionInput"
row="10"
th:field="*{description}"
></textarea>
<p
th:if="${#fields.hasErrors('description')}"
th:errors="*{description}"
>
(error)
</p>
</div>
<div>
<button type="submit">作成</button>
</div>
</form>
</body>
</html>
19. 課題詳細画面を実装
(1)View
①issues/detail.htmlを作成する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題詳細 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題詳細</h1>
<div>
<h2 th:text="${issue.summary}">(summary)</h2>
<p th:text="${issue.description}">(description)</p>
</div>
</body>
</html>
(2)Controller
①issue/IssueController.javaを編集する。
package com.example.its.web.issue;
import com.example.its.domainissue.IssueEntity;
import com.example.its.domainissue.IssueService;
import lombok.RequiredArgsConstructor;
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;
@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm(@ModelAttribute issueForm form) {
return "issues/creationForm";
}
@PostMapping
public String create(@Validated issueForm form, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
return showCreationForm(form;)
}
issueService.create(form.getSummary(), form.getDescription());
return "redirect:/issues";
}
@GetMapping("/{issueId}")
public String showDetail(@PathVariable("issueId") long issueId, Model model) {
IssueEntity dummyEntity = new IssueEntity(id:1,summary:"概要", description:"説明"));
model.addAttribute("issue", dummyEntity);
return "issues/detail";
}
}
(3)Serivce + Repository
①issue/IssueController.javaを編集する。
package com.example.its.web.issue;
import com.example.its.domainissue.IssueEntity;
import com.example.its.domainissue.IssueService;
import lombok.RequiredArgsConstructor;
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;
@Controller
@RequestMapping("/issues")
@RequiredArgsConstructor
public class IssueController {
private final IssueService issueService;
@GetMapping
public String showList(Model model) {
model.addAttribute("issueList", issueService.findAll());
return "issues/list";
}
@GetMapping("creationForm")
public String showCreationForm(@ModelAttribute issueForm form) {
return "issues/creationForm";
}
@PostMapping
public String create(@Validated issueForm form, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
return showCreationForm(form;)
}
issueService.create(form.getSummary(), form.getDescription());
return "redirect:/issues";
}
@GetMapping("/{issueId}")
public String showDetail(@PathVariable("issueId") long issueId, Model model) {
model.addAttribute("issue", issueService.findById(issueId));
return "issues/detail";
}
}
②issue/IssueController.javaを編集する。
package com.example.its.domainissue;
import lombok.RequiredArgsConstructor;
import com.example.its.domainissue.IssueEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class IssueService {
praivate
final IssueRepository IssueRepository;
public List<IssueEntity> findAll() {
return IssueRepository.findAll();
}
@Transactional
public void create(String summary, String description) {
IssueRepository.insert(summary, description);
}
public IssueEntity findById(long issueId){
return IssueRepository.findById(issueId);
}
}
③issue/IssueRepository.javaを編集する。
package com.example.its.domainissue;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface IssueRepository {
@Select("Select * from issues")
List<IssueEntity> findAll();
@Insert("insert into issue(summary, description) values(#{summary},#{description})")
void insert(String summary, String description);
@Select("select * from issues where id = #{issueId}")
IssueEntity findById(long issueId);
}
(4)画面遷移のためのリンクを作る
①issue/IssueRepository.javaを編集する。
package com.example.its.domainissue;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface IssueRepository {
@Select("Select * from issues")
List<IssueEntity> findAll();
@Insert("insert into issue(summary, description) values(#{summary},#{description})")
void insert(String summary, String description);
@Select("select * from issues where id = #{issueId}")
IssueEntity findById(long issueId);
}
②issues/list.htmlのth:textをaタグに変える。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題一覧 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題一覧</h1>
<a href="../index.html" th:href="@/">トップページ</a>
<a href="./creationtForm.html" th:href="@/issues/creationtForm">作成</a>
<table>
<thead>
<tr>
<th>#</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<!-- Javaから渡された複数件のissueを1件ずつ取る -->
<tr th:each="issue : ${issueList}">
<th th:text="${issueList.id}">(id)</th>
<td>
<a
href="./detail.html"
th:href="@{/issueList/{issueId}(issueId=${issue.id})}"
th:text="${issueList.summary}"
>(summary)
</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
③issues/detail.htmlを編集する。
<!DOCTYPE >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>課題詳細 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題詳細</h1>
<a href="./list.html" th:href="@{/issues}">一覧に戻る</a>
<div>
<h2 th:text="${issue.summary}">(summary)</h2>
<p th:text="${issue.description}">(description)</p>
</div>
</body>
</html>
20. Thymleaf のすべてのページでフラグメントを使ってリファクタリングする
①templates/fragments/layout.htmlを作成する。
<!DOCTYPE >
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:fragment="layout(title,content)"
>
<head>
<meta charset="UTF-8" />
<title th:replace="${title}">(default title)</title>
</head>
<body>
<div th:insert="${content}">(default title)</div>
</body>
</html>
②templates/issues/index.htmlを編集する。
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
<head>
<meta charset="UTF-8" />
<title>トップページ | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理アプリケーション</h1>
<ul>
<li><a href="./issues/list.html" th:href="@{/issues}">課題一覧</a></li>
</ul>
</body>
</html>
③templates/issues/list.htmlをリファクタリングする。
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
<head>
<title>課題一覧 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題一覧</h1>
<a href="../index.html" th:href="@/">トップページ</a>
<a href="./creationtForm.html" th:href="@/issues/creationtForm">作成</a>
<table>
<thead>
<tr>
<th>#</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<!-- Javaから渡された複数件のissueを1件ずつ取る -->
<tr th:each="issue : ${issueList}">
<th th:text="${issueList.id}">(id)</th>
<td>
<a
href="./detail.html"
th:href="@{/issueList/{issueId}(issueId=${issue.id})}"
th:text="${issueList.summary}"
>(summary)
</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
④templates/issues/detail.htmlをリファクタリングする。
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
<head>
<title>課題詳細 | 課題管理アプリケーション</title>
</head>
<body>
<h1>課題詳細</h1>
<a href="./list.html" th:href="@{/issues}">一覧に戻る</a>
<div>
<h2 th:text="${issue.summary}">(summary)</h2>
<p th:text="${issue.description}">(description)</p>
</div>
</body>
</html>
④templates/issues/creationForm.htmlをリファクタリングする。
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
<head>
<title>課題管理アプリケーション</title>
</head>
<body>
<h1>課題管理</h1>
<form
action="#"
th:action="@{/issues}"
th:method="post"
th:object="${issueForm}"
>
<div>
<label for="summaryInput">概要</label>
<input type="text" id="summaryInput" th:field="*{summary}" />
<p th:if="${#fields.hasErrors('summary')}" th:errors="*{summary}">
(error)
</p>
</div>
<div>
<label for="descriptionInput">説明</label>
<textarea
type="text"
id="descriptionInput"
row="10"
th:field="*{description}"
></textarea>
<p
th:if="${#fields.hasErrors('description')}"
th:errors="*{description}"
>
(error)
</p>
</div>
<div>
<button type="submit">作成</button>
</div>
</form>
</body>
</html>
20. Boostrapを使用する
(1) BoostrapをCDNから読み込む。
公式サイト : Boostrap
<!DOCTYPE >
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:fragment="layout(title,content)"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title th:replace="${title}">(default title)</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous"
/>
</head>
<body>
<div th:insert="${content}"></div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"
></script>
</body>
</html>
(2) 余白を調整する:Container
①templates/fragments/layout.htmlに divタグに class="container"を記載する
<!DOCTYPE >
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:fragment="layout(title,content)"
>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title th:replace="${title}">(default title)</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous"
/>
</head>
<body>
<div class="container" th:insert="${content}"></div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"
></script>
</body>
</html>
(3)余白を調整する:Spacing
①htmlファイルに h1要素にmt-3というclassを付ける。
(4)テーブルのスタイルを調整する
①templates/issues/list.htmlに、TABLE要素にCLASSでTABLEと書く。
(5)フォームのスタイルを調整する
①templates/issues/creationForm.htmlを概要のラベルにCLASSでフォームラベルを指定する。
②input要素にはCLASSのフォームコントロールを指定する。
③作成ボタンにボタンクラスとボタンプライマリークラスを付ける。
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
<head>
<title>課題管理アプリケーション</title>
</head>
<body>
<h1 class="mt-3">課題管理</h1>
<form
action="#"
th:action="@{/issues}"
th:method="post"
th:object="${issueForm}"
>
<div class="mt-3">
<label for="summaryInput" class="form-label">概要</label>
<input
type="text"
id="summaryInput"
th:field="*{summary}"
class="form-control"
/>
<p th:if="${#fields.hasErrors('summary')}" th:errors="*{summary}">
(error)
</p>
</div>
<div class="mt-3">
<label for="descriptionInput" class="form-label">説明</label>
<textarea
type="text"
id="descriptionInput"
row="10"
th:field="*{description}"
class="form-control"
></textarea>
<p
th:if="${#fields.hasErrors('description')}"
th:errors="*{description}"
>
(error)
</p>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-primary">作成</button>
<a href="./list.html" th:href="@{/issues}" class="btn btn-secondary"
>キャンセル</a
>
</div>
</form>
</body>
</html>
(6)リデーションのスタイルを調整する
①バリデーションエラー時のみインバリとCLASSをCLASS属性に追加する。
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout(~{::title},~{::body})}"
>
<head>
<title>課題管理アプリケーション</title>
</head>
<body>
<h1 class="mt-3">課題管理</h1>
<form
action="#"
th:action="@{/issues}"
th:method="post"
th:object="${issueForm}"
>
<div class="mt-3">
<label for="summaryInput" class="form-label">概要</label>
<input
type="text"
id="summaryInput"
th:field="*{summary}"
class="form-control"
th:classappend="${#fields.hasErrors('summary')} ? is-invalid"
/>
<p
class="invalid-feedback"
th:if="${#fields.hasErrors('summary')}"
th:errors="*{summary}"
>
(error)
</p>
</div>
<div class="mt-3">
<label for="descriptionInput" class="form-label">説明</label>
<textarea
type="text"
id="descriptionInput"
row="10"
th:field="*{description}"
class="form-control"
is-invalid
></textarea>
<p
class="invalid-feedback"
th:if="${#fields.hasErrors('description')}"
th:errors="*{description}"
>
(error)
</p>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-primary">作成</button>
<a href="./list.html" th:href="@{/issues}" class="btn btn-secondary"
>キャンセル</a
>
</div>
</form>
</body>
</html>
参考サイト
IntelliJ ではじめる Spring Boot:課題管理アプリを作って学ぶWebアプリケーション開発の基礎
【5分でわかる】Springでよく使うアノテーション40選! 基本からバリデーションまでまるっとご紹介