Spring BootのWebアプリケーションの応用③〜注文フォームの更新と削除~


<a href="https://qiita.com/homes31285/items/2115d1e743ce8e5002f0">前回の記事</a>は、注文フォームにトランザクションと二重サブミット対策とバリデーションを追加をしました。

今回は、注文フォームの更新と削除の機能を追加していきましょう。


<details>
<summary>ファイル構成</summary>
<pre>
<code>
.
├── .gradle
├── .idea
├── build
├── gradle
└── src
     ├── main
     |      ├── java
     |      |      └── com
     |      |           └── example
     |      |                 └── practice 
     |      |                          PracticeApplication.java
     |      |                          ├── web 
     |      |                          |    ├── order
     |      |                          |    |    ├── OrderForm.java
     |      |                          |    |      └── OrderController.java
     |      |                          |    |                
     |      |                          |    └── IndexController.java
     |      |                          └── domain
     |      |                                 └── order 
     |      |                                       └── OrderEntity.java
     |      |                                       └── OrderService.java
     |      |                                       └── OrderRepository.java
     |      |                               
     |      └── resources
     |              ├── static
     |              ├── templates
     |              |        └── order
     |              |                           
     ├──delete_confirmation.html
     |              |                  ├── detail.html 
     |              |                  ├── form.html 
     |              |                  └── list.html
     |              └── index.html
     |              schema.sql
     |              data.sql 
     | 　　　　　　　　　　　　       application.properties
     └── test
.gitignore
build.gradle
gradlew.bat
HELP.md
settings.gradle
</code>
</pre>
</details>


今回作成したい完成形を見ていきましょう。

注文番号をクリックすると、注文リストの詳細が表示されます。

![スクリーンショット 2023-08-21 7.42.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/a3165c5f-ec97-36c7-b2c0-01511fc24c82.png)

注文リストの詳細では、既存のデータを修正または削除できるようにします。

![スクリーンショット 2023-08-21 7.42.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/071c03b0-e6e4-647f-b4c6-649588e91644.png)

削除ボタンをクリックすると、確認画面が表示され、削除ボタンをクリックすると、削除されます。

![スクリーンショット 2023-08-21 7.42.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782718/51a79c37-b4c8-6467-92dd-6a2b31ade158.png)


<h2>Modelの作成</h2>
まずは、OrderRepositoryから追加していきます。

最初に、注文番号をクリックすると、注文リストの詳細が表示されるように、特定の注文番号のIDを取得します。
```java
@Select("select * from ORDERS where order_id = #{orderId}")
OrderEntity findById(long orderId);
```
@Update アノテーションは、指定された SQLクエリを使用してデータベース内のデータを更新するためのメソッドを定義する際に使用し、修正する機能ができます。

@Delete アノテーションは、データベースからデータを削除するためのメソッドを定義する際に使用し、削除する機能ができます。


```Java:OrderRepository
package com.example.practice.domain.order;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Delete;
import java.util.List;
import com.example.practice.web.order.OrderForm;
import org.apache.ibatis.annotations.Options;

@Mapper
public interface OrderRepository {
    @Select("select * from orders")
    List<OrderEntity> findAll();
    @Insert("insert into ORDERS(order_id,order_date,company_no,company_name,item_no,item,quantity,unit_price,price) values (#{orderId}, #{orderDate}, #{companyNo}, #{companyName}, #{itemNo}, #{item}, #{quantity}, #{unitPrice}, #{price})")
    void insert(OrderForm orderForm);
    @Select("SELECT MAX(CAST(order_id AS SIGNED)) FROM ORDERS")
    Integer findMaxOrderId();
    @Select("select * from ORDERS where order_id = #{orderId}")
    OrderEntity findById(long orderId);
    @Update("update ORDERS set order_date = #{orderDate}, company_no = #{companyNo}, company_name = #{companyName}, item_no = #{itemNo}, item = #{item}, quantity = #{quantity}, unit_price = #{unitPrice}, price = #{price} where order_id = #{orderId}")
    void update(OrderForm orderForm);
    @Delete("delete from ORDERS where order_id = #{orderId}")
    void delete(long orderId);
}
```

次に、OrderRepositoryで追加したコードのロジックを追加します。

