---
title: 業務ロジック — VisitController.loadPetWithVisit()
created: 2026-07-22
---

# 業務ロジック — VisitController.loadPetWithVisit() [19 LOC]

| 項目 | 値 |
|-------|-------|
| 完全修飾名 | `org.springframework.samples.petclinic.owner.VisitController` |
| レイヤー | Controller |
| モジュール | `owner` (パッケージ: `org.springframework.samples.petclinic.owner`) |

## 1. 役割

### VisitController.loadPetWithVisit()

本メソッドは、Spring MVCの`@ModelAttribute`アノテーションにより、`VisitController`内のすべての`@RequestMapping`付きメソッドが呼び出される**直前に自動実行されるプリフェッチ（事前データ取得）メソッド**である。ペットクリニック経営ドメインにおいて、飼い主（Owner）とその所有するペット（Pet）に関する最新かつ一貫性のあるデータを、リクエスト毎に毎回取得することを目的としている。セッションスコープを使用しないStatelessな設計であるため、フォームデータに含まれないペットのID（`id`）を確実に保持し、Viewへ渡すモデル属性として`pet`および`owner`をセットアップする。具体的には、URLパスから抽出した`ownerId`で飼い主を一意に特定し、さらに`petId`でその飼い主が持つ特定のペットを検索する。見つかったペットに新規の診察記録（`Visit`）インスタンスを紐付け、モデルへ登録した上で、新規診察フォームのバディングターゲットとなる`Visit`オブジェクトを返却する。このように、本メソッドは**ブロードキャスティング・ディスパッチ（广播分发）**のパターンを採用し、コントローラ内の複数の画面アクションで共用される準備処理を一元化している。呼び出し元のメソッド（`initNewVisitForm`、`processNewVisitForm`）は、本メソッドが準備したモデル属性をそのまま利用して業務画面を表示・処理を行う。

## 2. 処理パターン（詳細業務ロジック）

```mermaid
flowchart TD
    START(["loadPetWithVisit(ownerId, petId, model)"])
    START --> STEP1["owners.findById(ownerId)"]
    STEP1 --> CHECK1{Owner 存在?}
    CHECK1 -- Yes --> STEP2["owner.getPet(petId)"]
    CHECK1 -- No --> ERR1["throw IllegalArgumentException"]
    STEP2 --> CHECK2{Pet 存在?}
    CHECK2 -- Yes --> STEP3["model.put(pet, owner)"]
    CHECK2 -- No --> ERR2["throw IllegalArgumentException"]
    STEP3 --> STEP4["new Visit()"]
    STEP4 --> STEP5["pet.addVisit(visit)"]
    STEP5 --> STEP6["return visit"]
    STEP6 --> END(["モデル属性設定完了"])
    ERR1 --> HALT(["処理終了: 例外"])
    ERR2 --> HALT
```

## 3. パラメータ分析

| No | パラメータ名 | 型 | 業務的説明 |
|----|---------------|------|---------------------|
| 1 | `ownerId` | `@PathVariable("ownerId") int` | URLパスから抽出される飼い主（Owner）の一意識別子。ペットクリニックにおける「顧客」に相当し、`/owners/{ownerId}/pets/{petId}/visits/new` のパスから取得される。このIDで`OwnerRepository.findById()`により`owners`テーブルからレコードを取得する。存在しないIDが渡された場合は`IllegalArgumentException`をスローする。 |
| 2 | `petId` | `@PathVariable("petId") int` | URLパスから抽出されるペット（Pet）の一意識別子。特定の飼い主に紐づく動物のIDであり、`owner.getPet(petId)`によりオーナーのペット一覧から該当レコードを検索する。存在しないIDや他のオーナーに属するペットのIDが渡された場合は`IllegalArgumentException`をスローする。 |
| 3 | `model` | `Map<String, Object>` | Spring MVCのモデルマップ。本メソッド内で検索結果の`Pet`オブジェクトおよび`Owner`オブジェクトがキー`"pet"`、`"owner"`で格納され、ThymeleafなどのViewテンプレートエンジンへ渡される。 |

**インスタンスフィールド:**

| フィールド名 | 型 | 業務的説明 |
|--------------|------|---------------------|
| `owners` | `OwnerRepository` | 飼い主（Owner）データの永続化レイヤーを担うSpring Data JPAリポジトリ。`findById()`により`owners`テーブルからの読込を行う。 |

