CodeAGIを使って開発を行っていきます。（連載６回目）

## 【連載第６回】最終動作確認と実行
前回の投稿では、CodeAGIで生成されたコードを修正する方法について紹介しました。本記事では、生成されたプログラムを修正して、実際に動かすまでのステップを紹介していきます。
修正していくプログラムは連載第４回目で生成した「仕入情報照会画面」です。

## 1. 開発の前提条件
今回の説明は、以下のPC環境を前提に進めていきます。

| 環境 | 詳細                                |
|:---|:------------------------------------|
| OS  | Windows 11 Home |
| DB  | MySQL 8.0.42 |
| JDK | JDK 21 (Java SE Development Kit 21) |
| IDE | STS4 (Spring Tools 4 for Eclipse) |
| ライブラリ | Lombok |

:::note warn
STS4は「Pleiades」プラグインをインストールして、日本語化しています。
:::

これらのインストール手順については割愛させていただきます。他サイトより手順を確認してください。

## 2. プロジェクトの雛形を作成
CodeAGIで生成されるプログラムは、それ単体では動かすことができません。
まずはプロジェクトのベースとなる雛形を用意する必要があります。
今回修正していく「仕入情報照会画面」はSpring Bootフレームワークのプロジェクトであるため、STS4から新しいSpringスタータープロジェクトを作成します。

STS4を開き、「ファイル(F) > 新規(N) > Spring スターター・プロジェクト（Spring Initializr）」をクリックしてください。
![スクリーンショット 2025-05-13 094132.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/fba68d1b-c146-4f71-9165-17b018635bcc.png)

### 基本情報の設定
表示される「新規 Spring スターター・プロジェクト（Spring Initializr）」ポップアップに、今回のプロジェクト情報を入力していきます。
![スクリーンショット 2025-05-13 100747.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/c5f5ebe6-ad51-4c9b-a614-1098f1e25f48.png)

入力項目の簡単な説明をします。
* **サービス URL（Service URL）**：規定値の「https:\//start.spring.io」で問題ありません
* **名前（Name）**：規定値の「demo」でも構いませんが、今回は「仕入情報照会画面」を作成しようとしているので、「purchaseinquiry」とします
* **タイプ（Type）**：今回は「Maven」とします
* **Java バージョン（Java Version）**：PCにインストールしているJDKのバージョンを入力するため、今回は「21」とします
* **パッケージング（Packaging）**：規定値の「Jar」で問題ありません
* **言語（Language）**：規定値の「Java」で問題ありません
* **グループ（Group）**：規定値の「com.example」で問題ありません
* **成果物（Artifact）**：「名前」項目と同じ値を入力してください
※「名前」項目を入力すると自動で補完されます
* **バージョン（Version）**：規定値の「0.0.1-SNAPSHOT」で問題ありません
* **説明（Description）**：規定値の「Demo project for Spring Boot」でも構いませんが、今回は「仕入情報照会画面」を作成しようとしているので、「Demo project for Purchase Info Inquiry Screen」とします
* **パッケージ（Package）**：こちらは、CodeAGIで生成したプログラムのパッケージ構成と同じ形にしたいため、今回は「com.example」とします

入力を終えた後は、「次へ(N)」をクリックしてください。

### 依存関係の設定
「次へ(N)」をクリックすると、「新規 Spring スターター・プロジェクト依存関係」ポップアップが表示されます。
![スクリーンショット 2025-05-14 114410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/84e45ca7-d21e-4141-b48a-91fdb31df47b.png)


こちらのポップアップで、使用するSpring Bootフレームワークのバージョンと、プロジェクトで使用予定の依存環境を選択します。

「Spring Boot バージョン」は規定値のままで問題ありません。

今回のプロジェクトで使用する依存関係は以下の通りです。

| 依存関係 | 詳細 |
|:-|:-|
| Spring Web | Webアプリケーションを構築するための依存関係 |
| Thymeleaf | Javaテンプレートエンジン |
| Spring Data JPA | ORM |
| MySQL Driver | MySQLを使用するためのDBドライバー |
| Lombok | アノテーションライブラリ |
| Validation（検証） | ユーザーが入力したデータ（フォーム）を検証する仕組み |
| Spring Boot DevTools | 開発を効率化するためのツールセット（ホットリロードなど） |
| Spring Boot Actuator | デバッグを容易にするための機能を提供 |

:::note info
「Spring Boot DevTools」と「Spring Boot Actuator」の選択は任意です。
:::


依存関係の選択が完了しましたら、「完了(F)」をクリックしてください。
作成したSpring Bootのプロジェクトが、画面左側の「パッケージ・エクスプローラー」に表示されます。
![スクリーンショット 2025-05-13 141035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/07f09576-b8b5-4157-a807-422c3ff06c19.png)

プロジェクトの雛形が作成できました！

## 3. 作成した雛形に生成コードを配置
先ほど作成したプロジェクトの雛形に、CodeAGIで生成したプログラムを配置します。
以下に生成したプログラム情報を再掲します。（連載４回目より）

- **Entity.java**
    - MstItemCategory.java
    - MstItem.java
    - MstPerson.java
    - MstSupplier.java
    - PurchaseInformation.java
- **SystemApplication.java**
    - SystemApplication.java
- **Repository.java**
    - MstItemCategoryRepository.java
    - MstItemRepository.java
    - MstPersonRepository.java
    - MstSupplierRepository.java
    - PurchaseInformationRepository.java
- **Service.java**
    - PurchaseInformationService.java
    - MstSupplierService.java
    - MstPersonService.java
    - MstItemCategoryService.java
- **Form.java**
    - PurchaseInformationSearchForm.java
- **Controller.java**
    - PurchaseInformationController.java
- **action.js**
    - action.js
- **HTML**
    - purchaseInformationSearch.html

:::note warn
連載４回目の記事では、「仕入先参照画面」のコードも生成していますが、本記事では「仕入情報照会画面」の修正のみ行うため、「仕入先参照画面」の掲載を省いています。
:::

<br>
CodeAGIからすべてのファイルを出力します。
出力時のフォルダ構成は、このようになっています。

```
#指定したルートディレクトリ#
├── プロジェクト共通定義
│   └── Spring Boot
│       └── src
│           └── main
│               └── java
│                   └── com
│                       └── example
│                           ├── entity
│                           │   ├── MstItem.java
│                           │   ├── MstItemCategory.java
│                           │   ├── MstPerson.java
│                           │   ├── MstSupplier.java
│                           │   └── PurchaseInformation.java
│                           └── SystemApplication.java
│
└── 仕入情報照会画面
    └── Spring Boot
        └── src
            └── main
                ├── java
                │   └── com
                │       └── example
                │           ├── repository
                │           │   ├── MstItemCategoryRepository.java
                │           │   ├── MstItemRepository.java
                │           │   ├── MstPersonRepository.java
                │           │   ├── MstSupplierRepository.java
                │           │   └── PurchaseInformationRepository.java
                │           ├── service
                │           │   ├── MstItemCategoryService.java
                │           │   ├── MstPersonService.java
                │           │   ├── MstSupplierService.java
                │           │   └── PurchaseInformationService.java
                │           ├── form
                │           │   └── PurchaseInformationSearchForm.java
                │           └── controller
                │               └── PurchaseInformationController.java
                └── resources
                    ├── static
                    │   └── js
                    │       └── action.js
                    └── templates
                        └── purchaseInformationSearch.html
```

