SpringBoot でリクエスト情報を DTO に使うサンプルコード
概要
前回 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 側で制御しています
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 ( ) ;
}
}
とりあえずここで動作確認
まずはここでちゃんと動作するか確認します
application.properties を暗号化しているので jasypt.encryptor.password を指定していますが暗号化していなければ不要です
curl で動作確認しましょう
正常系は上記です
バリデーションエラーの確認をする場合は以下のようにリクエストしてみましょう
{"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エラーになります
{"timestamp":"2026-05-23T01:50:23.001Z","status":500,"error":"Internal Server Error","path":"/demo/add"}
最後に
SpringBoot で DTO を使って直接リクエスト情報を扱わない方法を紹介しました
Service レイヤーを入れる場合にはビジネスロジックも実装するようにしましょう
個人的にメールアドレスの重複チェックはどちらかと言えばバリデーションに近いのではと思うのですがデータベースを使うバリデーションはビジネスロジックになるようでその場合は Service でバリデーションするのが良いようです
レスポンスに関しても DTO を使うのが定石なので次回はレスポンス用の DTO を作成し使うように修正します
参考サイト