0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Spring入門】第9章 社員削除

0
Last updated at Posted at 2026-09-30

社員一覧の各行に「削除」ボタンを付けて、押した社員をデータベースから削除できるようにします。データを「変える」操作を作るのは、この章が初めてです。

この章で学ぶこと

  • データを変える操作を POST で送る理由
  • @PostMapping と @PathVariable で、URL に含まれる社員IDを受け取る方法
  • Repository の deleteById で削除する方法と、トランザクション
  • 処理のあとに redirect: で一覧画面へ戻す方法(PRG パターン)

9.1 この章で作るもの

第8章で作った社員一覧画面に「操作」列を追加し、各行に「削除」ボタンを置きます。ボタンを押すと、その行の社員を削除して、一覧画面を表示し直します。

ch09-list-delete-button.png
図9-1 各行に「削除」ボタンを付けた社員一覧

この章で変更するファイルは、次の2つです。

ファイル 変更内容
EmployeeListController.java 削除の処理を行う delete メソッドを追加する
templates/employee/list.html 「操作」列と「削除」ボタンを追加する

9.2 Controller に削除の処理を追加する

  1. パッケージ・エクスプローラーで src/main/java の com.example.employeeapp.controller を開き、EmployeeListController.java をダブルクリックして開きます。
  2. 次のように、色の付いた行を追加します。
src/main/java/com/example/employeeapp/controller/EmployeeListController.java
package com.example.employeeapp.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.repository.EmployeeRepository;

@Controller
public class EmployeeListController {

    @Autowired
    private EmployeeRepository employeeRepository;

    /** 社員一覧画面を表示
     * 社員IDの順に社員を取り出して表示
     * @param model 表示する社員情報
     * @return 社員一覧画面 */
    @GetMapping("/employees")
    public String list(Model model) {
        List<Employee> employees = employeeRepository.findAllByOrderByEmpIdAsc();
        model.addAttribute("employees", employees);
        return "employee/list";
    }

    /** 社員を削除して社員一覧画面に戻る
     * @param empId 削除する社員の社員ID
     * @return 社員一覧画面 */
    @PostMapping("/employees/{empId}/delete")
    public String delete(@PathVariable Integer empId) {
        employeeRepository.deleteById(empId);
        return "redirect:/employees";
    }
}
  1. Ctrl + S で保存します。@PostMapping や @PathVariable は補完(Ctrl + Space)で入力すると、import 文も自動で追加されます(第4章「入力は補完で行う」)。

delete メソッドは、たった3行です。「URL から社員IDを受け取る」「その社員を削除する」「一覧画面へ移動させる」の3つを行っています。詳しくは 9.5 解説 で説明します。

9.3 一覧画面に削除ボタンを追加する

  1. src/main/resources/templates/employee/list.html を開きます。
  2. 見出し行の最後に「操作」列を、社員の行の最後に削除ボタンを追加します。
src/main/resources/templates/employee/list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員一覧</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
  <h1>社員一覧</h1>

  <table>
    <tr>
      <th>社員ID</th>
      <th>氏名</th>
      <th>性別</th>
      <th>年齢</th>
      <th>生年月日</th>
      <th>部署</th>
      <th>住所</th>
      <th>操作</th>
    </tr>
    <tr th:each="employee : ${employees}">
      <td th:text="${employee.empId}"></td>
      <td th:text="${employee.name}"></td>
      <td th:switch="${employee.gender}">
        <span th:case="1">男</span>
        <span th:case="2">女</span>
      </td>
      <td th:text="${employee.age}"></td>
      <td th:text="${#dates.format(employee.birthDate, 'yyyy/MM/dd')}"></td>
      <td th:text="${employee.department.deptName}"></td>
      <td th:text="${employee.address}"></td>
      <td>
        <form class="inline" th:action="@{/employees/{empId}/delete(empId=${employee.empId})}" method="post">
          <button type="submit">削除</button>
        </form>
      </td>
    </tr>
  </table>
</body>
</html>
  1. Ctrl + S で保存します。

【知識メモ】class="inline" について

<form> は、通常はそれだけで1行を使って表示されます。第8章で作った app.css には form.inline { display: inline; } という指定があり、class="inline" を付けたフォームは、文字と同じように横に並んで表示されます。第12章で「変更」ボタンを追加したとき、「変更」と「削除」を横に並べるために使います。

9.4 動作確認

  1. アプリを起動している場合は、コンソールの赤い四角(終了)ボタンで一度停止してから、プロジェクトを右クリック → 実行 → Spring Boot アプリケーション で起動し直します(Boot ダッシュボードの再起動ボタンでもかまいません)。停止している場合は、そのまま起動します。保存しただけでは、動いているアプリには反映されません。
  2. Chrome で http://localhost:8080/employees を開きます。各行の右端に「削除」ボタンが表示されていることを確認します(図9-1)。
  3. 林檎 星飛(社員ID 3)の行の「削除」ボタンを押します。
  4. 一覧画面が表示し直され、林檎 星飛の行がなくなっていれば成功です。