それぞれのファイルをプロジェクトの雛形に配置すると、下の画像のようになります。
![スクリーンショット 2025-05-13 165419.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/048eeaa5-b6f6-40ff-955f-e14f07d081f2.png)

:::note warn
プロジェクトの雛形を作成したときについてきた「PurchaseinquiryApplication.java」は、「SystemApplication.java」と内容が同等であるため、この段階で削除してあります。
:::

## 4. データベースの設定
データベースの設定を行わないとプロジェクトを起動することができません。そのため、まずはデータベースの設定から行っていきましょう。

### MySQL側の設定
今回のプロジェクトではORMとして「Spring Data JPA」を使用しているため、テーブルの作成は事前に行っておく必要はありません。しかし、データベースは作成しておく必要があります。

コマンドプロンプトからMySQLにログインして、下のコードを実行してください。※データベース名は「PURCHASE_INFO_DEMO」としています。

```sql
CREATE DATABASE PURCHASE_INFO_DEMO;
```

これで、MySQL側での設定は完了です。

### Spring Bootプロジェクト側の設定
データベースとの接続に必要な情報は、
「src/main/resources/application.properties」
ファイルに記載していきます。

プロジェクトの雛形を作る段階で自動的に作成されますが、中身は一行のコードのみとなっています。
```application.properties
spring.application.name=purchaseinquiry
```

application.propertiesの内容を以下のように書き換えます。
```application.properties
spring.application.name=purchaseinquiry

# --- 1. Database config properties ---
spring.datasource.url=jdbc:mysql://localhost:3306/PURCHASE_INFO_DEMO
spring.datasource.username={ユーザー名}
spring.datasource.password={パスワード}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# -------------------------------------

# --- 2. JPA config ---
spring.jpa.hibernate.ddl-auto=update
# ---------------------
```
コードの簡単な説明をします。
- 「1. Database config properties」には、MySQLとの接続に必要な情報を記載します
    - `spring.datasource.url`：今回のプロジェクトで使用する、MySQL内のデータベースへのURLを指定します
    - `spring.datasource.username`：ログイン用のユーザー名を指定します
    - `spring.datasource.password`：ログイン用のパスワードを指定します
    - `spring.datasource.driver-class-name`：MySQLのDBドライバーを指定します
- 「2. JPA config」には、ORMの設定を記載します。
    - `spring.jpa.hibernate.ddl-auto`：「update」を指定することで、プロジェクトを実行したときに、テーブルが自動で生成・更新されます。

<br>
これでひとまずデータベースの設定は終わりです。
あとはプロジェクトを実行することができれば、データベースにEntityクラスに対応したテーブルが自動的に作成されます。

次のステップでは、プロジェクトを実行できるようになるまでの流れを説明します。

## 5. プロジェクトの実行
プロジェクトを実行するには、プログラム内で構文エラーとなっている部分をすべて修正する必要があります。

STS4の「パッケージ・エクスプローラー」を確認すると、以下のパッケージ内でエラーが発生しています。
- `com.example.entity`
- `com.example.form`
- `com.example.controller`

今回発生しているエラー内容は、全ファイルで共通していて、import文でエラーが出ています。jakartaパッケージからインポートするべきところが、javaxパッケージになっています。
「`javax`」という記載をすべて「`jakarta`」に置き換えます。

```diff_java
// 例) MstItem.java
- import javax.persistence.*;
+ import jakarta.persistence.*;

// 例) PurchaseInformationSearchForm.java
- import javax.validation.constraints.Size;
+ import jakarta.validation.constraints.Size;

// 例) PurchaseInformationController.java
- import javax.validation.Valid;
+ import jakarta.validation.Valid;
```

これでプロジェクトの実行を妨げている構文エラーはすべて消えました。

:::note info
CodeAGIで生成したコードが、「jakarta」ではなく「javax」で実装されてしまう問題を回避するためには、以下の2つの方法をお試しください。（100%回避できるわけではありません）
- プロジェクト依存環境のJavaのバージョンを明示的に設定する
    <img width=200 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/c7ad3029-e2a4-4fe6-81fd-e0baca9dfe13.png" alt="プロジェクト依存環境">
- 最新の生成AIモデルを使用してプログラム生成する
:::

ではプロジェクトを実行してみましょう。
STS4の画面左下に位置する「Boot ダッシュボード」から、今回のプロジェクト（purchaseinquiry）を選択して実行してみます。
![スクリーンショット 2025-05-14 104354.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/ec5ed330-0d7f-41d4-8b8b-7e7f3069ee2b.png)

画面下部の「コンソール」に下の画像のような表示が出ていれば、プロジェクトの実行は成功です！
![スクリーンショット 2025-05-14 104909.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/dcfa18c6-10f2-4fca-9f16-201a8c2c7816.png)

:::note info
この時点で、MySQLにテーブルが作成されます。
```console
mysql> show tables;
+------------------------------+
| Tables_in_purchase_info_demo |
+------------------------------+
| mst_item                     |
| mst_item_category            |
| mst_person                   |
| mst_supplier                 |
| purchase_information         |
+------------------------------+
```
:::

## 6. 初期表示
先ほどのステップで、プロジェクトを実行することができました。
このステップでは、ブラウザに「仕入情報照会画面」を表示できるようになるまでの流れを説明します。

### URLの確認
今回生成した「仕入情報照会画面」がどのURLで表示されるのかは、Controller.javaのコードを確認することで判断できます。
```PurchaseInformationController.java
package com.example.controller;

import com.example.form.PurchaseInformationSearchForm;
// ...

/**
 * 仕入情報コントローラークラス
 */
@Controller
@RequestMapping("/purchase")
public class PurchaseInformationController {

    // ...

    /**
     * 初期表示処理
     *
     * @param model モデル
     * @return 仕入情報照会画面
     */
    @GetMapping("/search")
    public String init(Model model) {
        // ...
    }

    /**
     * 検索処理
     *
     * @param form 検索フォーム
     * @param result バインディング結果
     * @param model モデル
     * @return 仕入情報照会画面
     */
    @PostMapping("/search")
    public String search(@Valid PurchaseInformationSearchForm form, BindingResult result, Model model) {
        // ...  
    }
}
```

ファイル内のアノテーション（@~）を確認すると、
- クラスに「`@RequestMapping("/purchase")`」
- 初期表示処理メソッドに「`@GetMapping("/search")`」

となっています。

したがって、今回の初期表示用のURLは、このようになります。
<font color="#3d2">http:\//localhost:8080/purchase/search</font>

