【Java】Controllerとは?サンプルコードを交えて紹介!!

この記事は約37分で読めます。
Controllerアイキャッチ

JavaにはMVCモデルという考え方があります。

その中のCがControllerに当たります。

今回はControllerとは何かを、サンプルコードを交えてご紹介していきます。

詳細説明についても、サンプルコードにコメントに載せています。

また今回のサンプルについては、以下のGitHubにも載せています。
合わせてご覧ください。

GitHub - tkmttkm/ControllerSample: Controlerのサンプル
Controlerのサンプル. Contribute to tkmttkm/ControllerSample development by creating an account on GitHub.

Controllerとは?

Controllerとは、プログラムの処理の命令をするところです。
司令塔のようなものです。

主な流れとして、
Controlller → Service → Repository
というように繋がっていきます。

Controllerが指令を出し、その指令によってServiceが処理をし、
DBにまつわるところはRepositoryへお願いする。

そして処理結果をControllerへ返し、View(MVCのVの部分)へ渡して、
適切な結果を画面表示すると言った流れです。

このように処理に役割を持たせることで、可読性が増し、
修正もしやすくなっていきます。

では、実際にサンプルコードを見ていきましょう。

環境・定義

build.gradle

plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '3.3.0'
	id 'io.spring.dependency-management' version '1.1.5'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	//コンソールに取得データや発行したSQLを表示するために使用
	runtimeOnly 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	//JUnit
	testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
}

tasks.named('test') {
	useJUnitPlatform()
}

application.properties

spring.application.name=ControllerSample

#DBの接続方法やコンソールに発行したSQLや取得したデータを表示するための設定
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy

#MySQLの設定
spring.datasource.url=jdbc:log4jdbc:mysql://localhost/ENTITY_TEST
spring.datasource.username=root
spring.datasource.password=パスワード(独自で設定)

#springDataJPA設定
spring.jpa.hibernate.ddl-auto=validate

DBはMySQLを使用します。

CREATE_DATABASE_ENTITY_TEST.sql

CREATE DATABASE ENTITY_TEST;

CREATE_TABLE_ENTITY_TEST_TABLE.sql

CREATE TABLE ENTITY_TEST_TABLE 
(id INT NOT NULL
,full_name VARCHAR(255) NULL
,insert_date INT NULL
,PRIMARY KEY (id));

INSERT_DATA

INSERT INTO ENTITY_TEST_TABLE (id, full_name ,insert_date) VALUES (1, 'エンティティ ダンプティ', 20240418);

INSERT INTO ENTITY_TEST_TABLE (id, full_name ,insert_date) VALUES (2, 'エンティティ ティティ', 20240418);

INSERT INTO ENTITY_TEST_TABLE (id, full_name ,insert_date) VALUES (3, 'エンティティ えん', 20240418);

INSERT INTO ENTITY_TEST_TABLE (id, full_name ,insert_date) VALUES (4, 'エンティティ ダ ヴィンチ', 20240418);

ファイル階層

  • /
    • src/
      • main/
        • java/
          • com/example/demo/
            • controller/
              • Controller.java
            • entity/
              • Entity.java
            • form/
              • Form.java
            • repository/
              • Repository.java
            • service/
              • AbstractService.java
              • IService.java
              • extend/
                • Service.java
            • util/
              • Constants.java
            • ControllerSampleApplication.java
            • ServletInitializer.java
        • resources/
          • templates
            • search.html
          • static/
            • css/
              • search.css
          • application.properties
          • messages.properties
          • CREATE_DATABASE_ENTITY_TEST.sql
          • CREATE_TABLE_ENTITY_TEST_TABLE.sql
          • INSERT_DATA.sql
      • test/
        • java/
    • build.gradle
    • settings.gradle

サンプルコード

Search.html、Search.cssと表示画面

コード

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

<head>
	<meta charset="UTF-8">
	<title th:text="${title}"></title>
	<link rel="stylesheet" th:href="@{/css/search.css}"> 
