はじめに
本記事ではREST APIをデータベース(MySQL)と連携させ、基本的なCRUD操作を通じて、その動作の確認と例外処理についても説明していきます。
前回までの記事で、①使用する環境・ツールの基本構築から簡単な動作確認、及び②基本的なCRUD操作ができる簡単なREST APIを構築する方法について解説していますので、よければそちらもご覧ください。
Spring Bootを使ったREST APIの基本構築から動作確認まで①
Spring Bootを使ったREST APIの基本構築から動作確認まで②
対象者
- Javaの基礎知識を持っている方
- Spring Bootを初めて触る方、もしくは少し触ったことがある方
- REST APIについて学びたい方
動作環境
- Eclipse 2022(Pleiades All in One)
- Spring Boot 3.4.4
- Advanced REST Client
- MySQL 8.0.42
事前準備
本記事で使用する環境・ツールについて、インストール手順を記載していきます。
Eclipse(Pleiades All in One)、Spring Boot(Spring Initializr)、Advanced REST Clientについては前回までの記事で紹介していますので、そちらをご確認ください。
Spring Bootを使ったREST APIの基本構築から動作確認まで①-事前準備
Spring Bootを使ったREST APIの基本構築から動作確認まで②-事前準備
また、MySQLについてはこちらの記事をご参考ください。
MySQLのインストール手順
データベース作成
MySQL Workbenchを使って本記事で使用するデータベースを作成します。
手順
- Queryの入力欄に以下を入力して実行
問題なければ、画面下部に緑のチェックが表示されます。(以降も同様)
create database TaskDB;
- Queryの入力欄に以下を入力して実行
USE TaskDB;
- Navigator欄のSchemasタブでリフレッシュボタンを押下することで、作成したtaskdbが表示されます
実装
EclipseでSpringのプロジェクトを作成し、実装していきましょう。
Spring Initializrを使用したプロジェクト作成については前々回の記事で紹介していますので、そちらをご確認ください。
Spring Bootを使ったREST APIの基本構築から動作確認まで①-Spring Initializr
今回はプロジェクト名を"restapidemo3"として作成します。
依存性はSpring Webに加えて、MySQLに接続するため、MySQL Driver及びSpring Data JPAの計3つを選択してください。
MySQL Driver
その名の通り、Spring BootアプリケーションがMySQLと通信するために必要なソフトウェアです。
Spring Data JPA
データベースとのやり取りを簡単にするためのSpring Frameworkの一部です。SQLを書くことなく、シンプルなリポジトリインターフェースを使用してCRUD操作を行うことが可能となります。
model
まずはmodelの実装です。
com.example.restapidemo3パッケージの配下にmodelパッケージを作成し、その中にTaskクラスを作成します。
前回記事で作成したTaskクラスを流用しますが、データベースに接続するため一部修正を加えています。
Spring Bootを使ったREST APIの基本構築から動作確認まで②-model
Taskクラスでは新たに3つのアノテーションを使用します。
@Entity
JPAエンティティを表すクラスに付与するアノテーションで、クラスがデータベースのテーブルにマッピングされます。
今回は名前にテーブル名:m_taskを指定しています。
@Id
エンティティの主キーとなるフィールドに付与するアノテーションです。
主キーの生成方法は通常、後述するGeneratedValueアノテーションで指定します。
@GeneratedValue
Idアノテーションと一緒に使用し、主キーが自動生成されることを指定します。
生成戦略をstrategy属性で指定しますが、今回はデータベースに依存する方法で主キーが生成される、GenerationType.IDENTITYを指定します。
また、主キーは自動採番されるため、以下2点も修正しておきます。
- idの型をStringからLongに変更
- 主キーの自動採番に伴い、コンストラクタでidを設定する必要がなくなったため、コンストラクタから削除
package com.example.restapidemo3.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity(name="m_task")
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String status;
public Task() {
}
public Task(String title, String status) {
// this.id = id;
this.title = title;
this.status = status;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
repository
次にrepositoryです。
com.example.restapidemo3パッケージの配下にrepoパッケージを作成し、その中にTaskRepositoryインターフェースを作成します。
※クラスではなく、インターフェースです。
中身としては、CrudRepositoryを継承して、@Repositoryを付与するだけです。
CrudRepositoryはSpring Dataが提供するリポジトリインターフェースで、CRUD操作を簡単に実現できるようになります。
データベースにアクセスするには、このTaskRepositoryインターフェースを定義するのみでOKです。
こちらを実装するクラスはフレームワークが裏で自動的に生成してくれます。
@Repository
データアクセス層(DAO)であることを示すために使用されるアノテーションです。
package com.example.restapidemo3.repo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.restapidemo3.model.Task;
@Repository
public interface TaskRepository extends CrudRepository<Task, Long> {
}
service
続いて、serviceの実装です。
同じく、com.example.restapidemo3パッケージの配下にserviceパッケージを作成し、その中にTaskServiceクラスを作成します。
前回記事で作成したTaskServiceクラスを流用しますが、こちらもデータベースに接続するため一部修正を加えます。
Spring Bootを使ったREST APIの基本構築から動作確認まで②-service
主な修正箇所は以下の3点です。
- データベースからデータ取得を行うため、タスク一覧のリストは削除
- TaskRepositoryクラスを使用するためにフィールドとして定義し、@Autowiredを付与して明示的にnewしなくとも使用できるように修正
- 各メソッドについて、taskRepositoryのfindAll()やfindById()などのメソッドを使用して、DBを操作して処理を行うように修正
package com.example.restapidemo3.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.restapidemo3.model.Task;
import com.example.restapidemo3.repo.TaskRepository;
@Service
public class TaskService {
@Autowired
private TaskRepository taskRepository;
// private List<Task> allTasks = new ArrayList<> (Arrays.asList(
// new Task("001", "掃除", "TODO"),
// new Task("002", "洗濯", "DONE"),
// new Task("003", "勉強", "IN_PROGRESS"),
// new Task("004", "料理", "TODO"),
// new Task("005", "買い物", "DONE")));
public List<Task> getAllTasks(){
List<Task> allTasks = new ArrayList<>();
taskRepository.findAll().forEach(allTasks::add);
return allTasks;
}
public Optional<Task> getTask(Long id) {
return taskRepository.findById(id);
}
public void addTask(Task task) {
taskRepository.save(task);
}
public void updateTask(Long id, Task task) {
if(taskRepository.findById(id).get() != null) {
taskRepository.save(task);
}
}
public void deleteTask(Long id) {
taskRepository.deleteById(id);
}
}
exception
次はexceptionの実装です。
com.example.restapidemo3パッケージの配下にexceptionパッケージを作成し、その中にTaskNotFoundExceptionクラスを作成します。
作成する際、スーパークラスはRuntimeExceptionを指定してください。
クラスの中身としては、シリアライズされたデータとの互換性を保つためにserialVersionUIDを定義しておくのと、例外がスローされた際に指定されたIDを含むエラーメッセージを設定するため、コンストラクタを定義しておきます。
package com.example.restapidemo3.exception;
public class TaskNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public TaskNotFoundException(Long id) {
super("タスクコード" + id + "は見つかりません。");
}
}
exceptionパッケージの配下にもう一つ、例外をハンドルするためのクラスとしてTaskNotFoundExceptionControllerAdviceクラスを作成します。
ここでは、例外をハンドリングするためのメソッドを定義しておくのと、新たに4つのアノテーションを使用します。
@ControllerAdvice
アプリケーションの複数のコントローラーで共通するロジックをまとめるために付与するアノテーションで、主に例外処理を簡単に実現するために使用します。
例外処理においては、通常、後述するExceptionHandlerアノテーションと組み合わせます。
@ResponseBody
HTTPレスポンスのボディとして直接返すために使用されるアノテーションで、メソッドの戻り値をJSONやXML形式にシリアライズして返却します。
@ExceptionHandler
例外をハンドリングするためのアノテーションで、括弧内に指定した例外が発生した際にその例外をキャッチして、適切なレスポンスをクライアントに返してくれます。
@ResponseStatus
レスポンスのHTTPステータスコードを設定するためのアノテーションで、特に例外クラスに適用することで、その例外がスローされた際に特定のステータスコードを返すことができます。
ここではNOT_FOUNDを指定して、「404 Not Found」を返却するようにしています。
package com.example.restapidemo3.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class TaskNotFoundExceptionControllerAdvice {
@ResponseBody
@ExceptionHandler(TaskNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String taskNotFoundHandler(TaskNotFoundException ex) {
return ex.getMessage();
}
}
controller
最後に、controllerの実装です。
com.example.restapidemo3パッケージの配下にcontrollerパッケージを作成し、その中にTaskControllerクラスを作成します。
こちらも、前回記事で作成したTaskControllerクラスを流用しますが、一部修正を加えます。
Spring Bootを使ったREST APIの基本構築から動作確認まで②-controller
修正箇所は以下の2点です。
- Taskクラスでidの型を変更したため、StringからLongに修正
- taskServiceのgetTaskメソッドがOptional型を返すように修正したため、.orElseThrowを追加して、もしnullが返ってきた場合にカスタム例外をスローするように修正
package com.example.restapidemo3.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.example.restapidemo3.exception.TaskNotFoundException;
import com.example.restapidemo3.model.Task;
import com.example.restapidemo3.service.TaskService;
@RestController
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("/tasks")
public List<Task> getAllTasks() {
return taskService.getAllTasks();
}
@GetMapping("/tasks/{id}")
public Task getTask(@PathVariable("id") Long id) {
return taskService.getTask(id).orElseThrow(() -> new TaskNotFoundException(id));
}
@PostMapping("/tasks")
public void addTask(@RequestBody Task task) {
taskService.addTask(task);
}
@PutMapping("/tasks/{id}")
public void updateTask(@RequestBody Task task, @PathVariable("id") Long id) {
taskService.updateTask(id, task);
}
@DeleteMapping("/tasks/{id}")
public void deleteTask(@PathVariable("id") Long id) {
taskService.deleteTask(id);
}
}
以上で処理の実装は完了となりますが、演習用データの投入とデータベースの接続情報設定のため、以下にコードを追加します。
Restapidemo3Application
アプリケーションの起動時にMySQLのtaskdb上に初期値が登録されるようにしておきます。
package com.example.restapidemo3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.example.restapidemo3.model.Task;
import com.example.restapidemo3.repo.TaskRepository;
@SpringBootApplication
public class Restapidemo3Application implements CommandLineRunner{
@Autowired
private TaskRepository taskRepository;
public static void main(String[] args) {
SpringApplication.run(Restapidemo3Application.class, args);
}
@Override
public void run(String... args) throws Exception {
taskRepository.save(new Task("掃除", "TODO"));
taskRepository.save(new Task("洗濯", "DONE"));
taskRepository.save(new Task("勉強", "IN_PROGRESS"));
taskRepository.save(new Task("料理", "TODO"));
taskRepository.save(new Task("買い物", "DONE"));
}
}
application.properties
データベースの接続情報を設定します。
passwordは各自で設定したものを入力してください。
その他について、一部解説しておきます。
spring.jpa.hibernate.ddl-auto
アプリケーションの起動時にデータベースのスキーマをどのように操作するかを制御します。
createを指定した場合、既存のスキーマが無ければ作成し、もしあるようであれば削除して再作成します。
spring.jpa.show-sql
SQLクエリをコンソールに表示するための設定で、trueにした場合、実行されるクエリをコンソール上で確認することができます。
spring.jpa.properties.hibernate.format_sql
出力されるSQLクエリを整形された形で表示するための設定で、trueにした場合、クエリが見やすい形式で表示されます。
spring.application.name=restapidemo3
spring.datasource.url=jdbc:mysql://localhost:3306/TaskDB
spring.datasource.username=root
spring.datasource.password=各自で設定したパスワード
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
動作確認
ここまで出来たら、アプリケーションを起動してみましょう。
起動すると、コンソールに以下のように表示されているかと思います。
※テーブルの初期化とデータ挿入
Hibernate:
drop table if exists m_task
Hibernate:
create table m_task (
id bigint not null auto_increment,
status varchar(255),
title varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
insert
into
m_task
(status, title)
values
(?, ?)
MySQL Workbench
MySQL Workbenchでも確認してみましょう。
Navigator欄右上のリフレッシュボタンを押下すると、taskdb-Tablesの下にm_taskが作成されているかと思います。
また、以下のSQLを実行すると、m_taskテーブルに5レコード追加されていることが分かります。
select *
from m_task;
REST Client
続いて、REST Clientでも確認してみましょう。
操作方法については前回記事で説明していますので割愛します。
Spring Bootを使ったREST APIの基本構築から動作確認まで②-動作確認
POST
新たにタスクを1件追加します。
メソッド:POST
URL:http://localhost:8080/tasks
形式:JSON
{
"title": "運動",
"status": "TODO"
}
idは自動採番されるため、指定不要です。
上記を実行後、MySQL Workbenchで確認してみます。
select *
from m_task;
id:6にPOSTしたデータが追加されました。
PUT
id:1のタスクのステータスをDONEに更新します。
メソッド:PUT
URL:http://localhost:8080/tasks/1
形式:JSON
{
"id":1,
"title": "掃除",
"status": "DONE"
}
上記を実行後、MySQL Workbenchで確認してみます。
select *
from m_task;
id:1のステータスがPUTした内容に更新されました。
DELETE
先程追加したid:6のタスクを削除します。
メソッド:DELETE
URL:http://localhost:8080/tasks/6
上記を実行後、MySQL Workbenchで確認してみます。
select *
from m_task;
id:6のタスクがDELETEメソッドによって削除されました。
例外の確認
最後に例外の確認をしておきましょう。
REST ClientでGETメソッドを選択し、URLに存在しないidを指定します。
ここでは、先程削除したid:6を指定してみます。
メソッド:GET
URL:http://localhost:8080/tasks/6
実行すると、以下のようにHTTPステータスコード:404と、エラーメッセージが表示されることが確認できました。