### 初期表示までの修正点
どのURLで「仕入情報照会画面」を表示できるのかは確認できました。しかし、今の状態でURLにアクセスしようとすると、エラーが出てしまいます。エラーの原因を修正していきましょう。

「PurchaseInformationController.java」ファイルの内容を確認すると、クラスの各メソッドに「`return "purchase/search";`」というコードがあります。
Controllerクラスのメソッドの戻り値は、HTMLテンプレートへのファイルパスにしないといけません。
生成された「仕入情報照会画面」のHTMLファイルは、templatesフォルダ直下に配置した「purchaseInformationSearch.html」だけです。そのため、このようにコードを書き換えます。
```diff_java
- return "purchase/search";
+ return "purchaseInformationSearch";
```

間違っていた6ヵ所を書き換え、「<font color="#3d2">http:\//localhost:8080/purchase/search</font>」にアクセスしてみます。
![スクリーンショット 2025-05-14 142200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/e2890bf5-7d74-4e91-9f4b-87be50b0ee42.png)


画面を表示することができました！

### Bootstrap CSSの反映
画面を表示することはできましたが、デザインがまるっきり抜けてしまっています。

この原因は、HTMLファイルでインポートしているBootstrap CSSへのパスが間違っているためです。

```purchaseInformationSearch.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>仕入情報照会</title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <script src="/js/action.js"></script>
</head>

<body>
    <!-- ... -->
</body>

</html>
```

今回はBootstrap CSSをローカル環境にダウンロードしていないため、CDN経由でインポートする方法に変更します。

```diff_html
- <link rel="stylesheet" href="/css/bootstrap.min.css">
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
```

変更後、ブラウザをリフレッシュすると、デザインが反映された状態の「仕入情報照会画面」が表示されます。
![スクリーンショット 2025-05-14 142138.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/397d02c9-e22b-4dbc-8e42-79388a78dcde.png)


## 7. 設計書通りに修正
ここからは、「仕入情報照会画面」が設計書通りに動くように、プログラムを修正していきます。

### action.jsの削除
今回のプロジェクトでは、JavaScriptのコードは不要です。そのため、「purchaseInformationSearch.html」から「action.js」へのリンクを削除します。
`<head>`タグ内から`<script>`タグの記載を消してください。

```diff_html
- <script src="/js/action.js"></script>
```

### Formオブジェクトのリンク
HTMLとController間でFormオブジェクトの受け渡しができるように、「PurchaseInformationController.java」の検索処理メソッドを修正します。
メソッド定義を以下のように書き換えます。

```diff_java
- public String search(@Valid PurchaseInformationSearchForm form, BindingResult result, Model model) {
+ public String search(@Valid @ModelAttribute("searchForm") PurchaseInformationSearchForm form, BindingResult result, Model model) {
```

メソッドの引数でFormクラスを指定しているところに、「`@ModelAttribute("searchForm")`」を追加しました。この修正により、画面から検索処理を実行したときに、フォームの入力値をController側に渡すことができます。
※このアノテーションの引数には、HTMLとController間で受け渡しするFormオブジェクトの識別名を定義する必要があります。そのため、ここで定義した名前をHTML側でも使用してください。

### セッションスコープに保存
「PurchaseInformationController.java」の初期表示処理メソッドでは、マスタデータとFormオブジェクトをリクエストスコープに保存しています。このままでは、アプリで画面遷移があったときに、保存していた値が消えてしまいます。
したがって、セッションスコープに保存するようにコードを修正します。
```diff_java
// ...

+ import org.springframework.web.bind.annotation.SessionAttributes;
+ import java.time.LocalDate;
+ import java.time.temporal.TemporalAdjusters;

// ...

/**
 * 仕入情報コントローラークラス
 */
@Controller
+ @SessionAttributes({"suppliers", "persons", "itemCategories", "searchForm"})
@RequestMapping("/purchase")
public class PurchaseInformationController {

    // ...

+   @ModelAttribute("suppliers")
+   public List<MstSupplier> suppliers() {
+   	// 仕入先リストを取得
+   	return mstSupplierService.getAllSuppliers();
+   }
    
+   @ModelAttribute("persons")
+   public List<MstPerson> persons() {
+   	// 担当者リストを取得
+   	return mstPersonService.getAllPersons();
+   }
    
+   @ModelAttribute("itemCategories")
+   public List<MstItemCategory> itemCategories() {
+   	// 商品種別リストを取得
+   	return mstItemCategoryService.getAllItemCategories();
+   }
    
+   @ModelAttribute("searchForm")
+   public PurchaseInformationSearchForm searchForm() {
+   	PurchaseInformationSearchForm searchForm = new PurchaseInformationSearchForm();
+   	// フォームの仕入開始日と仕入終了日に値を自動で入力
+   	LocalDate today = LocalDate.now();
+   	searchForm.setStartDate(today.with(TemporalAdjusters.firstDayOfMonth()));
+   	searchForm.setEndDate(today.with(TemporalAdjusters.lastDayOfMonth()));
+   	return searchForm;
+   }

    /**
     * 初期表示処理
     *
     * @param model モデル
     * @return 仕入情報照会画面
     */
    @GetMapping("/search")
    public String init(Model model) {
-       // 仕入先リストを取得
-       model.addAttribute("suppliers", mstSupplierService.getAllSuppliers());
        
-       // 担当者リストを取得
-       model.addAttribute("persons", mstPersonService.getAllPersons());
        
-       // 商品種別リストを取得
-       model.addAttribute("itemCategories", mstItemCategoryService.getAllItemCategories());
    
-       // フォームの初期化
-       PurchaseInformationSearchForm form = new PurchaseInformationSearchForm();
-       model.addAttribute("searchForm", form);
    
        return "purchaseInformationSearch";
    }
```

この変更に伴って、「purchaseInformationSearch.html」のコードもセッションスコープから値を読み取るように修正します。
```diff_html
<!-- 仕入先 -->
- ${suppliers}"
+ ${session.suppliers}"

<!-- 仕入担当者 -->
- ${persons}"
+ ${session.persons}"

<!-- 商品種別 -->
- ${itemCategories}"
+ ${session.itemCategories}"
```

### 日付型の変換
アプリ全体で使用している日付型が、今では非推奨の「`java.util.Date`」になっています。これらをすべて「`java.time.LocalDate`」に変更していきます。
修正箇所は、以下のファイルです。

- **Entity.java**
    修正ファイル：「MstItemCategory.java」、「MstItem.java」、「MstPerson.java」、「MstSupplier.java」、「PurchaseInformation.java」
    ```diff_java
    // 例）PurchaseInformation.java
    - import java.util.Date;
    + import java.time.LocalDate;

    // ...
    
    /**
     * 更新日時
     */
    @Column(name = "EDIT_DATETIME")
    - @Temporal(TemporalType.TIMESTAMP)
    - private Date editDatetime;
    + private LocalDate editDatetime;
    
    // 他にも2フィールドを修正
    ```
    ※「`java.util.Date`」の廃止に伴って、「`@Temporal`」アノテーションの削除も行っています。
