Spring Bootで予約機能を実装してみた|バリデーション・例外処理
はじめに
現在、Spring Bootを使って「イベント予約管理システム」の開発を進めています。
今回の記事では、「予約機能」の実装についてまとめました。
予約機能では以下のようなことを行いました。
- Service層・Controller層 の実装
- Formによるバリデーション設定(入力チェック)
- バリデーションエラーや存在しないIDなどの Exception(例外)処理
- APIの POST / GET を使った登録・一覧取得の動作確認
また、今後の開発に向けて、Spring BootでのMVC構成の基本も一緒に振り返りながら、
コードの一部をピックアップして解説していきます。
これまでの記事も併せてご覧ください。
今回の実装内容
今回の開発では、「予約機能」を中心に以下の内容を実装しました。
対象となるドメインは以下の3つです。
- Event(イベント)
- Reservation(予約)
- User(ユーザー)
これらのドメインに対して、以下の層を実装しています。
- Form(入力値のバリデーション設定)
- Service層(ビジネスロジックの実装)
- Controller層(APIの受け口)
- Exception(例外処理)
MVCアーキテクチャの簡単な説明
Spring Boot では、アプリケーションの構成に「MVCアーキテクチャ」がよく使われます。
MVCとは、以下の3つの役割に分けて機能を整理する考え方です。
-
Model(モデル)
アプリケーションのデータやその処理を担当します。
今回の開発では、Entity や Repository がこれにあたります。 -
View(ビュー)
画面の見た目や表示部分を担当します。
今回はバックエンド API のみ実装しており、View はまだありません。 -
Controller(コントローラー)
ユーザーからのリクエストを受け取り、適切な処理(Serviceなど)に振り分けます。
予約APIのリクエストの流れ
ユーザーが「予約を登録」したいときの処理は、次のように流れていきます。
-
ユーザーからのリクエスト(例:POST /reservations)
フロントエンドなどからAPIへリクエストが送られます。 -
Controller
リクエストを受け取り、入力内容(Form)のチェックを行います。
バリデーションが通れば、Service層に処理を渡します。
※バリデーションエラーがあればここでエラーレスポンス(Exception)を返します。 -
Service
ビジネスロジックを担当する層です。
例えば「イベントが存在するか?」「すでに予約済みではないか?」などの確認を行います。
必要なデータは Repository を通じてデータベースから取得します。 -
Repository
データベースと直接やり取りを行う層です。
データの検索・保存・更新などを行います。 -
Entity
データベースのテーブル構造に対応するクラスです。
Repositoryが取得したデータは Entity として Service に返されます。
MVC構成を意識することで、機能ごとの責任が明確になり、コードの見通しも良くなります。
ReservationのForm解説
予約登録の際、フロントエンドから送られてくるデータを受け取るために ReservationForm
を作成しています。
このクラスでは、ユーザーIDとイベントIDの2つの値を受け取り、入力チェック(バリデーション) を行っています。
ReservationFormのコード
package com.example.event_reservation_app.form;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
public class ReservationForm {
@NotNull(message = "ユーザーIDは必須です")
@Positive(message = "ユーザーIDは正の数である必要があります")
private Long userId;
@NotNull(message = "イベントIDは必須です")
@Positive(message = "イベントIDは正の数である必要があります")
private Long eventId;
// --- Getter / Setter ---
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getEventId() {
return eventId;
}
public void setEventId(Long eventId) {
this.eventId = eventId;
}
}
バリデーションの説明
-
@NotNull
値がnull
(空)でないことをチェックします。
→ ユーザーID・イベントIDはどちらも必須の入力項目です。 -
@Positive
0より大きい正の数であることをチェックします。
→ IDは通常、1以上の連番で管理されているため、不正な値(0やマイナス)を防ぐ目的で使用しています。
これらのバリデーションは、spring-boot-starter-validation
という依存関係を pom.xml
に追加することで有効になります。
フォームの入力に対して制約違反があった場合は、Controller で 自動的にエラーレスポンス(400 Bad Request) が返されます。
バリデーション機能を有効にするには?
現状だとバリデーションが有効でなかったので、以下の依存関係を pom.xml
に追加しました
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
ReservationControllerの解説
予約機能のAPIの入り口となる ReservationController
では、予約登録と予約一覧の取得を担当しています。
package com.example.event_reservation_app.controller;
import com.example.event_reservation_app.entity.Reservation;
import com.example.event_reservation_app.form.ReservationForm;
import com.example.event_reservation_app.service.ReservationService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/reservations")
public class ReservationController {
@Autowired
private ReservationService reservationService;
// 予約登録(POST)
@PostMapping
public Reservation createReservation(@RequestBody @Valid ReservationForm form) {
return reservationService.createReservation(form);
}
// 予約一覧取得(GET)
@GetMapping
public List<Reservation> getAllReservations() {
return reservationService.getAllReservations();
}
}
ポイント解説
-
@RestController
→ REST API用のコントローラーであることを示しています。
→ メソッドの戻り値は自動的にJSON形式で返されます。 -
@RequestMapping("/reservations")
→ このクラスのAPIのベースURLを/reservations
に設定しています。
予約登録(POST)メソッド
-
@PostMapping
でPOSTリクエストを受け取ります。 -
@RequestBody
でリクエストのJSONをReservationForm
にマッピングします。 -
@Valid
によりFormのバリデーションが有効になります。 - バリデーションがOKなら
reservationService.createReservation(form)
を呼び出し、処理結果のReservation
を返します。
予約一覧取得(GET)メソッド
-
@GetMapping
でGETリクエストを受け取ります。 -
reservationService.getAllReservations()
を呼び出し、予約リストを取得して返します。
ReservationServiceの解説
予約のビジネスロジックを担当するサービス層です。
package com.example.event_reservation_app.service;
import com.example.event_reservation_app.entity.Event;
import com.example.event_reservation_app.entity.Reservation;
import com.example.event_reservation_app.entity.User;
import com.example.event_reservation_app.form.ReservationForm;
import com.example.event_reservation_app.repository.EventRepository;
import com.example.event_reservation_app.repository.ReservationRepository;
import com.example.event_reservation_app.repository.UserRepository;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class ReservationService {
private final ReservationRepository reservationRepository;
private final UserRepository userRepository;
private final EventRepository eventRepository;
// コンストラクタインジェクションでリポジトリを注入
public ReservationService(
ReservationRepository reservationRepository,
UserRepository userRepository,
EventRepository eventRepository) {
this.reservationRepository = reservationRepository;
this.userRepository = userRepository;
this.eventRepository = eventRepository;
}
// 予約登録処理
public Reservation createReservation(ReservationForm form) {
// ユーザーIDからユーザー情報を取得。なければ400エラーを返す
User user = userRepository.findById(form.getUserId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "指定されたユーザーが存在しません"));
// イベントIDからイベント情報を取得。なければ400エラーを返す
Event event = eventRepository.findById(form.getEventId())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "指定されたイベントが存在しません"));
// 予約エンティティを作成して日時をセット
Reservation reservation = new Reservation();
reservation.setUser(user);
reservation.setEvent(event);
reservation.setReservedAt(LocalDateTime.now());
// DBに保存して結果を返す
return reservationRepository.save(reservation);
}
// 全予約の一覧取得処理
public List<Reservation> getAllReservations() {
return reservationRepository.findAll();
}
}
ポイント解説
-
コンストラクタインジェクション
リポジトリをコンストラクタで受け取り、サービスクラス内で使います。
→ テストしやすく、依存関係が明確になります。 -
createReservationメソッド
- フォームのユーザーID・イベントIDが正しいかDBからチェックします。
- 存在しなければ
ResponseStatusException
を投げて400 Bad Requestを返します。 - 問題なければ予約エンティティを作成し、現在日時を予約日時としてセットします。
- 予約情報をDBに保存して返します。
-
getAllReservationsメソッド
予約テーブルの全データを取得して返します。
例外処理の解説(GlobalExceptionHandler)
Spring Bootでのバリデーションエラーなどを一括で処理するクラスです。
package com.example.event_reservation_app.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
// バリデーションエラー発生時の処理
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
// フィールドごとのエラーメッセージを取得してMapに格納
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String message = error.getDefaultMessage();
errors.put(fieldName, message);
});
// 400 BAD REQUEST とエラーメッセージを返す
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
ポイントまとめ
-
@ControllerAdvice
で全コントローラーに対して例外処理を適用 -
@ExceptionHandler
で特定の例外をキャッチし、レスポンスをカスタマイズ -
バリデーションエラーはフィールドごとのエラーメッセージをまとめて返す
-
クライアントは400エラーとして受け取り、どこが問題かを把握しやすくなる
おわりに
今回は、Spring Boot を使った「イベント予約機能」の実装についてご紹介しました。
Formバリデーション、サービス層でのビジネスロジック、コントローラーでのAPI実装、例外処理など、MVCアーキテクチャの流れを意識しながら開発を進めることができました。
次回は、フロントエンド開発(React) に入っていく予定です。
IntelliJでReactの開発環境を整えつつ、まずはイベント登録画面から作っていきたいと思います。
次に予定している開発ステップ
- イベント登録画面(POST送信)
- イベント一覧表示画面(GET取得)
- 予約登録フォーム(ユーザーID・イベントIDを指定 → POST送信)
- 予約一覧表示画面(GET取得)
今後も、バックエンドとフロントエンドをつなぎながら、少しずつ全体像を作っていく予定です!
Discussion