概要
前回 SpringBoot から MySQL に接続する方法を紹介しました
その際はリクエストの情報をそのまま Entity に渡していましたがそれだといろいろと脆弱です (マスアサインメントなど)
なので通常は DTO(Data Transfer Object) と呼ばれる内部で扱うクラスに変換してから Entity に渡します
今回は DTO を使ったサンプルコードを紹介します
更に応用として Service レイヤーを追加して Repository とまとめる方法も紹介します
環境
- macOS 26.4.1
- openjdk 26.0.1
- SpringBoot 4.0.6
- jasypt-spring-boot 4.0.4
- gradle 9.5.1
- VSCode 1.121.0
- MySQL 9.6.0
バリデーション用のライブラリ追加
spring-boot-starter-validation を使うので以下を追加します
バージョンは plugins 側で制御しています
- vim build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.6'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
DTO 用のクラスを作成する
基本的にはここでリクエスト値などのバリデーションを行います
今回はバリデーション用のライブラリにある基本的なバリデーションのみを追加します
- vim src/main/java/com/example/demo/dto/UserRequest.java
package com.example.demo.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public class UserRequest {
@NotBlank
private String name;
@Email
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
コントローラの修正
コントローラ側ではリクエスト情報を先程の RequestUser クラスに自動的に変換するように修正します
@Valid, @RequestBody を使います
- vim src/main/java/com/example/demo/MainController.java
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.dto.UserRequest;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import jakarta.validation.Valid;
@Controller
@RequestMapping(path = "/demo")
public class MainController {
@Autowired
private UserRepository userRepository;
@PostMapping(path = "/add")
public @ResponseBody String addNewUser(
@Valid @RequestBody UserRequest req) {
User user = new User();
user.setName(req.getName());
user.setEmail(req.getEmail());
userRepository.save(user);
return "Saved";
}
@GetMapping(path = "/all")
public @ResponseBody Iterable<User> getAllUsers() {
return userRepository.findAll();
}
}
とりあえずここで動作確認
まずはここでちゃんと動作するか確認します
-
./gradlew bootRun --args='--jasypt.encryptor.password=xxx'
application.properties を暗号化しているので jasypt.encryptor.password を指定していますが暗号化していなければ不要です
curl で動作確認しましょう
-
curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail@someemailprovider.com"}' -H 'content-type: application/json'
正常系は上記です
バリデーションエラーの確認をする場合は以下のようにリクエストしてみましょう
-
curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "error_email"}' -H 'content-type: application/json'
{"timestamp":"2026-05-23T00:54:34.921Z","status":400,"error":"Bad Request","path":"/demo/add"}
コンソールのエラーメッセージには以下のように表示されると思います
2026-05-23T09:54:34.907+09:00 WARN 69601 --- [demo] [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.demo.MainController.addNewUser(com.example.demo.dto.UserRequest): [Field error in object 'userRequest' on field 'email': rejected value [error_email]; codes [Email.userRequest.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userRequest.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@2601ac31,.*]; default message [電子メールアドレスとして正しい形式にしてください]] ]
更に Service レイヤーを追加する
これでも十分ですが通常は更に Service レイヤーを追加します
Service レイヤーでは Repository を扱い受け取った UserRequest を使用します
基本的には何かしらのビジネスルールなどを実装する必要があります
- vim src/main/java/com/example/demo/service/UserService.java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.dto.UserRequest;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(UserRequest req) {
// 例:ビジネスルール
if (userRepository.existsByEmail(req.getEmail())) {
throw new IllegalArgumentException("既に登録されています");
}
User user = new User();
user.setName(req.getName());
user.setEmail(req.getEmail());
userRepository.save(user);
}
public Iterable<User> getAllUsers() {
return userRepository.findAll();
}
}
UserRepository.java 修正
ビジネスロジック用の処理を追加します
- vim src/main/java/com/example/demo/repository/UserRepository.java
package com.example.demo.repository;
import org.springframework.data.repository.CrudRepository;
import com.example.demo.entity.User;
public interface UserRepository extends CrudRepository<User, Integer> {
boolean existsByEmail(String email);
}
MainController の修正
MainController で Repository は扱わずに Service を扱うように修正します
- vim src/main/java/com/example/demo/MainController.java
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.dto.UserRequest;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import jakarta.validation.Valid;
@Controller
@RequestMapping(path = "/demo")
public class MainController {
@Autowired
private UserService userService;
@PostMapping(path = "/add")
public @ResponseBody String addNewUser(@Valid @RequestBody UserRequest req) {
userService.createUser(req);
return "Saved";
}
@GetMapping(path = "/all")
public @ResponseBody Iterable<User> getAllUsers() {
return userService.getAllUsers();
}
}
動作確認
-
curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail2@someemailprovider.com"}' -H 'content-type: application/json'
-
curl http://localhost:8080/demo/all
同じメールアドレスで登録しようとするとエラーになります
現状は特にエラーを指定していないので500エラーになります
-
curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail@someemailprovider.com"}' -H 'content-type: application/json'
{"timestamp":"2026-05-23T01:50:23.001Z","status":500,"error":"Internal Server Error","path":"/demo/add"}
最後に
SpringBoot で DTO を使って直接リクエスト情報を扱わない方法を紹介しました
Service レイヤーを入れる場合にはビジネスロジックも実装するようにしましょう
個人的にメールアドレスの重複チェックはどちらかと言えばバリデーションに近いのではと思うのですがデータベースを使うバリデーションはビジネスロジックになるようでその場合は Service でバリデーションするのが良いようです
レスポンスに関しても DTO を使うのが定石なので次回はレスポンス用の DTO を作成し使うように修正します
0 件のコメント:
コメントを投稿