<br></br>
- **Repository.java**
    修正ファイル：「PurchaseInformationRepository.java」
    ```diff_java
    - import java.util.Date;
    + import java.time.LocalDate;

    // ...
    
    List<PurchaseInformation> findByCriteria(
            @Param("supplierCode") String supplierCode,
            @Param("purchasePic") String purchasePic,
    -       @Param("startDate") Date startDate,
    +       @Param("startDate") LocalDate startDate,
    -       @Param("endDate") Date endDate,
    +       @Param("endDate") LocalDate endDate,
            @Param("itemCategory") String itemCategory,
            @Param("itemName") String itemName);
    ```
<br></br>
- **Service.java**
    修正ファイル：「PurchaseInformationService.java」
    ```diff_java
    - import java.util.Date;
    + import java.time.LocalDate;

    // ...
    
    - public List<PurchaseInformation> searchPurchaseInformation(String supplierCode, String purchasePic, Date startDate, Date endDate, String itemCategory, String itemName) {
    + public List<PurchaseInformation> searchPurchaseInformation(String supplierCode, String purchasePic, LocalDate startDate, LocalDate endDate, String itemCategory, String itemName) {
        return purchaseInformationRepository.findByCriteria(supplierCode, purchasePic, startDate, endDate, itemCategory, itemName);
    }
    ```
<br></br>
- **Form.java**
    修正ファイル：「PurchaseInformationSearchForm.java」
    ```diff_java
    - import java.util.Date;
    + import java.time.LocalDate;

    // ...
    
    /**
     * 仕入開始日
     */
    - @DateTimeFormat(pattern = "yyyy/MM/dd")
    + @DateTimeFormat(pattern = "yyyy-MM-dd")
    - private Date startDate;
    + private LocalDate startDate;

    // 仕入終了日のフィールドも同じように修正する
    ```
    ※「`@DateTimeFormat`」アノテーションのコードも修正しました。（HTMLのフォームに入力する日付の形と、Controller側で受け取る日付の形が異なっていたため）

最後に、HTMLのコードも修正する必要があります。
「purchaseInformationSearch.html」ファイルの「`#dates.~`」というコードを「`#temporals.~`」に変更します。

```diff_html
<!-- 仕入日 -->
<div class="form-group">
    <label for="startDate">仕入開始日：</label>
-   <input type="date" id="startDate" name="startDate" class="form-control" th:value="${#dates.format(searchForm.startDate, 'yyyy-MM-dd')}">
+   <input type="date" id="startDate" name="startDate" class="form-control" th:value="${#temporals.format(searchForm.startDate, 'yyyy-MM-dd')}">
</div>

<!-- 他の2ヵ所も修正する -->
```
※「`#dates`」は「`java.util.Date`」などを扱うユーティリティで、「`#temporals`」は「`java.time.LocalDate`」などを扱うユーティリティとなっています。

### HTML側に渡す検索結果（DTO対応）
画面側で表示するデータと、データベースから取得されるデータが異なっています。

| HTML側 | DB取得結果 |
|:-:|:-:|
| 商品コード<br>商品名<br>商品種別<br>仕入先<br>仕入日<br>仕入単価<br>仕入数量<br>仕入金額<br>仕入担当者名<br><br> | 仕入番号<br>仕入日<br>仕入先コード<br>商品コード<br>仕入単価<br>仕入担当者<br>登録者<br>登録日時<br>更新者<br>更新日時 |

この違いをなくすために、今回はDTOクラスを新しく定義します。
具体的には、「`com.example.dto`」パッケージを作成して、そのパッケージに「PurchaseInformationDto.java」ファイルを作成します。

「パッケージ・エクスプローラー」からプロジェクトを右クリックし、「新規(W) > パッケージ」をクリックして、新しいパッケージを作成します。
![スクリーンショット 2025-05-15 103527.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/db28fea4-ff3c-4f0b-bc65-35e0c5104224.png)
「名前(M)」欄に「com.example.dto」と入力し、「完了(F)」をクリックしてください。

次に、新しいDTOクラスを作成します。
「パッケージ・エクスプローラー」から「`com.example.dto`」パッケージを右クリックし、「新規(W) > クラス」をクリックします。
![スクリーンショット 2025-05-15 110839.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/1cdeb492-1683-48cc-b27b-159ab75d2baa.png)
「名前(M)」欄に「PurchaseInformationDto」と入力し、「完了(F)」をクリックしてください。


作成された「PurchaseInformationDto.java」ファイルの内容はこのようにします。

```PurchaseInformationDto.java
package com.example.dto;

import java.time.LocalDate;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PurchaseInformationDto {
	
	/**
     * 商品コード
     */
    private String itemCode;
    
    /**
     * 商品名
     */
    private String itemName;
    
    /**
     * 商品種別
     */
    private String itemCategory;
    
    /**
     * 仕入先
     */
    private String supplierName;

    /**
     * 仕入日
     */
    private LocalDate purchaseDate;

    /**
     * 仕入単価
     */
    private Integer purchaseUnitPrice;

    /**
     * 仕入数量
     */
    private Integer purchaseQuantity;

    /**
     * 仕入担当者名
     */
    private String purchasePic;
}
```
※HTML側で表示するデータ名と、DTO側で定義するフィールド名が一致するようにしてください。

それでは、新しいDTOクラスを使用するように他のファイルも修正します。

- Repository.java
    修正ファイル：「PurchaseInformationRepository.java」
    ```diff_java
    // ...
    
    + import com.example.dto.PurchaseInformationDto;
    
    // ...

    - @Query("SELECT pi FROM PurchaseInformation pi " +
    + @Query("SELECT new com.example.dto.PurchaseInformationDto(" + 
	+   	"mi.itemCode, mi.itemName, mic.itemCategoryName, ms.supplierName, pi.purchaseDate, pi.purchaseUnitPrice, pi.purchaseQuantity, mp.personName) " + 
	+   	"FROM PurchaseInformation pi " +
            "JOIN MstSupplier ms ON pi.supplierCode = ms.supplierCode " +
            "JOIN MstPerson mp ON pi.purchasePic = mp.personCode " +
            "JOIN MstItem mi ON pi.itemCode = mi.itemCode " +
            "JOIN MstItemCategory mic ON mi.itemCategory = mic.itemCategory " +
            "WHERE (:supplierCode IS NULL OR pi.supplierCode = :supplierCode) " +
            "AND (:purchasePic IS NULL OR pi.purchasePic = :purchasePic) " +
            "AND (:startDate IS NULL OR pi.purchaseDate >= :startDate) " +
            "AND (:endDate IS NULL OR pi.purchaseDate <= :endDate) " +
            "AND (:itemCategory IS NULL OR mi.itemCategory = :itemCategory) " +
            "AND (:itemName IS NULL OR mi.itemName LIKE %:itemName%) " +
            "ORDER BY pi.supplierCode, pi.purchaseDate")
    - List<PurchaseInformation> findByCriteria(
    + List<PurchaseInformationDto> findByCriteria(
    // ...
    ```
    ※SQL文のSELECT内もDTO対応に伴って修正しています。