ch09-list-after-delete.png
図9-2 林檎 星飛(社員ID 3)を削除したあとの社員一覧

SQL Developer で empapp に接続し、SELECT * FROM EMPLOYEE ORDER BY EMP_ID; を実行すると、データベースからも社員ID 3 の行が消えていることを確認できます。

【ご注意】削除したデータを元に戻したいとき

削除したデータを元に戻すボタンはありません。サンプルデータを最初の状態に戻したいときは、第3章「やり直したいとき」 の手順でテーブルを作り直してください。

なお、この教材の以降の画面は、社員ID 3(林檎 星飛)を削除した状態 で進みます。第11章で新しく登録する社員の社員IDは 4 になります(一度使われた番号は、削除しても再利用されません)。

9.5 解説

なぜ POST で送るのか

第1章で、「データを見るだけなら GET、データを変えるなら POST」と説明しました。削除はデータを変える操作なので、POST で送ります。

もし削除を GET で作ると、リンクをクリックしたり URL を開いたりするだけで削除が実行されてしまいます。ブラウザの履歴やブックマークから URL を開いただけで社員が消えてしまう、といった事故も起こり得ます。POST は、フォームのボタンを押したときに送られるので、このような「うっかり実行」を防げます。

HTML でボタンから POST を送るには、<form method="post"> の中に <button type="submit"> を置きます。

@PostMapping

@GetMapping が GET のリクエストを受け取るのに対して、@PostMapping は POST のリクエストを受け取る メソッドに付けます。

アノテーション 受け取るリクエスト この章での例
@GetMapping("/employees") GET /employees 一覧画面の表示
@PostMapping("/employees/{empId}/delete") POST /employees/3/delete など 社員の削除

URL に社員IDを埋め込む

どの社員を削除するかをサーバーに伝えるため、この章では URL の中に社員IDを入れて 送っています。社員ID 3 の削除ボタンは、/employees/3/delete に POST します。

この URL を作っているのが、list.html の次の部分です。

th:action="@{/employees/{empId}/delete(empId=${employee.empId})}"
書き方 意味
@{ … } Thymeleaf で URL を作るときの書き方
{empId} あとから値を入れる場所(名前は empId)
(empId=${employee.empId}) {empId} の場所に、その行の社員の社員IDを入れる

実際に作られた HTML は、Chrome で確認できます。一覧画面の何もないところで右クリック → ページのソースを表示 を選ぶと、次のように行ごとに違う URL になっていることがわかります。

ページのソース(一部)
<form class="inline" action="/employees/1/delete" method="post">
  <button type="submit">削除</button>
</form>
…
<form class="inline" action="/employees/3/delete" method="post">
  <button type="submit">削除</button>
</form>

@PathVariable:URL から値を取り出す

Controller 側では、@PostMapping("/employees/{empId}/delete") の {empId} の部分に入っている値を、@PathVariable を付けた引数で受け取ります。

@PathVariable は、引数の名前(empId)から、URL のどの部分の値を受け取るかを決めます。そのため、@PathVariable("empId") のように名前を書く必要はありません。