## 4. CRUD操作／呼び出しサービス

### コード解析グラフからの事前抽出エビデンス:

| CRUD | SC / CBS | SCコード | エンティティ / DB | 操作の説明 |
|------|----------|---------|-------------|----------------------|
| R | `OwnerRepository.findById` | OwnerRepository | owners | `findById(ownerId)` で`owners`テーブルから飼い主レコードを1件取得（JPA `Optional` 戻り値） |
| R | `Owner.getPet` | Owner | pets | 飼い主のペット一覧内から`petId`に一致するペットをループ検索（`owners` テーブルとは別、`pets` テーブルからEagerフェッチ済み） |

### 補足：`pet.addVisit(visit)` について

本メソッド内で呼び出される `pet.addVisit(visit)` は、`Pet`クラスのローカルメソッド（`visits`集合への`Visit`追加）であり、直接のDB INSERTを伴わない。実際の`visits`テーブルへのCreateは、`VisitController`の呼び出し元である`processNewVisitForm`メソッド（`@PostMapping`）において`visitService.save(visit)`が呼ばれたタイミングでJPAにより永続化される。したがって、本メソッド単体でのCRUDは**Readのみに限定**される。

| CRUD | SC / CBS | SCコード | エンティティ / DB | 操作の説明 |
|------|----------|---------|-------------|----------------------|
| - | `pet.addVisit(visit)` | Pet（ローカル） | visits | 在メモリ上で`Pet`の`visits`コレクションに`Visit`インスタンスを追加（DB書き込みは後続の`@PostMapping`メソッドで実施） |

## 5. 依存関係トレース

| # | 呼び出し元（画面/バッチ） | 呼び出しチェーン（本メソッドまでの全経路） | 終端（SC / CRUD / エンティティ） |
|---|----------------------|--------------------------------------|-------------------------------|
| 1 | Spring MVC (Viewレンダリング) | `@ModelAttribute` 自動バインディング -> `VisitController.loadPetWithVisit` | `OwnerRepository.findById [R] owners`, `Owner.getPet [R] pets`, `pet.addVisit [-] visits (in-memory)` |
| 2 | Spring MVC (Viewレンダリング) | `@GetMapping("/owners/{ownerId}/pets/{petId}/visits/new")` -> `initNewVisitForm` -> `@ModelAttribute` 自動バインディング -> `VisitController.loadPetWithVisit` | 同上 |
| 3 | Spring MVC (フォーム送信後処理) | `@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")` -> `processNewVisitForm` -> `@ModelAttribute` 自動バインディング -> `VisitController.loadPetWithVisit` | 同上 |

**注釈:** `@ModelAttribute`メソッドは、同一コントローラ内の全リクエストマッピングメソッド実行前にSpring MVCによって自動呼び出される。本メソッドの「呼び出し元」は、コントローラ内の個別の`@RequestMapping`メソッドではなく、Spring MVCフレームワーク自体である。

## 6. 分岐ごとの詳細ブロック

**ブロック 1** — [CALL] `owners.findById(ownerId)` (L64)

> URLパスから取得した`ownerId`で飼い主レコードを取得する。戻り値は`Optional<Owner>`型。

| # | 種別 | コード |
|---|------|------|
| 1 | CALL | `owners.findById(ownerId)` // OwnerRepository.findById -> ownersテーブル検索 [R] |
| 2 | SET | `optionalOwner = Optional<Owner>` // 取得結果をOptionalにラップ |

**ブロック 2** — [CALL / THROW] Owner存在チェック (L65-66)

> Optionalが空の場合（該当Ownerが存在しない場合）、業務例外をスローして処理を中断する。

| # | 種別 | コード |
|---|------|------|
| 1 | EXEC | `optionalOwner.orElseThrow(...)` // Ownerが存在しない場合、IllegalArgumentExceptionをスロー |
| 2 | SET | `owner = Owner` // 存在するOwnerインスタンスを取得 |
| 3 | THROW | `IllegalArgumentException("Owner not found with id: " + ownerId)` // エラーメッセージにownerIdを含める |

**ブロック 3** — [CALL] `owner.getPet(petId)` (L68)

> 取得したOwnerのペット一覧から、指定された`petId`に一致するPetを検索する。