<br></br>
- Service.java
    修正ファイル：「PurchaseInformationService.java」
    ```diff_java
    // ...
    
    - import com.example.entity.PurchaseInformation;
    + import com.example.dto.PurchaseInformationDto;

    // ...
    
    - public List<PurchaseInformation> searchPurchaseInformation(String supplierCode, String purchasePic, LocalDate startDate, LocalDate endDate, String itemCategory, String itemName) {
    + public List<PurchaseInformationDto> searchPurchaseInformation(String supplierCode, String purchasePic, LocalDate startDate, LocalDate endDate, String itemCategory, String itemName) {
        return purchaseInformationRepository.findByCriteria(supplierCode, purchasePic, startDate, endDate, itemCategory, itemName);
    }
    ```
<br></br>
- Controller.java
    修正ファイル：「PurchaseInformationController.java」
    ```diff_java
    // ...
    
    - import com.example.entity.PurchaseInformation;
    + import com.example.dto.PurchaseInformationDto;

    // ...

    // 検索処理
    - List<PurchaseInformation> purchaseInformationList = purchaseInformationService.searchPurchaseInformation(
    + List<PurchaseInformationDto> purchaseInformationList = purchaseInformationService.searchPurchaseInformation(
            form.getSupplierCode(),
            form.getPurchasePic(),
            form.getStartDate(),
            form.getEndDate(),
            form.getItemCategory(),
            form.getItemName()
    );
    
    // ...
    ```

### DB検索用SQLの最終調整
画面側から送られてくる「仕入先」、「仕入担当者」、「商品種別」、「商品名」の値が未選択（空文字）の場合は、データベースの抽出条件から外さないと、空文字のデータを検索するSQL文が発行されてしまいます。
そのため、「PurchaseInformationRepository.java」のSQL文を修正します。
```diff_java
// ...

 /**
 * 検索条件に基づいて仕入情報を取得するクエリ
 * 
 * @param supplierCode 仕入先コード
 * @param purchasePic 仕入担当者
 * @param startDate 仕入開始日
 * @param endDate 仕入終了日
 * @param itemCategory 商品種別
 * @param itemName 商品名
 * @return 仕入情報のリスト
 */
@Query("SELECT new com.example.dto.PurchaseInformationDto(" + 
        "mi.itemCode, mi.itemName, mic.itemCategoryName, ms.supplierName, pi.purchaseDate, pi.purchaseUnitPrice, pi.purchaseQuantity, mp.personName) " + 
        "FROM PurchaseInformation pi " +
        "JOIN MstSupplier ms ON pi.supplierCode = ms.supplierCode " +
        "JOIN MstPerson mp ON pi.purchasePic = mp.personCode " +
        "JOIN MstItem mi ON pi.itemCode = mi.itemCode " +
        "JOIN MstItemCategory mic ON mi.itemCategory = mic.itemCategory " +
-       "WHERE (:supplierCode IS NULL OR pi.supplierCode = :supplierCode) " +
+       "WHERE (:supplierCode IS NULL OR :supplierCode = '' OR pi.supplierCode = :supplierCode) " +
-       "AND (:purchasePic IS NULL OR pi.purchasePic = :purchasePic) " +
+       "AND (:purchasePic IS NULL OR :purchasePic = '' OR pi.purchasePic = :purchasePic) " +
        "AND (:startDate IS NULL OR pi.purchaseDate >= :startDate) " +
        "AND (:endDate IS NULL OR pi.purchaseDate <= :endDate) " +
-       "AND (:itemCategory IS NULL OR mi.itemCategory = :itemCategory) " +
+       "AND (:itemCategory IS NULL OR :itemCategory = '' OR mi.itemCategory = :itemCategory) " +
-       "AND (:itemName IS NULL OR mi.itemName LIKE %:itemName%) " +
+       "AND (:itemName IS NULL OR :itemName = '' OR mi.itemName LIKE %:itemName%) " +
        "ORDER BY pi.supplierCode, pi.purchaseDate")
List<PurchaseInformation> findByCriteria(
// ...
```

### HTMLの最終調整
フォーム内のドロップダウン要素から、「`th:field="項目への参照"`」というコードが抜けているため、画面遷移が発生した場合に入力内容が保持されません。
「purchaseInformationSearch.html」のコードを修正します。
```diff_html
<!-- ... -->

- <select id="supplierCode" name="supplierCode" class="form-control">
+ <select id="supplierCode" name="supplierCode" th:field="*{supplierCode}" class="form-control">
    
<!-- ... -->

- <select id="purchasePic" name="purchasePic" class="form-control">
+ <select id="purchasePic" name="purchasePic" th:field="*{purchasePic}" class="form-control">

<!-- ... -->

- <select id="itemCategory" name="itemCategory" class="form-control">
+ <select id="itemCategory" name="itemCategory" th:field="*{itemCategory}" class="form-control">

<!-- ... -->
```

また、検索結果表示テーブルのフォーマット変換処理コードにも誤りがあるため、そちらも修正します。
```diff_html
<!-- ... -->

- <td th:text="${#numbers.formatInteger(info.purchaseUnitPrice, '###,###,##0')}">仕入単価</td>
+ <td th:text="${#numbers.formatInteger(info.purchaseUnitPrice, 3, 'COMMA')}">仕入単価</td>
- <td th:text="${#numbers.formatInteger(info.purchaseQuantity, '###,###,##0')}">仕入数量</td>
+ <td th:text="${#numbers.formatInteger(info.purchaseQuantity, 3, 'COMMA')}">仕入数量</td>
- <td th:text="${#numbers.formatInteger(info.purchaseUnitPrice * info.purchaseQuantity, '###,###,##0')}">仕入金額</td>
+ <td th:text="${#numbers.formatInteger(info.purchaseUnitPrice * info.purchaseQuantity, 3, 'COMMA')}">仕入金額</td>

<!-- ... -->
```

## 8. 最後に
これですべての修正が終わりました。あとはデータベースの各テーブルにテスト用データを追加すれば、「仕入情報照会画面」が動作します。
![スクリーンショット 2025-05-15 153343.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958419/7a612948-b7f5-40a2-8899-9c313d0cead4.png)


:::note info
CodeAGIを使用すれば、テストデータの生成も簡単にできます。
テストデータ生成についての説明は、本記事では割愛させていただきますが、未来の投稿では詳細に説明させていただきます！
:::

各ファイルの修正後の内容はこちらです。
<details><summary>MstItem.java</summary>