@PostMapping("/employees/{empId}/delete")
public String delete(@PathVariable Integer empId) {

/employees/3/delete に POST されると、引数 empId に 3 が入ります。URL の中は文字ですが、引数の型を Integer にしておけば、Spring が数値に変換してくれます。

【ご注意】名前をそろえる

画面の {empId}、@PostMapping の {empId}、@PathVariable を付けた引数の名前 empId の3か所は、同じ名前にそろえます。どれか1つでも綴りが違うと、正しく受け取れません。

deleteById:削除する

deleteById は、第7章で紹介した JpaRepository に最初から用意されているメソッドです。引数に渡した ID の行を削除します。

employeeRepository.deleteById(empId);

実行されると、コンソールに Hibernate が発行した SQL が表示されます。削除の前に、対象の社員を検索する SELECT も実行されています。

コンソール(一部を省略)
Hibernate:
    select
        e1_0.emp_id,
        …
    from
        employee e1_0
    left join
        department d1_0
            on d1_0.dept_id=e1_0.dept_id
    where
        e1_0.emp_id=?
Hibernate:
    delete
    from
        employee
    where
        emp_id=?

トランザクションと COMMIT

第3章では、INSERT のあとに COMMIT を実行しないと、登録した内容が確定しないことを学びました。では、deleteById のあとに COMMIT は必要でしょうか。

答えは「自分で書く必要はない」です。deleteById や、第11章で使う save などの Repository のメソッドは、Spring Data JPA の中で 自動的にトランザクションが開始され、処理が終わると COMMIT されます。そのため、この教材のように Repository のメソッドを1回呼ぶだけの処理では、トランザクションを意識しなくても正しく動きます。

【知識メモ】トランザクションとは

複数の処理を「すべて成功」か「すべて取り消し」のどちらかにまとめる仕組みです。業務システムで「複数のテーブルをまとめて更新する」といった処理を作るときは、@Transactional というアノテーションで範囲を指定します。この教材では使いませんが、名前だけ覚えておきましょう。

redirect: と PRG パターン

delete メソッドは、テンプレート名ではなく "redirect:/employees" を返しています。先頭に redirect: を付けると、Spring は画面を返す代わりに、ブラウザに「/employees を開き直してください」と伝えます。これを リダイレクト といいます。第1章で紹介したステータスコード 302 は、このときに使われます。

ch09-1.png

図9-3 削除からリダイレクトまでの流れ

削除のあと、一覧のテンプレート名("employee/list")を直接返してもよさそうに見えます。しかしそうすると、ブラウザは「POST /employees/3/delete の結果」として一覧画面を表示することになります。この状態で利用者がブラウザの 再読み込み(F5)を押すと、同じ POST がもう一度送信されてしまいます。

リダイレクトしておけば、ブラウザが最後に送ったリクエストは GET /employees になります。再読み込みしても一覧を表示し直すだけで、POST が再送されることはありません。

このように、POST で処理したら Redirect し、GET で画面を表示する作り方を、頭文字をとって PRG パターン といいます。データを変える処理のあとは、PRG パターンにするのが基本です。第11章の新規登録、第12章の変更でも使います。

9.6 よくあるエラー

症状 主な原因 対処
「削除」を押すと 405(Method Not Allowed)になる <form> に method="post" を書き忘れて、GET で送られている list.html の削除フォームに method="post" があるか確認する
「削除」を押すと 404 になる th:action の URL の書き方が違う(/delete の付け忘れなど) 「ページのソースを表示」で action="/employees/3/delete" のようになっているか確認する
「削除」を押すと 400(Bad Request)になる th:action の {empId} と (empId=…) の名前が一致していない。{empId} が社員IDに置き換わらず、action="/employees/{empId}/delete?empid=3" のようになる {empId} と (empId=…) の綴りをそろえる。送られた URL は付録D の方法でも確かめられる
「削除」を押すと 500 になり、コンソールに「Required URI template variable 'id'」などと出る @PostMapping の {empId} と、@PathVariable を付けた引数の名前が一致していない 3か所の名前を empId にそろえる
削除はされるが、Whitelabel エラー画面(500)になる "redirect:/employees" の綴りが違う(redirect の打ち間違い、: の抜けなど) 戻り値を "redirect:/employees" に直す

【知識メモ】外部キーのエラーは起きない?

第3章で、社員テーブルから部署テーブルへの外部キー制約を作りました。部署を削除しようとすると、その部署に所属する社員がいる場合にエラー(ORA-02292)になります。しかし社員は他のテーブルから参照されていないので、社員の削除ではこのエラーは起きません。

9.7 まとめと確認問題

まとめ

  • データを変える操作は、<form method="post"> を使って POST で送る
  • @PostMapping を付けたメソッドで POST のリクエストを受け取る
  • @{/employees/{empId}/delete(empId=${employee.empId})} のように書くと、URL に社員IDを埋め込める。Controller では @PathVariable で受け取る
  • deleteById で削除できる。Repository のメソッドは自動的にトランザクションの中で実行され、COMMIT される
  • 処理のあとは "redirect:/employees" でリダイレクトする(PRG パターン)。再読み込みで POST が再送されるのを防げる

確認問題

問1 削除を GET ではなく POST で作るのはなぜですか。

解答を見る

GET で作ると、リンクのクリックや URL を開くだけで削除が実行されてしまい、履歴やブックマークからの「うっかり実行」が起こり得るためです。データを変える操作は POST で送るのが基本です。

問2 社員ID 2 の行の削除ボタンは、どの URL に POST しますか。

解答を見る

/employees/2/delete です。@{/employees/{empId}/delete(empId=${employee.empId})} の {empId} の場所に、その行の社員ID 2 が入ります。

問3 @PathVariable Integer empId は何をしていますか。

解答を見る

@PostMapping("/employees/{empId}/delete") の {empId} の部分に入っている値を取り出し、Integer に変換して引数 empId に入れています。どの部分の値を取り出すかは、引数の名前 empId で決まります。

問4 deleteById のあとに COMMIT を実行するコードを書いていないのに、削除が確定するのはなぜですか。

解答を見る

Repository のメソッドは、Spring Data JPA の中で自動的にトランザクションが開始され、処理が終わると COMMIT されるためです。

問5 削除のあとに "employee/list" を返すのではなく、"redirect:/employees" を返すのはなぜですか。

解答を見る

テンプレートを直接返すと、ブラウザの再読み込みで削除の POST がもう一度送信されてしまうためです。リダイレクトすると最後のリクエストが GET /employees になり、再読み込みしても一覧を表示し直すだけになります(PRG パターン)。

前へ

第8章 社員一覧画面

次へ

第10章 社員新規登録① 入力画面とセッション

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?