1. test 1
  2. test 2
  3. test 3
  4. test 4
コース一覧
Spring Boot入門
DTOとエンティティを分ける

Spring Boot入門

Java で Web API サーバーを作るための Spring Boot の入門コースです。DI コンテナ、REST コントローラ、リクエスト検証、レイヤ分割、Spring Data JPA、例外ハンドリング、テストまでを扱います。毎レッスン、Spring が裏側でやっている判断そのものを pure Java の関数として自分で書き、フレームワークが何を肩代わりしてくれているのかを手触りで理解できる構成にしました。約 12 時間 (1 日 30 分 × 24 日) で 48 レッスンを修了できます。

1
Spring Bootとは何か
01. Spring Bootで何が作れるのか10分
02. サーブレットからフレームワークへ15分
03. ルーティング表という発想15分
04. プロジェクトの構成10分
05. 起動のしくみと自動設定15分
06. パスのマッチング15分
2
DIコンテナ
01. newで書くと何が困るのか15分
02. コンストラクタインジェクション15分
03. インターフェースへの依存15分
04. @Componentと@Bean15分
05. 自作のDIコンテナ20分
06. Beanのスコープとライフサイクル15分
3
RESTコントローラ
01. RESTという設計方針15分
02. @RestControllerの役割15分
03. @GetMappingとパス変数15分
04. クエリパラメータの受け取り15分
05. @PostMappingとリクエストボディ15分
06. ステータスコードの選び方15分
07. ResponseEntityで組み立てる15分
4
リクエストとレスポンスの変換
01. JSONとオブジェクトの往復15分
02. DTOとエンティティを分ける15分
03. recordでDTOを書く15分
04. 入力検証の基本15分
05. 検証エラーをまとめて返す15分
06. アノテーションによる検証15分
5
レイヤ構成とビジネスロジック
01. 3層アーキテクチャ10分
02. @Serviceに置くもの15分
03. 状態遷移をルールにする15分
04. 料金計算のような業務ロジック15分
05. @Transactionalとは何か15分
06. トランザクションの巻き戻し15分
6
データアクセス
01. @Entityとテーブルの対応15分
02. リポジトリという抽象15分
03. メソッド名からクエリを作る20分
04. 検索条件の組み立て15分
05. ページングと並び替え15分
06. N+1問題15分
7
例外処理と横断的関心事
01. 例外をHTTPに変換する15分
02. @ExceptionHandlerと@ControllerAdvice15分
03. エラーレスポンスの形を決める15分
04. フィルタとインターセプタ15分
05. ログとリクエストの追跡15分
06. 設定値の外部化15分
8
テストと仕上げ
01. テストの種類と使い分け15分
02. Serviceの単体テスト15分
03. モックで依存を差し替える15分
04. MockMvcでエンドポイントを試す15分
05. 本番へ出すための次の一歩10分

DTOとエンティティを分ける

このレッスンでは、エンティティをそのままレスポンスに使うことの危うさを理解し、公開してよい形へ詰め替える DTO の役割を説明できるようになります。

エンティティをそのまま返すとどうなるでしょうか

データベースのテーブルに対応するクラスを、そのままコントローラの戻り値にすると、コードは短くなります。

Java

@GetMapping("/users/{id}") public User find(@PathVariable Long id) { return userRepository.findById(id).orElseThrow(); }

しかし User は本来テーブルの列をそのまま写した内部向けのクラスです。passwordHash、failedLoginCount、internalMemo、deletedAt のような、外に出す気のなかった項目まで一緒に JSON へ載ります。Jackson は「見せてよい項目」を知りません。フィールドがあれば、そのまま文字列にします。

3つの問題が同時に起きます

問題は次の3つです。

  1. 漏洩 — パスワードのハッシュや内部メモのような、外部に出してはいけない値が返る
  2. 内部構造の露出 — 列名の変更やリレーションの追加が、そのままレスポンスの形の変更になる。テーブル設計を変えただけでクライアントが壊れる
  3. 意図しない再帰 — 双方向の関連を持つエンティティどうしを直列化すると、参照をたどり続けて無限ループになる