```MstItem.java
// MstItem.java
package com.example.entity;

import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDate;

/**
 * 商品マスタ
 */
@Entity
@Table(name = "MST_ITEM")
@Data
public class MstItem {

    /**
     * 商品コード
     */
    @Id
    @Column(name = "ITEM_CODE", length = 13, nullable = false)
    private String itemCode;

    /**
     * 商品名
     */
    @Column(name = "ITEM_NAME", length = 100)
    private String itemName;

    /**
     * 商品略称
     */
    @Column(name = "ITEM_SHORT_NAME", length = 50)
    private String itemShortName;

    /**
     * 商品種別
     */
    @Column(name = "ITEM_CATEGORY", length = 3)
    private String itemCategory;

    /**
     * 登録者
     */
    @Column(name = "REGIST_PERSON", length = 5)
    private String registPerson;

    /**
     * 登録日時
     */
    @Column(name = "REGIST_DATETIME")
    private LocalDate registDatetime;

    /**
     * 更新者
     */
    @Column(name = "EDIT_PERSON", length = 5)
    private String editPerson;

    /**
     * 更新日時
     */
    @Column(name = "EDIT_DATETIME")
    private LocalDate editDatetime;
}
```
</details>
<details><summary>MstItemCategory.java</summary>

```MstItemCategory.java
// MstItemCategory.java
package com.example.entity;

import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDate;

// 商品種別マスタ
@Entity
@Table(name = "MST_ITEM_CATEGORY")
@Data
public class MstItemCategory {

    // 商品種別
    @Id
    @Column(name = "ITEM_CATEGORY", length = 3, nullable = false)
    private String itemCategory;

    // 商品種別名
    @Column(name = "ITEM_CATEGORY_NAME", length = 50)
    private String itemCategoryName;

    // 登録者
    @Column(name = "REGIST_PERSON", length = 5)
    private String registPerson;

    // 登録日時
    @Column(name = "REGIST_DATETIME")
    private LocalDate registDatetime;

    // 更新者
    @Column(name = "EDIT_PERSON", length = 5)
    private String editPerson;

    // 更新日時
    @Column(name = "EDIT_DATETIME")
    private LocalDate editDatetime;
}
```
</details>
<details><summary>MstPerson.java</summary>

```MstPerson.java
// MstPerson.java
package com.example.entity;

import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDate;

/**
 * 担当者マスタ
 */
@Entity
@Table(name = "MST_PERSON")
@Data
public class MstPerson {

    /**
     * 担当者コード
     */
    @Id
    @Column(name = "PERSON_CODE", length = 5, nullable = false)
    private String personCode;

    /**
     * 担当者名
     */
    @Column(name = "PERSON_NAME", length = 100)
    private String personName;

    /**
     * 担当者部署
     */
    @Column(name = "PERSON_DIVISION", length = 50)
    private String personDivision;

    /**
     * 担当者連絡先
     */
    @Column(name = "PERSON_PHON_NO", length = 15)
    private String personPhonNo;

    /**
     * 登録者
     */
    @Column(name = "REGIST_PERSON", length = 5)
    private String registPerson;

    /**
     * 登録日時
     */
    @Column(name = "REGIST_DATETIME")
    private LocalDate registDatetime;

    /**
     * 更新者
     */
    @Column(name = "EDIT_PERSON", length = 5)
    private String editPerson;

    /**
     * 更新日時
     */
    @Column(name = "EDIT_DATETIME")
    private LocalDate editDatetime;
}
```
</details>
<details><summary>MstSupplier.java</summary>

```MstSupplier.java
// MstSupplier.java
package com.example.entity;

import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDate;

/**
 * 仕入先マスタ
 */
@Entity
@Table(name = "MST_SUPPLIER")
@Data
public class MstSupplier {

    /**
     * 仕入先コード
     */
    @Id
    @Column(name = "SUPPLIER_CODE", length = 4, nullable = false)
    private String supplierCode;

    /**
     * 仕入先名
     */
    @Column(name = "SUPPLIER_NAME", length = 100)
    private String supplierName;

    /**
     * 仕入先略称
     */
    @Column(name = "SUPPLIER_SHORT_NAME", length = 50)
    private String supplierShortName;

    /**
     * 仕入先連絡先
     */
    @Column(name = "SUPPLIER_PHONE_NO", length = 15)
    private String supplierPhoneNo;

    /**
     * 仕入先担当者
     */
    @Column(name = "SUPPLIER_PIC", length = 100)
    private String supplierPic;

    /**
     * 登録者
     */
    @Column(name = "REGIST_PERSON", length = 5)
    private String registPerson;

    /**
     * 登録日時
     */
    @Column(name = "REGIST_DATETIME")
    private LocalDate registDatetime;

    /**
     * 更新者
     */
    @Column(name = "EDIT_PERSON", length = 5)
    private String editPerson;

    /**
     * 更新日時
     */
    @Column(name = "EDIT_DATETIME")
    private LocalDate editDatetime;
}
```
</details>
<details><summary>PurchaseInformation.java</summary>

```PurchaseInformation.java
// PurchaseInformation.java
package com.example.entity;

import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDate;

/**
 * 仕入情報エンティティクラス
 * テーブル名: 仕入情報
 */
@Entity
@Table(name = "PURCHASE_INFORMATION")
@Data
public class PurchaseInformation {

    /**
     * 仕入番号
     */
    @Id
    @Column(name = "PURCHASE_NO", nullable = false, length = 10)
    private Long purchaseNo;

    /**
     * 仕入日
     */
    @Column(name = "PURCHASE_DATE", nullable = false)
    private LocalDate purchaseDate;

    /**
     * 仕入先コード
     */
    @Column(name = "SUPPLIER_CODE", nullable = false, length = 4)
    private String supplierCode;

    /**
     * 商品コード
     */
    @Column(name = "ITEM_CODE", nullable = false, length = 13)
    private String itemCode;

    /**
     * 仕入単価
     */
    @Column(name = "PURCHASE_UNIT_PRICE", nullable = false, length = 7)
    private Integer purchaseUnitPrice;

    /**
     * 仕入数量
     */
    @Column(name = "PURCHASE_QUANTITY", nullable = false, length = 5)
    private Integer purchaseQuantity;

    /**
     * 仕入担当者
     */
    @Column(name = "PURCHASE_PIC", nullable = false, length = 5)
    private String purchasePic;

    /**
     * 登録者
     */
    @Column(name = "REGIST_PERSON", length = 5)
    private String registPerson;

    /**
     * 登録日時
     */
    @Column(name = "REGIST_DATETIME")
    private LocalDate registDatetime;

    /**
     * 更新者
     */
    @Column(name = "EDIT_PERSON", length = 5)
    private String editPerson;

    /**
     * 更新日時
     */
    @Column(name = "EDIT_DATETIME")
    private LocalDate editDatetime;
}
```
</details>
<details><summary>PurchaseInformationRepository.java</summary>

