第15章までを終えたときの、社員管理アプリのすべてのファイルです。うまく動かないときは、自分のコードと見比べてみましょう。
【知識メモ】このソースコードについて
ここに掲載したコードは、実際に Oracle Database 21c XE に接続して、すべての機能が動くことを確認したものです。同じファイルは、この教材を打ち込んだフォルダーの sample/employee-app にも入っているはずです。
A.0 フォルダーの構成
employee-app
employee-app
├─ src/main/java
│ └─ com.example.employeeapp
│ ├─ EmployeeAppApplication.java
│ ├─ component
│ │ └─ GreetingMessage.java(練習用)
│ ├─ controller
│ │ ├─ EmployeeListController.java
│ │ ├─ EmployeeRegisterController.java
│ │ ├─ EmployeeUpdateController.java
│ │ └─ HelloController.java(練習用)
│ ├─ entity
│ │ ├─ Department.java
│ │ └─ Employee.java
│ ├─ form
│ │ ├─ RegisterForm.java
│ │ └─ UpdateForm.java
│ └─ repository
│ ├─ DepartmentRepository.java
│ └─ EmployeeRepository.java
├─ src/main/resources
│ ├─ application.properties
│ ├─ messages.properties
│ ├─ static
│ │ └─ css
│ │ └─ app.css
│ └─ templates
│ ├─ hello.html(練習用)
│ └─ employee
│ ├─ list.html
│ ├─ register
│ │ ├─ input.html
│ │ ├─ confirm.html
│ │ └─ complete.html
│ └─ update
│ ├─ input.html
│ ├─ confirm.html
│ └─ complete.html
├─ src/test/java
│ └─ com.example.employeeapp
│ ├─ EmployeeAppApplicationTests.java(Eclipse が作成)
│ └─ RepositoryCheckTest.java
└─ pom.xml
A.1 データベース(SQL)
第3章で SQL Developer から実行した SQL です。
ワークシート(XE_SYSTEM)
-- アプリ専用のユーザー empapp を作成する
CREATE USER empapp IDENTIFIED BY empapp;
-- empapp にすべての権限を与える(学習用)
GRANT ALL PRIVILEGES TO empapp;
ワークシート(XE_EMPAPP)
-- 部署IDの番号を振るシーケンス
CREATE SEQUENCE SEQ_DEPT NOCACHE;
-- 社員IDの番号を振るシーケンス
CREATE SEQUENCE SEQ_EMP NOCACHE;
-- 部署テーブル
CREATE TABLE DEPARTMENT (
DEPT_ID NUMBER(4) CONSTRAINT PK_DEPARTMENT PRIMARY KEY,
DEPT_NAME VARCHAR2(20 CHAR) NOT NULL
);
-- 社員テーブル
CREATE TABLE EMPLOYEE (
EMP_ID NUMBER(6) CONSTRAINT PK_EMPLOYEE PRIMARY KEY,
NAME VARCHAR2(20 CHAR) NOT NULL,
GENDER NUMBER(1) NOT NULL,
AGE NUMBER(3) NOT NULL,
BIRTH_DATE DATE NOT NULL,
DEPT_ID NUMBER(4) NOT NULL
CONSTRAINT FK_EMPLOYEE_DEPARTMENT REFERENCES DEPARTMENT (DEPT_ID),
ADDRESS VARCHAR2(10 CHAR) NOT NULL
);
-- 部署(DEPT_ID は SEQ_DEPT から 1, 2, 3 と振られる)
INSERT INTO DEPARTMENT (DEPT_ID, DEPT_NAME) VALUES (SEQ_DEPT.NEXTVAL, '総務部');
INSERT INTO DEPARTMENT (DEPT_ID, DEPT_NAME) VALUES (SEQ_DEPT.NEXTVAL, '営業部');
INSERT INTO DEPARTMENT (DEPT_ID, DEPT_NAME) VALUES (SEQ_DEPT.NEXTVAL, '開発部');
-- 社員(EMP_ID は SEQ_EMP から 1, 2, 3 と振られる)
INSERT INTO EMPLOYEE (EMP_ID, NAME, GENDER, AGE, BIRTH_DATE, DEPT_ID, ADDRESS)
VALUES (SEQ_EMP.NEXTVAL, '雷禅 海皇', 1, 26, DATE '2000-06-22', 2, '大阪府');
INSERT INTO EMPLOYEE (EMP_ID, NAME, GENDER, AGE, BIRTH_DATE, DEPT_ID, ADDRESS)
VALUES (SEQ_EMP.NEXTVAL, '院照 心愛', 2, 27, DATE '1999-07-11', 1, '京都府');
INSERT INTO EMPLOYEE (EMP_ID, NAME, GENDER, AGE, BIRTH_DATE, DEPT_ID, ADDRESS)
VALUES (SEQ_EMP.NEXTVAL, '林檎 星飛', 1, 24, DATE '2001-12-24', 3, '東京都');
-- 登録を確定する
COMMIT;
A.2 設定ファイル
| ファイル | 役割 | 作る章 |
|---|---|---|
application.properties |
データベースの接続設定など | 第4章 |
messages.properties |
入力チェックのエラーメッセージ | 第13章 |
pom.xml |
プロジェクトの設定と依存関係(Eclipse が作成。必要に応じて編集することもあるが、この教材では編集しない) | 第4章 |
application.properties
src/main/resources/application.properties
spring.application.name=employee-app
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1
spring.datasource.username=empapp
spring.datasource.password=empapp
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.open-in-view=false
spring.thymeleaf.cache=false
server.servlet.session.tracking-modes=cookie
messages.properties
src/main/resources/messages.properties
# 入力チェックのエラーメッセージ
name.notBlank=氏名を入力してください。
name.size=氏名は20文字以内で入力してください。
gender.notNull=性別を選択してください。
age.notNull=年齢を入力してください。
age.range=年齢は15〜99の範囲で入力してください。
birthDate.notNull=生年月日を入力してください。
birthDate.past=生年月日には過去の日付を入力してください。
deptId.notNull=部署を選択してください。
address.notBlank=住所(都道府県)を入力してください。
address.size=住所(都道府県)は10文字以内で入力してください。
# 入力された値の形式が正しくないとき(数値の欄に文字を入力したときなど)のエラーメッセージ
typeMismatch.age=年齢は半角数字で入力してください。
typeMismatch.birthDate=生年月日を正しく入力してください。
pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>employee-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>employee-app</name>
<description/>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc17</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
A.3 Entity
| ファイル | 役割 | 作る章 |
|---|---|---|
Department.java |
部署テーブルに対応するクラス | 第6章 |
Employee.java |
社員テーブルに対応するクラス | 第6章 |
Department.java
src/main/java/com/example/employeeapp/entity/Department.java
package com.example.employeeapp.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
@Entity
@Table(name = "DEPARTMENT")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_DEPT")
@SequenceGenerator(name = "SEQ_DEPT", sequenceName = "SEQ_DEPT", allocationSize = 1)
private Integer deptId;
@Column
private String deptName;
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
Employee.java
src/main/java/com/example/employeeapp/entity/Employee.java
package com.example.employeeapp.entity;
import java.util.Date;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_EMP")
@SequenceGenerator(name = "SEQ_EMP", sequenceName = "SEQ_EMP", allocationSize = 1)
private Integer empId;
@Column
private String name;
@Column
private Integer gender;
@Column
private Integer age;
@Column
private Date birthDate;
@ManyToOne
@JoinColumn(name = "DEPT_ID", referencedColumnName = "deptId")
private Department department;
@Column
private String address;
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
A.4 Repository
| ファイル | 役割 | 作る章 |
|---|---|---|
DepartmentRepository.java |
部署テーブルを操作する | 第7章 |
EmployeeRepository.java |
社員テーブルを操作する | 第7章 |
DepartmentRepository.java
src/main/java/com/example/employeeapp/repository/DepartmentRepository.java
package com.example.employeeapp.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.employeeapp.entity.Department;
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Integer> {
List<Department> findAllByOrderByDeptIdAsc();
Department findByDeptId(Integer deptId);
}
EmployeeRepository.java
src/main/java/com/example/employeeapp/repository/EmployeeRepository.java
package com.example.employeeapp.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.employeeapp.entity.Employee;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
List<Employee> findAllByOrderByEmpIdAsc();
Employee findByEmpId(Integer empId);
}
A.5 フォーム
| ファイル | 役割 | 作る章 |
|---|---|---|
RegisterForm.java |
新規登録の入力内容(入力チェック付き) | 第10章 |
UpdateForm.java |
変更の入力内容(RegisterForm に社員IDを足したもの) | 第12章 |
RegisterForm.java
src/main/java/com/example/employeeapp/form/RegisterForm.java
package com.example.employeeapp.form;
import java.io.Serializable;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
public class RegisterForm implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "{name.notBlank}")
@Size(max = 20, message = "{name.size}")
private String name;
@NotNull(message = "{gender.notNull}")
private Integer gender;
@NotNull(message = "{age.notNull}")
@Min(value = 15, message = "{age.range}")
@Max(value = 99, message = "{age.range}")
private Integer age;
@NotNull(message = "{birthDate.notNull}")
@Past(message = "{birthDate.past}")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
@NotNull(message = "{deptId.notNull}")
private Integer deptId;
@NotBlank(message = "{address.notBlank}")
@Size(max = 10, message = "{address.size}")
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
UpdateForm.java
src/main/java/com/example/employeeapp/form/UpdateForm.java
package com.example.employeeapp.form;
public class UpdateForm extends RegisterForm {
private static final long serialVersionUID = 1L;
private Integer empId;
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
}
A.6 Controller
| ファイル | 役割 | 作る章 |
|---|---|---|
EmployeeListController.java |
一覧・削除 | 第8章 |
EmployeeRegisterController.java |
新規登録 | 第10章 |
EmployeeUpdateController.java |
変更 | 第12章 |
EmployeeListController.java
src/main/java/com/example/employeeapp/controller/EmployeeListController.java
package com.example.employeeapp.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.repository.EmployeeRepository;
@Controller
public class EmployeeListController {
@Autowired
private EmployeeRepository employeeRepository;
/** 社員一覧画面を表示
* 社員IDの順に社員を取り出して表示
* @param model 表示する社員情報
* @return 社員一覧画面 */
@GetMapping("/employees")
public String list(Model model) {
List<Employee> employees = employeeRepository.findAllByOrderByEmpIdAsc();
model.addAttribute("employees", employees);
return "employee/list";
}
/** 社員を削除して社員一覧画面に戻る
* @param empId 削除する社員の社員ID
* @return 社員一覧画面 */
@PostMapping("/employees/{empId}/delete")
public String delete(@PathVariable Integer empId) {
employeeRepository.deleteById(empId);
return "redirect:/employees";
}
}
EmployeeRegisterController.java
src/main/java/com/example/employeeapp/controller/EmployeeRegisterController.java
package com.example.employeeapp.controller;
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.PostMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.example.employeeapp.entity.Department;
import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.form.RegisterForm;
import com.example.employeeapp.repository.DepartmentRepository;
import com.example.employeeapp.repository.EmployeeRepository;
@Controller
@SessionAttributes("registerForm")
public class EmployeeRegisterController {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private DepartmentRepository departmentRepository;
/** セッションに RegisterForm オブジェクトがないとき呼ばれる
* @return 空の RegisterForm オブジェクト */
@ModelAttribute
public RegisterForm createForm() {
return new RegisterForm();
}
/** 社員登録の入力画面を表示
* @param model 表示する入力フォームと部署一覧
* @return 社員登録の入力画面 */
@GetMapping("/employees/register")
public String input(Model model) {
model.addAttribute("registerForm", new RegisterForm());
List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
model.addAttribute("departments", departments);
return "employee/register/input";
}
/** 社員登録の確認画面を表示
* @param registerForm 入力された社員情報
* @param bindingResult 入力チェックの結果
* @param model 表示する部署情報
* @return 正常:社員登録の確認画面
* エラー:社員登録の入力画面 */
@PostMapping("/employees/register/confirm")
public String confirm(@Validated @ModelAttribute RegisterForm registerForm,
BindingResult bindingResult, Model model) {
// 入力チェックでエラーがあれば、エラーメッセージを付けて入力画面に戻す
if (bindingResult.hasErrors()) {
List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
model.addAttribute("departments", departments);
return "employee/register/input";
}
Department department = departmentRepository.findByDeptId(registerForm.getDeptId());
model.addAttribute("department", department);
return "employee/register/confirm";
}
/** 社員登録の確認画面から入力画面に戻る
* @param model 表示する部署一覧
* @return 社員登録の入力画面 */
@GetMapping("/employees/register/back")
public String back(Model model) {
List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
model.addAttribute("departments", departments);
return "employee/register/input";
}
/** 社員登録の完了画面を表示
* @param registerForm 入力された社員情報
* @param sessionStatus セッション破棄時に利用
* @return 社員登録の完了画面 */
@PostMapping("/employees/register/complete")
public String complete(@ModelAttribute RegisterForm registerForm,
SessionStatus sessionStatus) {
Employee employee = new Employee();
employee.setName(registerForm.getName());
employee.setGender(registerForm.getGender());
employee.setAge(registerForm.getAge());
employee.setBirthDate(registerForm.getBirthDate());
employee.setDepartment(departmentRepository.findByDeptId(registerForm.getDeptId()));
employee.setAddress(registerForm.getAddress());
employeeRepository.save(employee);
// セッションに保存していた入力内容を破棄する
sessionStatus.setComplete();
return "redirect:/employees/register/done";
}
/** 社員登録の完了画面を表示
* @return 社員登録の完了画面 */
@GetMapping("/employees/register/done")
public String done() {
return "employee/register/complete";
}
}
EmployeeUpdateController.java
src/main/java/com/example/employeeapp/controller/EmployeeUpdateController.java
package com.example.employeeapp.controller;
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.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.example.employeeapp.entity.Department;
import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.form.UpdateForm;
import com.example.employeeapp.repository.DepartmentRepository;
import com.example.employeeapp.repository.EmployeeRepository;
@Controller
@SessionAttributes("updateForm")
public class EmployeeUpdateController {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private DepartmentRepository departmentRepository;
/** セッションに UpdateForm オブジェクトがないとき呼ばれる
* @return 空の UpdateForm オブジェクト */
@ModelAttribute
public UpdateForm createForm() {
return new UpdateForm();
}
/** 社員変更の入力画面を表示
* その社員の今のデータをフォームに入れて表示
* @param empId 変更する社員ID(取得元:URL)
* @param model 表示する社員情報と部署一覧
* @return 社員変更の入力画面 */
@GetMapping("/employees/{empId}/edit")
public String input(@PathVariable Integer empId, Model model) {
Employee employee = employeeRepository.findByEmpId(empId);
UpdateForm updateForm = new UpdateForm();
updateForm.setEmpId(employee.getEmpId());
updateForm.setName(employee.getName());
updateForm.setGender(employee.getGender());
updateForm.setAge(employee.getAge());
updateForm.setBirthDate(employee.getBirthDate());
updateForm.setDeptId(employee.getDepartment().getDeptId());
updateForm.setAddress(employee.getAddress());
model.addAttribute("updateForm", updateForm);
List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
model.addAttribute("departments", departments);
return "employee/update/input";
}
/** 社員変更の確認画面を表示
* @param updateForm 入力された社員情報
* @param bindingResult 入力チェックの結果
* @param model 表示する部署情報
* @return 正常:社員変更の確認画面
* エラー:社員変更の入力画面 */
@PostMapping("/employees/{empId}/edit/confirm")
public String confirm(@Validated @ModelAttribute UpdateForm updateForm,
BindingResult bindingResult, Model model) {
// 入力チェックでエラーがあれば、エラーメッセージを付けて入力画面に戻す
if (bindingResult.hasErrors()) {
List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
model.addAttribute("departments", departments);
return "employee/update/input";
}
Department department = departmentRepository.findByDeptId(updateForm.getDeptId());
model.addAttribute("department", department);
return "employee/update/confirm";
}
/** 社員変更の確認画面から入力画面に戻る
* @param updateForm 入力された社員情報
* @param model 表示する部署一覧
* @return 社員変更の入力画面 */
@GetMapping("/employees/{empId}/edit/back")
public String back(@ModelAttribute UpdateForm updateForm, Model model) {
List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
model.addAttribute("departments", departments);
return "employee/update/input";
}
/** 社員変更の完了画面を表示
* @param updateForm 入力された社員情報
* @param sessionStatus セッション破棄時に利用
* @return 社員変更の完了画面 */
@PostMapping("/employees/{empId}/edit/complete")
public String complete(@ModelAttribute UpdateForm updateForm, SessionStatus sessionStatus) {
Employee employee = employeeRepository.findByEmpId(updateForm.getEmpId());
employee.setName(updateForm.getName());
employee.setGender(updateForm.getGender());
employee.setAge(updateForm.getAge());
employee.setBirthDate(updateForm.getBirthDate());
employee.setDepartment(departmentRepository.findByDeptId(updateForm.getDeptId()));
employee.setAddress(updateForm.getAddress());
employeeRepository.save(employee);
// セッションに保存していた入力内容を破棄する
sessionStatus.setComplete();
return "redirect:/employees/edit/done";
}
/** 社員変更の完了画面を表示
* @return 社員変更の完了画面 */
@GetMapping("/employees/edit/done")
public String done() {
return "employee/update/complete";
}
}
A.7 テンプレート(画面)
| ファイル | 役割 | 作る章 |
|---|---|---|
list.html |
社員一覧 | 第8章 |
input.html |
新規登録:入力 | 第10章 |
confirm.html |
新規登録:確認 | 第11章 |
complete.html |
新規登録:完了 | 第11章 |
input.html |
変更:入力 | 第12章 |
confirm.html |
変更:確認 | 第12章 |
complete.html |
変更:完了 | 第12章 |
list.html
src/main/resources/templates/employee/list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員一覧</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員一覧</h1>
<p><a class="button" th:href="@{/employees/register}">新規登録</a></p>
<table>
<tr>
<th>社員ID</th>
<th>氏名</th>
<th>性別</th>
<th>年齢</th>
<th>生年月日</th>
<th>部署</th>
<th>住所</th>
<th>操作</th>
</tr>
<tr th:each="employee : ${employees}">
<td th:text="${employee.empId}"></td>
<td th:text="${employee.name}"></td>
<td th:switch="${employee.gender}">
<span th:case="1">男</span>
<span th:case="2">女</span>
</td>
<td th:text="${employee.age}"></td>
<td th:text="${#dates.format(employee.birthDate, 'yyyy/MM/dd')}"></td>
<td th:text="${employee.department.deptName}"></td>
<td th:text="${employee.address}"></td>
<td>
<a class="button" th:href="@{/employees/{empId}/edit(empId=${employee.empId})}">変更</a>
<form class="inline" th:action="@{/employees/{empId}/delete(empId=${employee.empId})}" method="post">
<button type="submit">削除</button>
</form>
</td>
</tr>
</table>
</body>
</html>
input.html
src/main/resources/templates/employee/register/input.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員新規登録</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員新規登録</h1>
<form th:action="@{/employees/register/confirm}" th:object="${registerForm}" method="post">
<table>
<tr>
<th>氏名</th>
<td>
<input type="text" th:field="*{name}">
<div class="error" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</td>
</tr>
<tr>
<th>性別</th>
<td>
<label><input type="radio" th:field="*{gender}" value="1"> 男</label>
<label><input type="radio" th:field="*{gender}" value="2"> 女</label>
<div class="error" th:if="${#fields.hasErrors('gender')}" th:errors="*{gender}"></div>
</td>
</tr>
<tr>
<th>年齢</th>
<td>
<input type="text" th:field="*{age}">
<div class="error" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></div>
</td>
</tr>
<tr>
<th>生年月日</th>
<td>
<input type="date" th:field="*{birthDate}">
<div class="error" th:if="${#fields.hasErrors('birthDate')}" th:errors="*{birthDate}"></div>
</td>
</tr>
<tr>
<th>部署</th>
<td>
<select th:field="*{deptId}">
<option value="">選択してください</option>
<option th:each="department : ${departments}"
th:value="${department.deptId}"
th:text="${department.deptName}"></option>
</select>
<div class="error" th:if="${#fields.hasErrors('deptId')}" th:errors="*{deptId}"></div>
</td>
</tr>
<tr>
<th>住所(都道府県)</th>
<td>
<input type="text" th:field="*{address}">
<div class="error" th:if="${#fields.hasErrors('address')}" th:errors="*{address}"></div>
</td>
</tr>
</table>
<button type="submit">確認</button>
</form>
<p><a th:href="@{/employees}">一覧へ戻る</a></p>
</body>
</html>
confirm.html
src/main/resources/templates/employee/register/confirm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員新規登録(確認)</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員新規登録(確認)</h1>
<p>次の内容で登録します。よろしければ「登録」ボタンを押してください。</p>
<table th:object="${registerForm}">
<tr><th>氏名</th><td th:text="*{name}"></td></tr>
<tr><th>性別</th><td th:switch="*{gender}"><span th:case="1">男</span><span th:case="2">女</span></td></tr>
<tr><th>年齢</th><td th:text="*{age}"></td></tr>
<tr><th>生年月日</th><td th:text="*{#dates.format(birthDate, 'yyyy/MM/dd')}"></td></tr>
<tr><th>部署</th><td th:text="${department?.deptName}"></td></tr>
<tr><th>住所(都道府県)</th><td th:text="*{address}"></td></tr>
</table>
<a class="button" th:href="@{/employees/register/back}">戻る</a>
<form class="inline" th:action="@{/employees/register/complete}" method="post">
<button type="submit">登録</button>
</form>
</body>
</html>
complete.html
src/main/resources/templates/employee/register/complete.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員新規登録(完了)</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員新規登録(完了)</h1>
<p>社員の登録が完了しました。</p>
<p><a th:href="@{/employees}">一覧へ戻る</a></p>
</body>
</html>
input.html
src/main/resources/templates/employee/update/input.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員変更</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員変更</h1>
<form th:action="@{/employees/{empId}/edit/confirm(empId=${updateForm.empId})}" th:object="${updateForm}" method="post">
<table>
<tr>
<th>社員ID</th>
<td th:text="*{empId}"></td>
</tr>
<tr>
<th>氏名</th>
<td>
<input type="text" th:field="*{name}">
<div class="error" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</td>
</tr>
<tr>
<th>性別</th>
<td>
<label><input type="radio" th:field="*{gender}" value="1"> 男</label>
<label><input type="radio" th:field="*{gender}" value="2"> 女</label>
<div class="error" th:if="${#fields.hasErrors('gender')}" th:errors="*{gender}"></div>
</td>
</tr>
<tr>
<th>年齢</th>
<td>
<input type="text" th:field="*{age}">
<div class="error" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></div>
</td>
</tr>
<tr>
<th>生年月日</th>
<td>
<input type="date" th:field="*{birthDate}">
<div class="error" th:if="${#fields.hasErrors('birthDate')}" th:errors="*{birthDate}"></div>
</td>
</tr>
<tr>
<th>部署</th>
<td>
<select th:field="*{deptId}">
<option value="">選択してください</option>
<option th:each="department : ${departments}"
th:value="${department.deptId}"
th:text="${department.deptName}"></option>
</select>
<div class="error" th:if="${#fields.hasErrors('deptId')}" th:errors="*{deptId}"></div>
</td>
</tr>
<tr>
<th>住所(都道府県)</th>
<td>
<input type="text" th:field="*{address}">
<div class="error" th:if="${#fields.hasErrors('address')}" th:errors="*{address}"></div>
</td>
</tr>
</table>
<button type="submit">確認</button>
</form>
<p><a th:href="@{/employees}">一覧へ戻る</a></p>
</body>
</html>
confirm.html
src/main/resources/templates/employee/update/confirm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員変更(確認)</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員変更(確認)</h1>
<p>次の内容に変更します。よろしければ「変更」ボタンを押してください。</p>
<table th:object="${updateForm}">
<tr><th>社員ID</th><td th:text="*{empId}"></td></tr>
<tr><th>氏名</th><td th:text="*{name}"></td></tr>
<tr><th>性別</th><td th:switch="*{gender}"><span th:case="1">男</span><span th:case="2">女</span></td></tr>
<tr><th>年齢</th><td th:text="*{age}"></td></tr>
<tr><th>生年月日</th><td th:text="*{#dates.format(birthDate, 'yyyy/MM/dd')}"></td></tr>
<tr><th>部署</th><td th:text="${department?.deptName}"></td></tr>
<tr><th>住所(都道府県)</th><td th:text="*{address}"></td></tr>
</table>
<a class="button" th:href="@{/employees/{empId}/edit/back(empId=${updateForm.empId})}">戻る</a>
<form class="inline" th:action="@{/employees/{empId}/edit/complete(empId=${updateForm.empId})}" method="post">
<button type="submit">変更</button>
</form>
</body>
</html>
complete.html
src/main/resources/templates/employee/update/complete.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員変更(完了)</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
<h1>社員変更(完了)</h1>
<p>社員の変更が完了しました。</p>
<p><a th:href="@{/employees}">一覧へ戻る</a></p>
</body>
</html>
A.8 CSS
| ファイル | 役割 | 作る章 |
|---|---|---|
app.css |
画面の見た目 | 第8章 |
app.css
src/main/resources/static/css/app.css
body { /* ページ全体 */
margin: 24px;
color: #222;
}
h1 { /* 見出し */
font-size: 24px;
}
table { /* 表 */
border-collapse: collapse;
margin-bottom: 16px;
}
th, td { /* 表のマス */
border: 1px solid #999;
padding: 6px 12px;
text-align: left;
}
th { /* 表の見出しのマス */
background-color: #eee;
}
a.button { /* リンクをボタンの見た目にする(新規登録・変更・戻るボタン用) */
display: inline-block;
padding: 1px 6px;
border: 1px solid #767676;
border-radius: 3px;
background-color: #efefef;
color: #000;
font-size: 13px;
text-decoration: none;
}
form.inline { /* フォームを横に並べる(削除ボタン用) */
display: inline;
}
.error { /* 入力チェックのエラーメッセージ */
color: #c00;
font-size: 14px;
}
A.9 その他(練習・起動用)
| ファイル | 役割 | 作る章 |
|---|---|---|
EmployeeAppApplication.java |
アプリを起動する main メソッド(Eclipse が作成) | 第4章 |
HelloController.java |
Hello World(練習用) | 第4章 |
GreetingMessage.java |
DI の練習用 | 第5章 |
hello.html |
Hello World の画面(練習用) | 第4章 |
RepositoryCheckTest.java |
Repository の動作確認テスト | 第7章 |
EmployeeAppApplication.java
src/main/java/com/example/employeeapp/EmployeeAppApplication.java
package com.example.employeeapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EmployeeAppApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeAppApplication.class, args);
}
}
HelloController.java
src/main/java/com/example/employeeapp/controller/HelloController.java
package com.example.employeeapp.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.example.employeeapp.component.GreetingMessage;
@Controller
public class HelloController {
@Autowired
private GreetingMessage greetingMessage;
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", greetingMessage.getMessage());
return "hello";
}
}
GreetingMessage.java
src/main/java/com/example/employeeapp/component/GreetingMessage.java
package com.example.employeeapp.component;
import org.springframework.stereotype.Component;
@Component
public class GreetingMessage {
public String getMessage() {
return "DI で受け取ったメッセージです";
}
}
hello.html
src/main/resources/templates/hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1>はじめての Spring Boot</h1>
<p th:text="${message}">ここにメッセージが入ります</p>
</body>
</html>
RepositoryCheckTest.java
src/test/java/com/example/employeeapp/RepositoryCheckTest.java
package com.example.employeeapp;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.employeeapp.entity.Department;
import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.repository.DepartmentRepository;
import com.example.employeeapp.repository.EmployeeRepository;
@SpringBootTest
class RepositoryCheckTest {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private DepartmentRepository departmentRepository;
/** 全社員を取得できること */
@Test
void findAllEmployees() {
List<Employee> list = employeeRepository.findAllByOrderByEmpIdAsc();
for (Employee employee : list) {
System.out.println(employee.getEmpId() + " " + employee.getName() + " "
+ employee.getDepartment().getDeptName());
}
assertEquals(3, list.size());
}
/** 社員IDを指定して社員を取得できること */
@Test
void findEmployee() {
Employee employee = employeeRepository.findByEmpId(1);
assertEquals("雷禅 海皇", employee.getName());
}
/** 全部署を取得できること */
@Test
void findAllDepartments() {
List<Department> list = departmentRepository.findAllByOrderByDeptIdAsc();
assertEquals(3, list.size());
}
}