```Java:OrderService
package com.example.practice.domain.order;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.example.practice.web.order.OrderForm;
import java.util.List;
import org.springframework.ui.Model;
import org.springframework.transaction.annotation.Transactional;
import java.lang.IllegalAccessException;

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository repository;
    @Transactional(readOnly = true)
    public List<OrderEntity> findAll() {
        return repository.findAll();
    }
    @Transactional
    public void create(OrderForm form, Model model) {
        int nextOrderId = getNextOrderId(); // 事前にorderIdを取得するメソッド
        form.setOrderId(nextOrderId); // OrderFormに次のorderIdを設定
        repository.insert(form); // OrderFormをDBに挿入
    }
    // 最大orderId + 1を計算して返すメソッド
    public int getNextOrderId() {
        Integer maxOrderId = repository.findMaxOrderId();
        if (maxOrderId == null) {
            return 1; // 最初の場合、1を返す
        }
        int nextId = maxOrderId + 1;
        System.out.println(nextId);
        return nextId;
    }
    //指定された orderId を使用して、データベースから該当する注文情報を取得するメソッド
    public OrderEntity findById(long orderId) {
        return repository.findById(orderId);
    }
    //渡された OrderForm オブジェクトの情報を使用して、データベース内の注文情報を更新するためメソッド
    @Transactional
    public void update(OrderForm form, Model model) {
        repository.update(form);
    }
    //指定された orderId を使用して、データベース内の注文情報を削除するためメソッド
    @Transactional
    public void delete(long orderId) {
        repository.delete(orderId);
    }
}
```


<h2>Controllerの作成</h2>
注文情報を詳細表示、更新、削除するためのメソッドを作成します。

以下がその内容のコードです。

```java:OrderController
package com.example.practice.web.order;

import com.example.practice.domain.order.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import java.time.LocalDate;
@Controller
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderService orderService;
    @GetMapping
    public String showList(Model model) {
        //orderServiceクラスのfindAllメソッドで取得されたオーダーリストを、orderListでビューに渡す
        model.addAttribute("orderList", orderService.findAll());
        // "order/list"という名前のThymeleafビューを表示する
        return "order/list";
    }
    @GetMapping("/form")
    public String showForm(@ModelAttribute OrderForm form, Model model) {
        int nextOrderId = orderService.getNextOrderId();
        form.setOrderId(nextOrderId);
        form.setOrderDate(LocalDate.now());
        model.addAttribute("nextOrderId", nextOrderId);
        return "order/form";
    }
    @PostMapping
    public String create(@Validated OrderForm form, BindingResult bindingResult, Model model) {
        if (bindingResult.hasErrors()) {
            return showForm(form, model);
        }
        orderService.create(form, model);
        return "redirect:/orders";
    }
    @GetMapping("/{orderId}")
    public String showDetail(@PathVariable("orderId") long orderId, Model model) {
        // 特定の注文の詳細を表示するためのメソッド
        // URLパラメータからorderIdを受け取り、その注文の詳細情報をデータベースから取得してモデルに追加
        model.addAttribute("order", orderService.findById(orderId));
        return "order/detail"; // 注文の詳細を表示するためのビューにリダイレクト
    }

    @PostMapping("/update")
    public String update(@Validated OrderForm form, BindingResult bindingResult, Model model) {
        // 注文情報を更新するためのメソッド
        // フォームデータを受け取り、バリデーションエラーがある場合はフォームを再表示
        if (bindingResult.hasErrors()) {
            return showForm(form, model); // 更新フォームを表示するためのメソッドを呼び出し
        }
        System.out.println("OrderForm:" + form); // フォームデータをコンソールに出力（デバッグ用）
        return "redirect:/orders"; // オーダー一覧画面にリダイレクト
    }

    @GetMapping("/delete/{orderId}")
    public String showDeleteConfirmation(@PathVariable("orderId") long orderId, Model model) {
        // 注文の削除確認画面を表示するためのメソッド
        // URLパラメータからorderIdを受け取り、削除対象の注文の情報をデータベースから取得してモデルに追加
        model.addAttribute("order", orderService.findById(orderId));
        return "order/delete_confirmation"; // 削除確認画面を表示するためのビューにリダイレクト
    }

    @PostMapping("/delete/{orderId}")
    public String deleteOrder(@PathVariable("orderId") long orderId) {
        // 注文を削除するためのメソッド
        orderService.delete(orderId); // 指定されたorderIdに対応する注文をデータベースから削除
        return "redirect:/orders"; // オーダー一覧画面にリダイレクト
    }
}
```


<h2>Viewの作成</h2>

次に、詳細画面と削除詳細画面のViewを作成します。