```PurchaseInformationRepository.java
package com.example.repository;

import com.example.dto.PurchaseInformationDto;
import com.example.entity.PurchaseInformation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDate;
import java.util.List;

/**
 * 仕入情報のリポジトリインターフェース
 */
@Repository
public interface PurchaseInformationRepository extends JpaRepository<PurchaseInformation, Long> {

    /**
     * 検索条件に基づいて仕入情報を取得するクエリ
     * 
     * @param supplierCode 仕入先コード
     * @param purchasePic 仕入担当者
     * @param startDate 仕入開始日
     * @param endDate 仕入終了日
     * @param itemCategory 商品種別
     * @param itemName 商品名
     * @return 仕入情報のリスト
     */
	@Query("SELECT new com.example.dto.PurchaseInformationDto(" + 
			"mi.itemCode, mi.itemName, mic.itemCategoryName, ms.supplierName, pi.purchaseDate, pi.purchaseUnitPrice, pi.purchaseQuantity, mp.personName) " + 
			"FROM PurchaseInformation pi " +
            "JOIN MstSupplier ms ON pi.supplierCode = ms.supplierCode " +
            "JOIN MstPerson mp ON pi.purchasePic = mp.personCode " +
            "JOIN MstItem mi ON pi.itemCode = mi.itemCode " +
            "JOIN MstItemCategory mic ON mi.itemCategory = mic.itemCategory " +
            "WHERE (:supplierCode IS NULL OR :supplierCode = '' OR pi.supplierCode = :supplierCode) " +
            "AND (:purchasePic IS NULL OR :purchasePic = '' OR pi.purchasePic = :purchasePic) " +
            "AND (:startDate IS NULL OR pi.purchaseDate >= :startDate) " +
            "AND (:endDate IS NULL OR pi.purchaseDate <= :endDate) " +
            "AND (:itemCategory IS NULL OR :itemCategory = '' OR mi.itemCategory = :itemCategory) " +
            "AND (:itemName IS NULL OR :itemName = '' OR mi.itemName LIKE %:itemName%) " +
            "ORDER BY pi.supplierCode, pi.purchaseDate")
    List<PurchaseInformationDto> findByCriteria(
            @Param("supplierCode") String supplierCode,
            @Param("purchasePic") String purchasePic,
            @Param("startDate") LocalDate startDate,
            @Param("endDate") LocalDate endDate,
            @Param("itemCategory") String itemCategory,
            @Param("itemName") String itemName);
}
```
</details>
<details><summary>PurchaseInformationService.java</summary>

```PurchaseInformationService.java
package com.example.service;

import com.example.dto.PurchaseInformationDto;
import com.example.repository.PurchaseInformationRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;

/**
 * 仕入情報サービスクラス
 */
@Service
public class PurchaseInformationService {

    @Autowired
    private PurchaseInformationRepository purchaseInformationRepository;

    /**
     * 検索条件に基づいて仕入情報を取得する
     *
     * @param supplierCode 仕入先コード
     * @param purchasePic 仕入担当者
     * @param startDate 仕入開始日
     * @param endDate 仕入終了日
     * @param itemCategory 商品種別
     * @param itemName 商品名
     * @return 仕入情報のリスト
     */
    public List<PurchaseInformationDto> searchPurchaseInformation(String supplierCode, String purchasePic, LocalDate startDate, LocalDate endDate, String itemCategory, String itemName) {
        return purchaseInformationRepository.findByCriteria(supplierCode, purchasePic, startDate, endDate, itemCategory, itemName);
    }
}
```
</details>
<details><summary>PurchaseInformationSearchForm.java</summary>

```PurchaseInformationSearchForm.java
package com.example.form;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import jakarta.validation.constraints.Size;
import java.time.LocalDate;

/**
 * 仕入情報検索フォームクラス
 */
@Data
public class PurchaseInformationSearchForm {

    /**
     * 仕入先コード
     */
    private String supplierCode;

    /**
     * 仕入担当者
     */
    private String purchasePic;

    /**
     * 仕入開始日
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;

    /**
     * 仕入終了日
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;

    /**
     * 商品種別
     */
    private String itemCategory;

    /**
     * 商品名
     */
    @Size(max = 50, message = "商品名は最大50文字までです。")
    private String itemName;
}
```
</details>
<details><summary>PurchaseInformationDto.java</summary>

```PurchaseInformationDto.java
package com.example.dto;

import java.time.LocalDate;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PurchaseInformationDto {
	
	/**
     * 商品コード
     */
    private String itemCode;
    
    /**
     * 商品名
     */
    private String itemName;
    
    /**
     * 商品種別
     */
    private String itemCategory;
    
    /**
     * 仕入先
     */
    private String supplierName;

    /**
     * 仕入日
     */
    private LocalDate purchaseDate;

    /**
     * 仕入単価
     */
    private Integer purchaseUnitPrice;

    /**
     * 仕入数量
     */
    private Integer purchaseQuantity;

    /**
     * 仕入担当者名
     */
    private String purchasePic;
}
```
</details>
<details><summary>PurchaseInformationController.java</summary>

```PurchaseInformationController.java
package com.example.controller;

import com.example.form.PurchaseInformationSearchForm;
import com.example.service.MstItemCategoryService;
import com.example.service.MstPersonService;
import com.example.service.MstSupplierService;
import com.example.service.PurchaseInformationService;
import com.example.dto.PurchaseInformationDto;
import com.example.entity.MstItemCategory;
import com.example.entity.MstPerson;
import com.example.entity.MstSupplier;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

import jakarta.validation.Valid;

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.List;

/**
 * 仕入情報コントローラークラス
 */
@Controller
@SessionAttributes({"suppliers", "persons", "itemCategories", "searchForm"})
@RequestMapping("/purchase")
public class PurchaseInformationController {

    @Autowired
    private PurchaseInformationService purchaseInformationService;

    @Autowired
    private MstSupplierService mstSupplierService;

    @Autowired
    private MstPersonService mstPersonService;

    @Autowired
    private MstItemCategoryService mstItemCategoryService;
    
    @ModelAttribute("suppliers")
    public List<MstSupplier> suppliers() {
    	// 仕入先リストを取得
    	return mstSupplierService.getAllSuppliers();
    }
    
    @ModelAttribute("persons")
    public List<MstPerson> persons() {
    	// 担当者リストを取得
    	return mstPersonService.getAllPersons();
    }
    
    @ModelAttribute("itemCategories")
    public List<MstItemCategory> itemCategories() {
    	// 商品種別リストを取得
    	return mstItemCategoryService.getAllItemCategories();
    }
    
    @ModelAttribute("searchForm")
    public PurchaseInformationSearchForm searchForm() {
    	PurchaseInformationSearchForm searchForm = new PurchaseInformationSearchForm();
        // フォームの仕入開始日と仕入終了日に値を自動で入力
    	LocalDate today = LocalDate.now();
    	searchForm.setStartDate(today.with(TemporalAdjusters.firstDayOfMonth()));
    	searchForm.setEndDate(today.with(TemporalAdjusters.lastDayOfMonth()));
    	return searchForm;
    }

    /**
     * 初期表示処理
     *
     * @param model モデル
     * @return 仕入情報照会画面
     */
    @GetMapping("/search")
    public String init(Model model) {
        return "purchaseInformationSearch";
    }

    /**
     * 検索処理
     *
     * @param form 検索フォーム
     * @param result バインディング結果
     * @param model モデル
     * @return 仕入情報照会画面
     */
    @PostMapping("/search")
    public String search(@Valid @ModelAttribute("searchForm") PurchaseInformationSearchForm form, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "purchaseInformationSearch";
        }

        // 検索条件のバリデーション
        if (form.getStartDate() == null && form.getEndDate() == null) {
            result.reject("error.search", "検索条件を指定してください。");
            return "purchaseInformationSearch";
        }

        if (form.getStartDate() != null && form.getEndDate() != null && form.getStartDate().isAfter(form.getEndDate())) {
            result.reject("error.search", "仕入日の検索条件の指定に誤りがあります。");
            return "purchaseInformationSearch";
        }

        // 検索処理
        List<PurchaseInformationDto> purchaseInformationList = purchaseInformationService.searchPurchaseInformation(
                form.getSupplierCode(),
                form.getPurchasePic(),
                form.getStartDate(),
                form.getEndDate(),
                form.getItemCategory(),
                form.getItemName()
        );

        if (purchaseInformationList.isEmpty()) {
            result.reject("error.search", "検索結果が存在しませんでした。");
            return "purchaseInformationSearch";
        }
        

        // 検索結果をモデルに追加
        model.addAttribute("purchaseInformationList", purchaseInformationList);

        return "purchaseInformationSearch";
    }
}
```
</details>
<details><summary>purchaseInformationSearch.html</summary>