とくに2つ目が効いてきます。エンティティは永続化の都合で決まる形、レスポンスは利用者の都合で決まる形です。この2つを同じクラスで兼ねると、片方の都合がもう片方を必ず引きずります。

DTO は「外に出す形」を宣言したものです

DTO は Data Transfer Object の略で、層をまたいで運ぶためだけの入れ物です。

Java

public record UserResponse(Long id, String name) {} @GetMapping("/users/{id}") public UserResponse find(@PathVariable Long id) { User user = userRepository.findById(id).orElseThrow(); return new UserResponse(user.getId(), user.getName()); }

このように書くと、公開する項目が型として明示されます。User にフィールドを増やしても UserResponse は変わらないので、レスポンスは勝手に太りません。逆に公開したいものが増えたときは、DTO に書き足すという明確な操作が必要になります。うっかり漏れることがなくなる、というのが最大の利点です。

観点エンティティレスポンス DTO
形を決める都合テーブル設計クライアントの必要
含む項目すべての列公開してよい項目だけ
変更の影響DB 都合で変わるAPI の互換性で守られる

詰め替えはどこで書くのでしょうか

小さなうちはコントローラや Service の中で new UserResponse(...) と書くだけで十分です。規模が大きくなったら、DTO 側に static UserResponse from(User user) のようなファクトリメソッドを置くか、専用のマッパークラスを用意します。いずれにしても、やっていることは「必要なフィールドを選んで新しいオブジェクトに入れ直す」という単純な作業です。

演習で書くもの

演習では、内部モデル相当の値である id、name、email、passwordHash の4つを受け取り、公開してよい項目だけを選んで連結した文字列を返します。passwordHash は絶対に出してはいけません。email はメールアドレスなので、そのまま全部を出すのではなく、ローカル部の先頭1文字だけを残して伏せます。Spring では DTO のコンストラクタ1行で終わる詰め替えを、ここでは「何を出して何を出さないか」の判断を含めて自分で書きます。

要件

  1. 戻り値は id=,name=,email=<マスクしたメール> の形にすること
  2. passwordHash は戻り値のどこにも含めないこと
  3. email はローカル部の先頭1文字 + + @ + ドメイン の形に伏せること。null か空なら空文字列、アットマークが無いかローカル部が空なら とすること

入出力例

toPublic(1, "taro", "taro@example.com", "$2a$10$abcdef") → "id=1,name=taro,email=t***@example.com" toPublic(42, "hanako", "hanako.y@mail.co.jp", "hashed") → "id=42,name=hanako,email=h***@mail.co.jp" toPublic(7, "guest", "", "x") → "id=7,name=guest,email=" toPublic(9, "noat", "invalid-mail", "y") → "id=9,name=noat,email=***"

ヒント

indexOf('@') でアットマークの位置を求め、substring でローカル部とドメインに切り分けます

アットマークが見つからない場合の戻り値は -1、先頭にある場合は 0 です。どちらも *** 扱いなので at <= 0 でまとめて判定できます

char と String を + でつなぐと文字列になります。local.charAt(0) + "***@" + domain と書けます

生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • recordでDTOを書く

    record を使った不変な DTO を定義できるようになります。

  • 入力検証の基本

    必須・長さ・範囲の検証を実装できるようになります。

  • 検証エラーをまとめて返す

    複数のエラーを集約したレスポンスを組み立てます。

  • アノテーションによる検証

    @NotNull と @NotBlank の違いなどを確認します。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • エンティティDB の 1 行 = アプリの 1 オブジェクト
  • リクエストWeb 通信の基本単位、ブラウザの問い合わせとサーバーの返答
  • データベースデータを構造化して保存する仕組み
  • テーブルDB の表 (Excel のシートみたいなもの)
  • クラスデータと振る舞いを束ねる単位
  • 戻り値呼び出し元への返答を表す点線矢印
  • applicationJSON 本文を送るときの Content-Type
  • フィールドクラスが持つデータ
main.java
学習モード
エディタを読み込んでいます

メモ

DTOとエンティティを分ける

⌘S で保存