```html:list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>注文リスト</title>
</head>
<body>
<h1>注文リスト</h1>
<a href="../index.html" th:href="@{/}">トップページ</a>
<a href="./form.html" th:href="@{/orders/form}">作成</a>

<table>
    <tr>
        <th>注文番号</th>
        <th>納入日付</th>
        <th>会社番号</th>
        <th>会社名</th>
        <th>品物番号</th>
        <th>品物</th>
        <th>数量</th>
        <th>単価</th>
        <th>金額</th>
    </tr>
    <!-- orderList の内容をループして表示 -->
    <tr th:each="order : ${orderList}">
        <td>
            <a href="./detail.html" th:href="@{/orders/{orderId}(orderId=${order.orderId})}" th:text="${order.orderId}">
                (orderId)
            </a>
        </td>
        <td th:text="${order.orderDate}"></td>
        <td th:text="${order.companyNo}"></td>
        <td th:text="${order.companyName}"></td>
        <td th:text="${order.itemNo}"></td>
        <td th:text="${order.item}"></td>
        <td th:text="${order.quantity}"></td>
        <td th:text="${order.unitPrice}"></td>
        <td th:text="${order.price}"></td>
    </tr>
</table>
</body>
</html>
```

```html:detail.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>注文リストフォーム</title>
</head>
<body>
<h1>注文リスト詳細</h1>
<form th:object="${order}" th:action="@{/orders/update}" method="post">
    <div>
        <input type="hidden" th:field="*{orderId}"> <!-- orderId を hidden フィールドとして追加 -->
        <label>注文番号</label>
        <span th:text="${order.orderId}"></span>
    </div>
    <div>
        <label for="orderDate">納入日付</label>
        <input type="date" id="orderDate" th:field="*{orderDate}">
        <p th:if="${#fields.hasErrors('orderDate')}" th:errors="*{orderDate}">(error)</p>
    </div>
    <div>
        <label for="companyNo">会社番号</label>
        <input type="number" id="companyNo" th:field="*{companyNo}">
        <p th:if="${#fields.hasErrors('companyNo')}" th:errors="*{companyNo}">(error)</p>
    </div>
    <div>
        <label for="companyName">会社名</label>
        <input type="text" id="companyName" th:field="*{companyName}">
        <p th:if="${#fields.hasErrors('companyName')}" th:errors="*{companyName}">(error)</p>
    </div>
    <div>
        <label for="itemNo">品物番号</label>
        <input type="number" id="itemNo" th:field="*{itemNo}">
        <p th:if="${#fields.hasErrors('itemNo')}" th:errors="*{itemNo}">(error)</p>
    </div>
    <div>
        <label for="item">品物</label>
        <input type="text" id="item" th:field="*{item}">
        <p th:if="${#fields.hasErrors('item')}" th:errors="*{item}">(error)</p>
    </div>
    <div>
        <label for="quantity">数量</label>
        <input type="number" id="quantity" th:field="*{quantity}">
        <p th:if="${#fields.hasErrors('quantity')}" th:errors="*{quantity}">(error)</p>
    </div>
    <div>
        <label for="unitPrice">単価</label>
        <input type="number" id="unitPrice" th:field="*{unitPrice}">
        <p th:if="${#fields.hasErrors('unitPrice')}" th:errors="*{unitPrice}">(error)</p>
    </div>
    <div>
        <label for="price">金額</label>
        <input type="number" id="price" th:field="*{price}">
        <p th:if="${#fields.hasErrors('price')}" th:errors="*{price}">(error)</p>
    </div>
    <button type="submit">更新</button>
    <a th:href="@{/orders/delete/{orderId}(orderId=${order.orderId})}">削除</a>
    <a href="./order/list.html" th:href="@{/orders}">取消</a>
</form>
</body>
</html>
```

```html:delete_confirm
<!-- delete_confirmation.html -->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>注文リスト削除確認</title>
</head>
<body>
<h1>注文リスト削除確認</h1>
<p>以下の注文を削除しますか？</p>
<p>注文番号: <span th:text="${order.orderId}"></span></p>
<p>納入日付: <span th:text="${order.orderDate}"></span></p>
<p>会社番号: <span th:text="${order.companyNo}"></span></p>
<p>品物番号: <span th:text="${order.itemNo}"></span></p>
<p>品物: <span th:text="${order.item}"></span></p>
<p>数量: <span th:text="${order.quantity}"></span></p>
<p>単価: <span th:text="${order.unitPrice}"></span></p>
<p>金額: <span th:text="${order.price}"></span></p>
<!-- 削除処理を実行するURLを指定 -->
<form th:action="@{/orders/delete/{orderId}(orderId=${order.orderId})}" method="post">
  <button type="submit">削除</button>
  <a th:href="@{/orders}">キャンセル</a>
</form>
</body>
</html>
```