| # | 種別 | コード |
|---|------|------|
| 1 | CALL | `owner.getPet(petId)` // Owner.getPet(Integer) -> petsリストのループ検索 [R] |
| 2 | SET | `pet = Pet` // 該当ペット、またはnull |

**ブロック 4** — [IF] Pet存在チェック (L69)

> 指定された`petId`のペットがOwnerに紐づいていない場合（null）、業務例外をスローする。

| # | 種別 | コード |
|---|------|------|
| 1 | SET | `pet == null` // 条件分岐 |
| 2 | [IF] | `pet != null` -> ブロック 5 へ継続 |
| 3 | [ELSE] | `pet == null` -> ブロック 4.1 へ |

**ブロック 4.1** — [ELSE / THROW] Pet不存在エラー (L70-72)

> ペットが見つからない場合、具体的なエラーメッセージをスローする。

| # | 種別 | コード |
|---|------|------|
| 1 | THROW | `IllegalArgumentException("Pet with id " + petId + " not found for owner with id " + ownerId + ".")` // 両IDを含めた詳細エラー |

**ブロック 5** — [EXEC] モデル属性への登録 (L73-74)

> 検索結果のPetとOwnerをSpring MVCのモデルに格納し、Viewテンプレートで利用可能にする。

| # | 種別 | コード |
|---|------|------|
| 1 | EXEC | `model.put("pet", pet)` // View用モデル属性にPetをセット |
| 2 | EXEC | `model.put("owner", owner)` // View用モデル属性にOwnerをセット |

**ブロック 6** — [CALL / RETURN] 新規Visitの生成・登録 (L76-78)

> 新規診察記録（Visit）オブジェクトを生成し、Petに紐づけた上で返却する。返却値はSpring MVCによって`@ModelAttribute("visit")`としてモデルにバインドされ、`@PostMapping`メソッドで利用可能になる。

| # | 種別 | コード |
|---|------|------|
| 1 | SET | `visit = new Visit()` // 新規Visitインスタンス生成（日付は今日） |
| 2 | CALL | `pet.addVisit(visit)` // PetのvisitsコレクションにVisitを追加（在メモリ） |
| 3 | RETURN | `return visit` // Spring MVCによりモデル属性"visit"としてバインド |

## 7. 用語集

| 用語 | 種別 | 業務的意味 |
|------|------|------------------|
| `VisitController` | Class | 飼い主のペットに関する診察記録（Visit）のCRUD操作を担うSpring MVCコントローラ |
| `loadPetWithVisit` | Method | 新規診察フォーム表示・登録前のプリフェッチ用@ModelAttributeメソッド |
| `Owner` | Entity | ペットの飼い主（顧客）を表すドメインエンティティ。`owners`テーブルにマッピングされる |
| `Pet` | Entity | クリニックで診察を受ける動物（ペット）を表すドメインエンティティ。`pets`テーブルにマッピングされる |
| `Visit` | Entity | 診察記録（受診履歴）を表すドメインエンティティ。`visits`テーブルにマッピングされる |
| `OwnerRepository` | Interface | Spring Data JPAのリポジトリインタフェース。`Owner`エンティティの永続化を抽象化する |
| `@ModelAttribute` | Annotation | Spring MVCのアノテーション。リクエストマッピング実行前に自動呼び出され、モデル属性を準備する |
| `@PathVariable` | Annotation | URLパスプレースホルダ（例: `{ownerId}`）から値を抽出してメソッドパラメータにバインドする |
| `@InitBinder` | Annotation | リクエストパラメータの型変換・バリデーション設定を行うメソッド用アノテーション |
| `@GetMapping` | Annotation | HTTP GETリクエストのマッピングを定義する |
| `@PostMapping` | Annotation | HTTP POSTリクエストのマッピングを定義する |
| `owners` | DB Table | 飼い主（Owner）情報を格納するデータベーステーブル |
| `pets` | DB Table | ペット（Pet）情報を格納するデータベーステーブル |
| `visits` | DB Table | 診察記録（Visit）情報を格納するデータベーステーブル |
| `IllegalArgumentException` | Exception | 不正な引数が渡された場合にスローされるJava標準例外。業務エラーとしてViewへ伝播 |
| `Spring PetClinic` | Application | Spring Frameworkのサンプルアプリケーション。ペットクリニック経営の管理システム |
| `PetType` | Entity | ペットの種別（犬、猫等）を表すエンティティ。`pets`テーブルの`type_id`外部キー参照先 |