```purchaseInformationSearch.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>仕入情報照会</title>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>

<body>
    <div class="container">
        <h1>仕入情報照会</h1>
        <form th:action="@{/purchase/search}" th:object="${searchForm}" method="post">
            <!-- エラーメッセージ表示 -->
            <div th:if="${#fields.hasErrors('*')}" class="alert alert-danger" role="alert">
                <p th:each="err : ${#fields.errors('*')}" th:text="${err}">エラーメッセージ</p>
            </div>

            <!-- 仕入先 -->
            <div class="form-group">
                <label for="supplierCode">仕入先：</label>
                <select id="supplierCode" name="supplierCode" th:field="*{supplierCode}" class="form-control">
                    <option value="">選択してください</option>
                    <option th:each="supplier : ${session.suppliers}" th:value="${supplier.supplierCode}"
                        th:text="${supplier.supplierShortName}"></option>
                </select>
            </div>

            <!-- 仕入担当者 -->
            <div class="form-group">
                <label for="purchasePic">仕入担当者：</label>
                <select id="purchasePic" name="purchasePic" th:field="*{purchasePic}" class="form-control">
                    <option value="">選択してください</option>
                    <option th:each="person : ${session.persons}" th:value="${person.personCode}"
                        th:text="${person.personName}"></option>
                </select>
            </div>

            <!-- 仕入日 -->
            <div class="form-group">
                <label for="startDate">仕入開始日：</label>
                <input type="date" id="startDate" name="startDate" class="form-control"
                    th:value="${#temporals.format(searchForm.startDate, 'yyyy-MM-dd')}">
            </div>
            <div class="form-group">
                <label for="endDate">仕入終了日：</label>
                <input type="date" id="endDate" name="endDate" class="form-control"
                    th:value="${#temporals.format(searchForm.endDate, 'yyyy-MM-dd')}">
            </div>

            <!-- 商品種別 -->
            <div class="form-group">
                <label for="itemCategory">商品種別：</label>
                <select id="itemCategory" name="itemCategory" th:field="*{itemCategory}" class="form-control">
                    <option value="">選択してください</option>
                    <option th:each="category : ${session.itemCategories}" th:value="${category.itemCategory}"
                        th:text="${category.itemCategoryName}"></option>
                </select>
            </div>

            <!-- 商品名 -->
            <div class="form-group">
                <label for="itemName">商品名：</label>
                <input type="text" id="itemName" name="itemName" class="form-control" th:value="${searchForm.itemName}"
                    maxlength="50">
            </div>

            <!-- 検索ボタン -->
            <button type="submit" id="searchButton" class="btn btn-primary">検索</button>
        </form>

        <!-- 検索結果表示 -->
        <div th:if="${purchaseInformationList != null}">
            <h2>検索結果</h2>
            <table class="table table-bordered">
                <thead>
                    <tr>
                        <th>№</th>
                        <th>商品コード</th>
                        <th>商品名</th>
                        <th>商品種別</th>
                        <th>仕入先</th>
                        <th>仕入日</th>
                        <th>仕入単価</th>
                        <th>仕入数量</th>
                        <th>仕入金額</th>
                        <th>仕入担当者名</th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:each="info, iterStat : ${purchaseInformationList}">
                        <td th:text="${iterStat.index + 1}">1</td>
                        <td th:text="${info.itemCode}">商品コード</td>
                        <td th:text="${info.itemName}">商品名</td>
                        <td th:text="${info.itemCategory}">商品種別</td>
                        <td th:text="${info.supplierName}">仕入先</td>
                        <td th:text="${#temporals.format(info.purchaseDate, 'yyyy/MM/dd')}">仕入日</td>
                        <td th:text="${#numbers.formatInteger(info.purchaseUnitPrice, 3, 'COMMA')}">仕入単価</td>
                        <td th:text="${#numbers.formatInteger(info.purchaseQuantity, 3, 'COMMA')}">仕入数量</td>
                        <td th:text="${#numbers.formatInteger(info.purchaseUnitPrice * info.purchaseQuantity, 3, 'COMMA')}">仕入金額</td>
                        <td th:text="${info.purchasePic}">仕入担当者名</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>

</html>
```
</details>
<details><summary>application.properties</summary>

```application.properties
spring.application.name=purchaseinquiry

# --- 1. Database config properties ---
spring.datasource.url=jdbc:mysql://localhost:3306/PURCHASE_INFO_DEMO
spring.datasource.username={ユーザー名}
spring.datasource.password={パスワード}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# -------------------------------------

# --- 2. JPA config ---
spring.jpa.hibernate.ddl-auto=update
# ---------------------
```
</details>

___
本連載「CodeAGIを使って開発を行っていきます。」は、今回が最終回となります。
最後までお読みいただき、ありがとうございました！:raised_hands:
<br>
「CodeAGI」の詳細については、以下のリンクをご覧ください：
👉 https://codeagi.ai/
___


### 連載一覧　CodeAGIを使って開発を行っていきます。
1. **[【連載第1回】CodeAGIで設計書からプログラムまでを自動化する流れを解説します](https://qiita.com/sono1/items/76588de183aaa2a09e3b)**
2. **[【連載第2回】「CodeAGI」の概要](https://qiita.com/to_sprdx/items/e5a065fe01ec588880af)**
3. **[【連載第3回】設計書の準備とCodeAGIへの取り込み方法](https://qiita.com/bellpepper/items/c0f71e8c2274ae290041)**
4. **[【連載第4回】CodeAGIを活用したプログラムの自動生成](https://qiita.com/k_nomo/items/468198abdd4abdf05230)**
5. **[【連載第5回】生成されたプログラムの修正・改良方法](https://qiita.com/sono1/items/cc977ae9b71d645ecd59)**
6. **【連載第6回】最終動作確認と実行**