Springboot/Mybatis/RESTAPI/axios 編集中
ーーー
Intellij IDEA使用 設定→ラグイン→Mybatisxを有効に。
Springboot
java JDK17
Springboot ビルドツール:Mavenではなくbuild.gradle使用
Mybatisとは、データベースにアクセスするフレームワーク
MySQL
vscode
※バックエンドとクライアントのhostが分かれている前提で進めます。
ディレクトリ構成
src/
└── main/
├── java/
│ └── pairWork/
│ ├── api/
│ │ ├── entity/
│ │ ├── repository/
│ │ ├── service/
│ │ └── controller/
│ └── ...
└── resources/
└── application.properties
└── mapper/
└── TaskMapper.xml
テーブル作成
CREATE TABLE task (
id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
task_name VARCHAR(255) NOT NULL COMMENT 'タスク名',
task_detail TEXT COMMENT 'タスク詳細',
status INT NOT NULL COMMENT 'ステータス',
expiration_date DATE COMMENT '期限日',
registration_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登録日',
updated_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日',
man_hour INT COMMENT '工数'
);
テーブルにテストデータとして値を格納
INSERT INTO task (task_name, task_detail, status, expiration_date, registration_date, updated_date, man_hour)
VALUES
('タスク1', 'デザインを完成させる', 1, '2024-11-25', '2024-11-18 09:00:00', '2024-11-18 09:00:00', 8),
('タスク2', 'クライアントミーティング準備', 2, '2024-11-27', '2024-11-18 10:00:00', '2024-11-18 10:00:00', 4),
('タスク3', 'プロジェクト仕様書の確認', 1, '2024-11-30', '2024-11-18 11:00:00', '2024-11-18 11:00:00', 12),
('タスク4', 'バックエンドAPIの設計', 3, '2024-12-05', '2024-11-18 12:00:00', '2024-11-18 12:00:00', 15),
('タスク5', 'ユニットテストケース作成', 2, '2024-11-28', '2024-11-18 13:00:00', '2024-11-18 13:00:00', 6),
('タスク6', 'コードレビューの実施', 1, '2024-11-29', '2024-11-18 14:00:00', '2024-11-18 14:00:00', 3),
('タスク7', '開発サーバーのセットアップ', 3, '2024-12-01', '2024-11-18 15:00:00', '2024-11-18 15:00:00', 10),
('タスク8', 'UIモックアップの作成', 2, '2024-11-26', '2024-11-18 16:00:00', '2024-11-18 16:00:00', 5),
('タスク9', 'ログ収集システムの導入', 1, '2024-11-30', '2024-11-18 17:00:00', '2024-11-18 17:00:00', 8),
('タスク10', '月次レポート作成', 3, '2024-11-24', '2024-11-18 18:00:00', '2024-11-18 18:00:00', 7);
Taskmapper.xml
<?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.xmlファイルの宣言とDTDの指定。
MyBatisで使用するためのファイルで、DTDによってXMLの構造を定義します。 -->
<!--<mapper namespace="com.example.mapper.TaskMapper">-->
<mapper namespace="pairWork.api.repository.mapper.task.TaskMapper">
<!-- <mapper>タグは、SQL文とJavaのMapperインターフェースを関連付けるルート要素。
namespace属性に、対応するJavaのMapperインターフェースの完全修飾名を指定します。 -->
<!-- タスクの条件検索 -->
<!-- タスクをステータスや期限日で検索するSQLクエリを記述する -->
<select id="findAll" resultType="pairWork.api.entity.Task">
SELECT
id,
task_name,
task_detail,
status,
expiration_date,
registration_date,
updated_date,
man_hour
FROM task
</select>
entity
package pairWork.api.entity;
import java.util.Date;//データ型
import lombok.Data;//Lombokの@Dataアノテーションをクラスに付けることで、以下のような基本的なコードを自動生成してくれます
@Data
public class Task {
private int id; // タスクID
private String taskName; // タスク名
private String taskDetail; // タスク詳細
private int status; // ステータス
private Date expirationDate; // 期限日
private Date registrationDate; // 登録日
private Date updatedDate; // 更新日
private int manHour; // 工数
}
repository mapper(インターフェイス)
細かく条件IDなどの取得ではなく簡易なfindAll();でデータをとりあえず全て取得できるように全検索
package pairWork.api.repository;
import java.util.List;
import pairWork.api.entity.Task;
import pairWork.api.entity.Todo;
import pairWork.api.service.output.task.TaskSearchServiceInput;
public interface TaskRepository {
List<Task> findAll();
void register(Task entity);
}
repositoryクラスをマッピングする理由は後ほど追記
package pairWork.api.repository.mapper.task;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
//import org.apache.ibatis.annotations.Param;
import pairWork.api.entity.Task;
@Mapper
public interface TaskMapper {
List<Task> findAll();
TaskDatasource
TaskDatasource が、TaskRepository インターフェースを実装している部分
package pairWork.api.repository.datasource;
import pairWork.api.entity.Task;
import pairWork.api.repository.TaskRepository;
import pairWork.api.repository.mapper.task.TaskMapper;
import java.util.List;
public class TaskDatasource implements TaskRepository {
private static TaskMapper taskMapper;
@Override
public List<Task> findAll(){
return taskMapper.findAll();
}
@Override
public void register(Task entity) {
}
}
・DTO
【入口】
【役割】TaskSearchRequestDtoは「タスク検索条件」をクライアントから受け取るための
DTO(データ転送オブジェクト) REST API がクエリパラメータで送信されたデータをこのクラスにマッピングします。
[クライアントリクエスト]
|
v
[クエリパラメータ] --> [TaskSearchRequestDto]
【service配下】
【役割】TaskDtoServiceOutput は、データベースから取ってきた
「タスク情報」を、他のプログラムで使いやすい形に整理するための「変換作業」をしています。
[データベース]
|
v
[生データ] --> [変換処理] --> [TaskDtoServiceOutput]
【出口】
【役割】TaskSearchResponseDto クラスは、
タスク検索APIのレスポンス(返り値)を表現するためDTO(データ転送オブジェクト)
[検索結果]
|
v
[整形処理] --> [TaskSearchResponseDto] --> [クライアントレスポンス]
【役割】TaskSearchResponseItemDtoは、タスク検索APIのレスポンスとして返却される
個々のタスク情報を表現するためのデータ転送オブジェクト(DTO)です
+--------------------------------------------------+
| TaskSearchResponseItemDto |
+--------------------------------------------------+
| - taskId: String | // タスクの一意識別子
| - title: String | // タスクのタイトル
| - description: String | // タスクの詳細説明
| - status: String | // タスクの状態(例: 未着手、進行中、完了)
| - createdAt: DateTime | // タスクの作成日時
| - updatedAt: DateTime | // タスクの最終更新日時
| - assignedTo: String | // タスクの担当者
| - dueDate: DateTime | // タスクの期限
+--------------------------------------------------+
serviceフォルダのoutputフォルダ配下にtaskフォルダ「TaskDtoServiceOutput」ファイル
【役割】TaskDtoServiceOutput は、データベースから取ってきた「タスク情報」を、他のプログラムで使いやすい形に整理するための「変換作業」をしています。
//DTOとは、データをそのまま使うんじゃなくて、サービス層で必要な情報だけを抜き出して、DTO(データ転送オブジェクト)っていう形式に変換して返してる
//目的:サービス層で使いやすい形に変換して出力用の情報を取り出す
/*
*「SQLを実行するクラス」は DAO (Data Access Object)
* 「テーブルのデータの情報を覚えておくためのクラス」は DTO (Data Transfer Object)
*/
import lombok.Builder;
import lombok.Data;
import pairWork.api.entity.Task;
import pairWork.api.repository.datasource.TaskDatasource;
@Data//@Data やけど、これはgetter、setter、toString、equals、hashCodeを自動で作ってくれるアノテーション
@Builder//@Builder は、ビルダーパターンを使ってオブジェクトを作るためのアノテーション
//Task エンティティから必要なフィールドを選んでDTOを作成
public class TaskDtoServiceOutput {
private int id;
private String taskName;
private String taskDetail;
// TaskエンティティからDTOに変換するメソッド
public static TaskDtoServiceOutput fromEntity(Task task){
return TaskDtoServiceOutput.builder()
.id(task.getId()) // TaskからIDを取り出してセット
.taskName(task.getTaskName()) // Taskからタスク名を取り出してセット
.taskDetail(task.getTaskDetail())// Taskからタスク詳細を取り出してセット
.build();//最後の .build() でそのオブジェクトを実際に作成して返している
}
}
//entityToServiceOutput fromEntity
②controllerフォルダのInputフォルダ配下にtaskフォルダ「TaskSearchRequestDto」ファイル
【役割】TaskSearchRequestDtoは「タスク検索条件」をクライアントから受け取るための DTO(データ転送オブジェクト) REST API がクエリパラメータで送信されたデータをこのクラスにマッピングします。
package pairWork.api.controller.input.task;
// Lombok を利用してコードを簡潔にするためのアノテーションをインポート
import lombok.AllArgsConstructor; // すべてのフィールドを引数に持つコンストラクタを自動生成
import lombok.Data; // getter/setter、toString、equals、hashCodeを自動生成
import lombok.NoArgsConstructor; // 引数なしのデフォルトコンストラクタを自動生成
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TaskSearchRequestDto {
private Integer id;
private String taskName;
private Integer status;
private String expirationFrom;
private String expirationTo;
}
③,④controllerフォルダのoutフォルダ配下にtaskフォルダ「TaskSearchResponseDto」「TaskSearchResponseItemDto」ファイル
【役割】TaskSearchResponseDto クラスは、タスク検索APIのレスポンス(返り値)を表現するための**DTO(データ転送オブジェクト)
package pairWork.api.controller.out.task;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.Data;
import pairWork.api.entity.Task;
@Builder
@Data
public class TaskSearchResponseDto {
private List<TaskSearchResponseItemDto> taskList;
public static TaskSearchResponseDto of(List<Task> taskList) {
return TaskSearchResponseDto.builder()
.taskList(taskList.stream()
// Task→TaskSearchResponseItemDtoに変換
.map(TaskSearchResponseItemDto::of)
// StreamになっているのをListに変換
.collect(Collectors.toList()))
.build();
}
}
【役割】TaskSearchResponseItemDtoは、
package pairWork.api.controller.out.task;
import java.time.format.DateTimeFormatter;
import lombok.Builder;
import lombok.Data;
import pairWork.api.entity.Task;
@Builder
@Data
public class TaskSearchResponseItemDto {
private int id; // タスクID
private String taskName; // タスク名
private int status; // ステータス
private String expirationDate; // 期限日
public static TaskSearchResponseItemDto of(Task task) {
return TaskSearchResponseItemDto.builder()
.id(task.getId())
.taskName(task.getTaskName())
.status(task.getStatus())
.expirationDate(task.getExpirationDate().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")))
.build();
}
}
DTO
//serviceの実装
service(サービス層ビジネスロジック)
//serviceの実装
controller (ApiResult)
package pairWork.api.controller;
import java.util.List;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import pairWork.api.controller.input.task.TaskSearchRequestDto;
import pairWork.api.controller.out.ApiResult;
import pairWork.api.controller.out.task.TaskSearchResponseDto;
import pairWork.api.entity.Task;
import pairWork.api.service.TaskService;
@AllArgsConstructor
@RestController
public class TaskController {
private final TaskService taskService;
//restAPI-reactで呼び出す
@GetMapping("/")
public String index(){
return "Hello HTML";
}
@RequestMapping(value = "/task/search", method = RequestMethod.GET)
public ApiResult<TaskSearchResponseDto> search(TaskSearchRequestDto request) {
List<Task> result = taskService.search(request);
System.out.println(request);
return ApiResult.of(TaskSearchResponseDto.of(result));
}
}
webクライアント(axios)
Discussion