</head>

<body>

	<div th:unless="${#strings.isEmpty(errorMessage)}">
		<div th:text="${errorMessage}" style="color:red"></div>
	</div>

	<div class="input-container">

		<form method="post" th:action="@{search}" th:object="${form}">
			<label for="full_name">ユーザー名</label>
			<input th:field="*{full_name}" type="text"></input>
			<button type="submit">検索</button>
		</form>

	</div>

	<div th:unless="${#lists.isEmpty(formList)}">
		<table>

			<thead>
				<tr>
					<th>ユーザー名</th>
					<th>挿入日</th>
				</tr>
			</thead>

			<tbody>
				<tr th:each="data : ${formList}">
					<td th:text="${data.fullName}"></td>
					<td th:text="${data.insert_date}"></td>
				</tr>
			</tbody>

		</table>
	</div>

</body>

</html>
@charset "UTF-8";


body {
	font-family: Arial, sans-serif;
	margin: 0;
	padding: 20px;
	background-color: #f4f4f9;
}

table {
	width: 100%;
	border-collapse: collapse;
	margin: 20px 0;
	font-size: 18px;
	text-align: left;
}

table th,
table td {
	padding: 12px 15px;
}

table thead tr {
	background-color: #009879;
	color: #ffffff;
	text-align: left;
	font-weight: bold;
}

table tbody tr {
	border-bottom: 1px solid #dddddd;
}

table tbody tr:nth-of-type(even) {
	background-color: #f3f3f3;
}

table tbody tr:last-of-type {
	border-bottom: 2px solid #009879;
}

table tbody tr:hover {
	background-color: #f1f1f1;
	cursor: pointer;
}

.input-container {
	display: flex;
	align-items: center;
	background-color: #ffffff;
	padding: 10px 20px;
	border-radius: 25px;
	box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.input-container form {
	width: 100%;
}

.input-container input {
	width: 50%;
	border:1px solid #dddddd;
	outline: none;
	padding: 10px;
	font-size: 16px;
	border-radius: 20px;
	margin-right: 10px;
	flex: 1;
}

.input-container button {
	background-color: #009879;
	color: white;
	border: none;
	padding: 10px 20px;
	font-size: 16px;
	border-radius: 20px;
	cursor: pointer;
	transition: background-color 0.3s ease;
}

.input-container button:hover {
	background-color: #007b5e;
}

画面

Serviceクラスでは、データの編集や追加などのメソッドがありますが、
今回は検索のみの画面を作成しています。

検索前
ControllerSample表示例1
検索後
ControllerSample表示例2

messages.properties

valid.pattern.id=idは数値のみ入力可能です
valid.pattern.fullName=検索する名前を入力してください
valid.pattern.insertData=insert_dateは数値のみ入力可能です
valid.empty.dataList=データがありません
valid.not.exist.data=idが{0}のデータは存在しません
valid.exist.data=idが{0}のデータは既に存在します

defaultErrorMessage=エラーが発生しました

メッセージ管理のファイルです。

Spring Bootでは、このファイル名でメッセージ管理しておけば、
MessageSourceというインターフェースを用いることで、メッセージを取り出すことができます。

MessageSourceについては、AbstractService.javaで使用している部分がございますので、
使用方法はそちらをご覧ください。

Controller.java

いきなりControllerのサンプルをご覧いただきます。
ここから遡る形でサンプルを見ていきましょう。

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
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 org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.example.demo.form.Form;
import com.example.demo.form.Form.Search;
import com.example.demo.service.extend.Service;

/**
 * <pre>
 * {@link org.springframework.stereotype.Controller Controller}アノテーションを付与することで、
 * DIコンテナにコントローラーとして登録される。
 * {@link RequestMapping}アノテーションを付与することで、
 * どのパスでこのコントローラーが動作するのかを指定する。
 * 今回は、パスの指定がないため、ローカル軌道で、http://localhost:8080/ でこのコントローラーが動作する
 * パスの指定をする場合は、{@code @RequestMapping("パス")}のような書き方をして指定する。
 * すると、http://localhost:8080/パス でこのコントローラーを通る
 * </pre>
 * @author Takumi
 *
 */
@org.springframework.stereotype.Controller
@RequestMapping
public class Controller {

	/** DIコンテナに登録してある{@link Service}クラスを取り出す */
	@Autowired
	private Service service;

	/**
	 * <pre>
	 * Getでリクエストが来た際にこのメソッドを通る。
	 * 今回は{@link GetMapping}にパスの指定がないため、
	 * 全体に付与されている{@link RequestMapping}のパスと合わせて、
	 * http://localhost:8080/ でgetリクエストが来た際にこのメソッドを通る。
	 * </pre>
	 * @param model viewからもらう、viewへ渡す情報のようなもの
	 * @param form modelの中でも、formというkey名がつけられているmodel
	 * @return 表示したいhtmlのファイルパス(拡張子抜きで相対パスで指定) 今回は」search.htmlを表示
	 */
	@GetMapping
	public String init(ModelMap model, @ModelAttribute(Service.FORM) Form form) {
		service.setCommonModel(model);
		return "search";
	}

	/**
	 * <pre>
	 * パス http://localhost:8080/search でpostリクエストを受けると
	 * このメソッドを通る
	 * </pre>
	 * @param form {@link Validated}の引数にインターフェースを記載することでどのフィールドをバリデーションするのかを指定する
	 * @param error formのエラーをこのerrorで受けとる
	 * @param redirect メソッドへパスを指定して投げなおす際の情報を格納する
	 * @return redirect;を指定することで、そのあとのパスの指定があるコントローラーへメソッドを投げることができる。
	 * 今回は何も指定がないため、http;//localhost:8080/でリクエストを受けた際に通るメソッドへ投げる。
	 * このControllerの場合は{@link #init(ModelMap, Form)}へ繋げる
	 */
	@PostMapping("/search")
	public String search(@Validated(Search.class) Form form, BindingResult error, RedirectAttributes redirect) {
		if (!service.existError(error, redirect)) {
			service.search(form, redirect);
		}

		return "redirect:";
	}

}

Service.java

package com.example.demo.service.extend;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.thymeleaf.util.ListUtils;

import com.example.demo.entity.Entity;
import com.example.demo.form.Form;
import com.example.demo.repository.Repository;
import com.example.demo.service.AbstractService;
import com.example.demo.util.Constants;

@org.springframework.stereotype.Service
public class Service extends AbstractService<Entity> {

	/** 検索の際のinputタグを格納 */
	public static final String FORM = "form";
	/** 検索結果を格納 */
	public static final String FORM_LIST = "formList";

	@Autowired
	private Repository repository;
	/** 処理対象のデータを格納 */
	private List<Entity> entityList;

	@Override
	public void findAll() {
		List<Entity> list = repository.findAll();
		setDataList(list);
	}

	@Override
	public Entity findById(int userId) {
		Optional<Entity> dataOpt = repository.findById(Integer.valueOf(userId));
		//データがない場合はnullを返す
		return dataOpt.isPresent() ? dataOpt.get() : null;
	}

	@Override
	public void findByUserName(String userName) {
		List<Entity> list = repository.findByFullName(userName);
		setDataList(list);
	}

	@Override
	public void addUser(Form form) {
		//データの潜在チェックをする。存在したらエラーメッセージ出す。
		if (isExist(form)) {
			System.err.println(getMessage(Constants.VALID_EXIST_DATA, new String[] {form.getId()}));
		} else {
			save(form);
			setAddCount(getAddCount() + 1);
		}
	}

	@Override
	public void addUser(List<Form> formList) {
		List<Form> existList = existList(formList);
		//データの潜在チェックをする。存在したらエラーメッセージ出す。
		if (existList.size() == 0) {
			List<Entity> savedList = saveAll(formList);
			setAddCount(getAddCount() + savedList.size());
		} else {
			isExistDatasErrorLog(existList, true);
		}
	}

	@Override
	public void deleteUser(int userId) {
		//データの潜在チェックをする。存在しなかったらエラーメッセージ出す。
		if (isExist(userId)) {
			Integer id = Integer.valueOf(userId);
			repository.deleteById(id);
			setDeleteCount(getDeleteCount() + 1);
		} else {
			System.err.println(getMessage(Constants.VALID_NOT_EXIST_DATA, new Integer[] {userId}));
		}
	}

	@Override
	public void editUser(Form form) {
		//データの潜在チェックをする。存在しなかったらエラーメッセージ出す。
		if (isExist(form)) {
			save(form);
			setEditCount(getEditCount() + 1);
		} else {
			System.err.println(getMessage(Constants.VALID_EXIST_DATA, new String[] {form.getId()}));
		}
	}

	@Override
	public void editUser(List<Form> formList) {
		//データの潜在チェックをする。存在しなかったらエラーメッセージ出す。
		if (existList(formList).size() == 0) {
			isExistDatasErrorLog(formList, false);
		} else {
			List<Entity> savedList = saveAll(formList);
			setEditCount(getEditCount() + savedList.size());
		}
	}

	/**
	 * 共通の{@link ModelMap}をセット
	 * @param model
	 */
	public void setCommonModel(ModelMap model) {
		model.put(Constants.TITLE_KEY, Constants.SEARCH_TITLE);
	}

	/**
	 * エラーの存在をチェックする
	 * @param error
	 * @param redirect
	 * @return エラーがあればtrue
	 */
	public boolean existError(BindingResult error, RedirectAttributes redirect) {
		if (error.hasFieldErrors()) {
			StringBuilder errorMessage = new StringBuilder();
			for (var errors : error.getAllErrors()) {
				errorMessage.append(errors.getDefaultMessage());
			}
			redirect.addFlashAttribute(Constants.ERROR_MESSAGE, errorMessage.toString());
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 検索ボタン押下後のメソッド
	 * @param form 検索の際のinputタグ内の値を格納
	 * @param redirect
	 */
	public void search(Form form, RedirectAttributes redirect) {
		findByUserName(form.getFull_name());
		if (ListUtils.isEmpty(dataList)) {
			redirect.addFlashAttribute(Constants.ERROR_MESSAGE, getMessage(Constants.VALID_EMPTY_DATALIST, null));
		} else {
			redirect.addFlashAttribute(FORM_LIST, getDataList());
		}
		redirect.addFlashAttribute(FORM, form);
	}

	/**
	 * 存在する複数データのログを出力
	 * @param existList 存在する複数データ
	 * @param isExist 存在するログを出力する場合はtrue
	 */
	private void isExistDatasErrorLog(List<Form> existList, boolean isExist) {
		String[] existIdArray = new String[existList.size()];
		for (int i = 0; i < existList.size(); i++) {
			existIdArray[i] = existList.get(i).getId();
		}

		String errorMessage = String.join(", ", existIdArray);
		if (isExist) {
			System.err.println(getMessage(Constants.VALID_EXIST_DATA, new String[] {errorMessage}));
		} else {
			System.err.println(getMessage(Constants.VALID_NOT_EXIST_DATA, new String[] {errorMessage}));
		}
	}

	/**
	 * データの存在確認をする
	 * @param form 存在確認をするデータ
	 * @return 存在すればtrue
	 */
	private boolean isExist(Form form) {
		Integer ID = Integer.parseInt(form.getId());
		int id = ID.intValue();
		return findById(id) != null;
	}

	/**
	 * データの存在確認をする
	 * @param userId 存在確認をするデータのid
	 * @return 存在すればtrue
	 */
	private boolean isExist(int userId) {
		return findById(userId) != null;
	}

	/**
	 * 存在するデータのみを返す
	 * @param form 存在確認するデータの{@code List<Form}
	 * @return 存在したデータの{@code List<Form}
	 */
	private List<Form> existList(List<Form> form) {
		List<Form> existList = new ArrayList<>();

		for (Form data : form) {
			Integer ID = Integer.parseInt(data.getId());
			int id = ID.intValue();
			Entity d = findById(id);
			if (d != null) {
				existList.add(data);
			}
		}

		return existList;
	}

	/**
	 * {@link Form}クラスを{@link Entity}クラスへ変換する
	 * @param form
	 * @return 変換後の{@link Entity}クラス
	 */
	private Entity setEntity(Form form) {
		Entity entity = new Entity();

		entity.setId(Integer.parseInt(form.getId()));
		entity.setFullName(form.getFull_name());
		entity.setInsert_date(Integer.parseInt(form.getInsert_date()));

		return entity;
	}

	/**
	 * INSERTもしくはUPDATEを実行
	 * @param form 更新、挿入したいデータ
	 * @return 更新、挿入したデータの{@link Entity}クラス
	 */
	private Entity save(Form form) {
		Entity entity = setEntity(form);
		return repository.save(entity);
	}

	/**
	 * 複数のINSERTもしくはUPDATEを実施
	 * @param formList 更新、挿入したいデータ
	 * @return 更新、挿入したいデータ
	 */
	private List<Entity> saveAll(List<Form> formList) {
		setEntityListByFromList(formList);
		return repository.saveAll(entityList);
	}

	/**
	 * {@link this#entityList}を{@link Form}クラスからセットする
	 * @param formList
	 */
	private void setEntityListByFromList(List<Form> formList) {
		List<Entity> entityList = new ArrayList<>();

		for (Form form : formList) {
			entityList.add(setEntity(form));
		}

		setEntityList(entityList);
	}

	public List<Entity> getEntityList() {
		return entityList;
	}

	public void setEntityList(List<Entity> entityList) {
		this.entityList = entityList;
	}

}

AbstractService.java

package com.example.demo.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;

import com.example.demo.util.Constants;

import lombok.Data;

/**
 * <pre>
 * サービスクラスの抽象クラス
 * このクラスを継承してサービスクラスを作成
 * </pre>
 * @author Takumi
 *
 * @param <T> 作成するサービスクラスで使用するEntityクラスを渡す
 */
@Data
public abstract class AbstractService<T> implements IService {

	/** 取得したデータ */
	protected List<T> dataList = new ArrayList<T>();

	/** 取得したデータ数 */
	protected int dataListSize;

	/** 追加したデータ数 */
	protected int addCount;

	/** 削除したデータ数 */
	protected int deleteCount;

	/** 編集したデータ数 */
	protected int editCount;

	/** messages.propertiesの文言取得 */
	@Autowired
	protected MessageSource messageSource;

	/**
	 * messages.propertiesからメッセージ取得
	 * @param messageKey messages.propertiesのkey
	 * @param args 文言に与える引数
	 * @return メッセージ
	 */
	public String getMessage(String messageKey, Object[] args) {
		//標準メッセージを設定
		String defaultMessage = messageSource.getMessage(Constants.DEAULT_ERROR_MESSAGE, null, Locale.JAPANESE);
		//第一引数にmessages.propertiesのkeyを、第二引数にそのメッセージに代入したい値を、第三引数に標準のメッセージをセット
		return messageSource.getMessage(messageKey, args, defaultMessage, Locale.JAPANESE);
	}

	public int getDataListSize() {
		return dataList.size();
	}
}

IService.java

package com.example.demo.service;

import java.util.List;

import com.example.demo.entity.Entity;
import com.example.demo.form.Form;

/**
 * サービスクラスのインターフェース
 * @author Takumi
 */
public interface IService {

	/**
	 * データをすべて取得
	 */
	void findAll();
	
	/**
	 * 対象のIdのデータを取得 
	 */
	Entity findById(int userId);

	/**
	 * ユーザー名からデータを取得
	 * @param userName ユーザー名
	 */
	void findByUserName(String userName);

	/**
	 * ユーザーを1件追加(INSERT)
	 * @param form
	 */
	void addUser(Form form);

	/**
	 * ユーザーを追加
	 * @param formList 追加するユーザー
	 */
	void addUser(List<Form> formList);

	/**
	 * ユーザーを削除
	 * @param userId 削除するユーザーのID
	 */
	void deleteUser(int userId);

	/**
	 * データを1件編集(UPDATE)
	 * @param form 更新するユーザーのデータ
	 */
	void editUser(Form form);

	/**
	 * データを編集(UPDATE)
	 * @param formList 編集するデータ
	 */
	void editUser(List<Form> formList);
}

Serviceについての詳細は、以下の記事をご覧ください。

Repository.java

package com.example.demo.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.entity.Entity;

/**
 * Spring Data JPA使用
 * @author Takumi
 */
public interface Repository extends JpaRepository<Entity, Integer>{
	
	public List<Entity> findByFullName(String fullName);

}

こちらSpring Data JPAの詳細については、以下の記事をご覧ください。

Entity.java

package com.example.demo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@jakarta.persistence.Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ENTITY_TEST_TABLE")
public class Entity {

	@Id
	private Integer id;
	@Column(name = "full_name")
	private String fullName;
	private Integer insert_date;
}

Entityについての詳細は、以下の記事をご覧ください。

Form.java

package com.example.demo.form;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.springframework.validation.annotation.Validated;

import io.micrometer.common.util.StringUtils;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;

/**
 * <pre>
 * 画面より入力された値を格納するクラス
 * その際に、NULL許容などのバリデーションもかける
 * Controllerクラすに{@link Validated}アノテーションを入れ、引数にインターフェースを入れることで、
 * どのフィールドのバリデーションをするのかを指定する
 * </pre>
 */
@Data
public class Form {

	/** すべてのフィールドのバリデーションを行う */
	public interface All{}
	/** 検索の際のバリデーションを行う */
	public interface Search{}

	/** \\d によって数値以外の入力を制限している。 */
	@NotEmpty
	@Pattern(regexp = "\\d", message = "{valid.pattern.id}", groups = All.class)
	private String id;
	@NotEmpty(message = "{valid.pattern.fullName}", groups = {Search.class, All.class})
	private String full_name;
	@Pattern(regexp = "\\d", message = "{valid.pattern.insertData}", groups = {Search.class, All.class} )
	private String insert_date;


	/**
	 * 値がセットされていない場合は、
	 * 現在日時をyyyyMMdd形式で取得
	 * @return yyyyMMdd
	 */
	public String getInsert_date() {
		if(!StringUtils.isBlank(this.insert_date)) {
			return this.insert_date;
		}

		LocalDateTime nowDate = LocalDateTime.now();
		DateTimeFormatter yyyyMMdd_formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
		String yyyyMMdd = yyyyMMdd_formatter.format(nowDate);
		return yyyyMMdd;
	}
}

DTO(Data Transfer Object)に該当するクラスです。

このクラスでバリデーションしてある、
検索の際に出るエラー画面を実際に表示すると、

未入力エラー
データなしエラー

上記のようになります。

Constants.java

package com.example.demo.util;

/**
 * 使用するkey情報などを格納
 */
public final class Constants {

	public static final String TITLE_KEY =  "title";
	public static final String ERROR_MESSAGE = "errorMessage";
	public static final String DEAULT_ERROR_MESSAGE = "defaultErrorMessage";
	public static final String VALID_EMPTY_DATALIST = "valid.empty.dataList";
	public static final String VALID_EXIST_DATA = "valid.exist.data";
	public static final String VALID_NOT_EXIST_DATA = "valid.not.exist.data";

	public static final String SEARCH_TITLE = "検索";
}