社員管理アプリが完成しました。この章では、作ったアプリと学んだことを振り返り、これから現場で出会う技術を紹介します。
この章で学ぶこと
- 完成したアプリの機能と、それを支える技術の整理
- アプリ全体の構成と、1つのリクエストが処理される流れ
- これまでに作った「事故を防ぐ仕組み」の整理
- この教材で学んだ Spring の用語
- 次に学ぶとよい技術(Service 層、検索、テスト、ログイン機能、REST API)
15.1 完成したアプリの振り返り
第1章で「完成するとできるようになること」として挙げた機能は、すべて動くようになりました。どの機能を、どの章で、どんな技術を使って作ったかを整理しましょう。
| 機能 | 章 | 主な技術 |
|---|---|---|
| 社員一覧 | 第8章 |
findAllByOrderByEmpIdAsc、Model、Thymeleaf の th:each・th:text、@ManyToOne による部署名の表示 |
| 社員削除 | 第9章 | POST で送るフォーム、@PostMapping、@PathVariable、deleteById、リダイレクトと PRG パターン |
| 社員新規登録 | 第10章・第11章 | フォームクラス、th:object・th:field、@SessionAttributes、確認画面と「戻る」、save(INSERT)、setComplete()
|
| 社員変更 | 第12章 |
findByEmpId → フォームへの詰め替え、save(UPDATE) |
| 入力チェック | 第13章・第14章 | Bean Validation(@NotBlank・@NotNull など)、@Validated・BindingResult、messages.properties のエラーメッセージ |
その土台として、第2章〜第7章では開発環境、データベース、Spring Boot のプロジェクト、DI、JPA を準備しました。一つひとつは小さな部品ですが、組み合わせることで「データベースとつながる Web アプリケーション」ができあがっています。
15.2 アプリ全体の構成
完成したアプリの部品と、そのつながりを1枚の図にまとめます。
| 部品 | 役割 |
|---|---|
| Controller | URL ごとの処理を書く。一覧・削除、新規登録、変更の3つに分けた |
| Repository | データベースの検索・登録・変更・削除を担当する。インターフェースを書くだけで、中身は Spring が用意する |
| Entity | テーブルの1行分のデータを表すクラス |
| フォーム | 画面の入力内容を受け取るクラス。入力チェックの決まりもここに書く |
| セッション | 入力画面・確認画面・完了画面の間で、入力内容を覚えておく場所 |
| テンプレート | Thymeleaf で書いた画面の HTML |
完成版のファイル一覧
完成したプロジェクトのファイル構成は次のとおりです。すべてのファイルの内容は 付録A 完成版ソースコード に掲載しています。
src/main/java
└─ com.example.employeeapp
├─ EmployeeAppApplication.java
├─ component
│ └─ GreetingMessage.java (第5章の練習用)
├─ controller
│ ├─ HelloController.java (第4・5章の練習用)
│ ├─ EmployeeListController.java
│ ├─ EmployeeRegisterController.java
│ └─ EmployeeUpdateController.java
├─ entity
│ ├─ Department.java
│ └─ Employee.java
├─ form
│ ├─ RegisterForm.java
│ └─ UpdateForm.java
└─ repository
├─ DepartmentRepository.java
└─ EmployeeRepository.java
src/main/resources
├─ application.properties
├─ messages.properties
├─ static
│ └─ css
│ └─ app.css
└─ templates
├─ hello.html (第4章の練習用)
└─ employee
├─ list.html
├─ register
│ ├─ input.html
│ ├─ confirm.html
│ └─ complete.html
└─ update
├─ input.html
├─ confirm.html
└─ complete.html
src/test/java
└─ com.example.employeeapp
└─ RepositoryCheckTest.java (第7章の確認用)
15.3 1つのリクエストの流れのおさらい
学んだことが実際にどうつながっているかを、1つの操作で追いかけてみましょう。例として、変更画面で内容を入力して「確認」ボタンを押したとき の流れです。
-
ブラウザがリクエストを送る
Chrome はPOST /employees/1/edit/confirmというリクエストを、入力内容と一緒に送ります(第1章)。Cookie のJSESSIONIDも一緒に送られるので、サーバーはどのブラウザからのリクエストかがわかります(第10章)。 -
URL に合うメソッドが選ばれる
Spring が@PostMapping("/employees/{empId}/edit/confirm")の付いたEmployeeUpdateControllerのconfirmメソッドを探し出します(第4章・第12章)。 -
セッションからフォームを取り出す
@SessionAttributes("updateForm")によって、セッションに保存しておいた変更用のフォームが取り出されます(第10章・第12章)。 -
入力内容をフォームに詰める
送られてきた氏名や年齢がフォームに詰められます。 -
入力チェックを行う
@Validatedによってフォームのアノテーションの決まりがチェックされ、結果がBindingResultに入ります(第13章・第14章)。 -
Controller の処理を実行する
入力チェックでエラーがあれば変更画面、なければ部署を検索して確認画面を選びます。部署の検索は DI で受け取ったDepartmentRepositoryが行い、JPA が SQL を実行します(第5章〜第7章)。 -
テンプレートで HTML を作る
Controller が返した"employee/update/confirm"に対応するテンプレートが選ばれ、Thymeleaf が Model とフォームのデータを埋め込んで HTML を作ります(第8章)。 -
レスポンスが返る
できあがった HTML がブラウザに返され、確認画面が表示されます。最後に、フォームは再びセッションに保存されます。
【便利な使い方】わからない部分があったら
流れの中でよくわからない部分があれば、カッコ内の章に戻って読み直してみましょう。一度全体を作り終えたあとに読み直すと、最初に読んだときより理解が深まるはずです。
15.4 事故を防ぐ仕組みの整理
第9章から第14章までで、社員管理アプリにはいくつかの「事故を防ぐ仕組み」が入りました。どの問題を、どの仕組みで防いでいるかを整理しておきましょう。
| 起こりうる問題 | 防いでいる仕組み | 章 |
|---|---|---|
| リンクを開いただけで、社員が削除される | データを変える操作は POST で送る(<form method="post"> と @PostMapping) |
第9章 |
| 完了画面の再読み込みで、二重に登録・変更・削除される | PRG パターン(処理のあと redirect: でリダイレクトする) |
第9・11・12章 |
| 必須の項目が空のまま、形式の正しくない値のまま登録・変更される | 入力チェック(@Validated と BindingResult、messages.properties) |
第13・14章 |
| 入力チェックは通ったのに、データベースへの登録でエラーになる | 入力チェックの文字数の上限を、テーブルの列の長さに合わせる | 第13章 |
15.5 学んだ用語のまとめ
この教材に登場した主な用語です。より詳しい一覧は 付録C 用語集 にまとめています。
| 用語 | 意味 | 章 |
|---|---|---|
| DI | 必要な部品(オブジェクト)を自分で new せず、Spring から受け取る仕組み | 第5章 |
| Bean | Spring が作って管理しているオブジェクト | 第5章 |
@Controller |
リクエストを受け取るクラスであることを表す | 第4章 |
@GetMapping / @PostMapping
|
どの URL・どのメソッド(GET / POST)のリクエストを受け取るかを決める | 第4章・第9章 |
Model |
Controller から画面へデータを渡す入れ物 | 第4章 |
| Thymeleaf | HTML にデータを埋め込むためのテンプレートエンジン | 第4章・第8章 |
| JPA | Java のオブジェクトとテーブルを対応づける仕組み | 第6章 |
| Entity | テーブルの1行分を表すクラス(@Entity) |
第6章 |
| Repository | データベースを操作するためのインターフェース | 第7章 |
@PathVariable |
URL の一部(社員ID など)を引数で受け取る | 第9章 |
| PRG パターン | POST のあとはリダイレクトして GET で画面を表示し、二重送信を防ぐ書き方 | 第9章・第11章 |
| セッション | リクエストをまたいでデータを覚えておく、サーバー側の保管場所 | 第10章 |
@SessionAttributes |
指定した名前のデータを自動でセッションに保存・取り出しする | 第10章 |
| Bean Validation | アノテーションで入力チェックの決まりを書く仕組み | 第13章 |
BindingResult |
入力チェックの結果(エラーの有無と内容)が入る入れ物 | 第13章 |
15.6 次のステップ
この教材で作ったアプリは、Web アプリケーションの基本がひととおり詰まったものです。実際の業務システムでは、さらに次のような技術が使われています。ここでは名前と考え方だけを紹介します。
Service 層(3層構成)
この教材では、Controller が Repository を直接使う2層構成で作りました。現場の多くのシステムでは、Controller と Repository の間に、業務の処理をまとめた Service を挟む3層構成にします。
| 層 | 役割 |
|---|---|
| Controller | リクエストを受け取り、処理の流れを決める司令塔 |
| Service | 業務の処理(登録の手順、計算、複数テーブルの更新など)を行う |
| Repository | データベースを操作する |
たとえば新規登録の処理を Service に移すと、次のようになります。
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private DepartmentRepository departmentRepository;
// 社員を登録する
@Transactional
public void register(RegisterForm form) {
Employee employee = new Employee();
employee.setName(form.getName());
// ……(ほかの項目も同じように詰め替える)
employee.setDepartment(departmentRepository.findByDeptId(form.getDeptId()));
employeeRepository.save(employee);
}
}
/** 社員登録の完了画面を表示
* @param registerForm 入力された社員情報
* @param sessionStatus セッション破棄時に利用
* @return 社員登録の完了画面 */
@PostMapping("/employees/register/complete")
public String complete(@ModelAttribute RegisterForm registerForm,
SessionStatus sessionStatus) {
employeeService.register(registerForm); // 登録は Service に任せる
sessionStatus.setComplete();
return "redirect:/employees/register/done";
}
Controller は「受け付けて、Service に頼んで、画面を選ぶ」だけになり、見通しがよくなります。同じ登録処理を別の画面から使いたくなったときも、Service を呼ぶだけで済みます。
また、Service のメソッドに付けた @Transactional は、「このメソッドの中のデータベース操作を、まとめて1つのトランザクションにする」という意味です。たとえば「社員を登録し、同時に部署の人数を更新する」のように複数のテーブルを更新するとき、途中で失敗したらすべてを取り消し(ロールバック)、全部成功したときだけ COMMIT できます。
検索とページング
社員が何百人にもなると、一覧をすべて表示するのは現実的ではありません。条件で絞り込む 検索 と、何件かずつに分けて表示する ページング が必要になります。
- 検索:第7章のクエリメソッドのルールで、たとえば
findByNameContaining(String name)(氏名に指定の文字を含む社員を探す)のようなメソッドを宣言するだけで作れます。 - ページング:Spring Data JPA には、
Pageableという「何ページ目を何件ずつ」を表す仕組みが用意されています。
テスト
第7章では JUnit で Repository の動作を確かめました。現場では、プログラムを変更するたびに自動でテストを実行し、壊れていないことを確かめます。
- JUnit:Java のテストを書くための標準的な仕組み
- MockMvc:ブラウザを使わずに、Controller にリクエストを送って結果を確かめる Spring の仕組み。「確認ボタンを押したら確認画面が返るか」「入力エラーのときは入力画面に戻るか」などを自動で確認できます
ログイン機能
業務システムでは、ログインした人だけが使える、社員の削除は管理者だけができる、といった制御が必要です。Spring には、ログイン(認証)と権限の管理(認可)を行う Spring Security という仕組みがあります。セキュリティは奥が深い分野なので、公式ドキュメントや専用の資料でしっかり学びましょう。
REST API
この教材の Controller は HTML を返しました。スマートフォンのアプリや、JavaScript で作った画面からデータを使いたい場合は、HTML ではなく JSON という形式でデータを返す REST API を作ります。Spring では、@Controller の代わりに @RestController を付けたクラスで作ることができます。
15.7 現場での心構え
| 心構え | 内容 |
|---|---|
| 公式ドキュメントを読む | Spring には、詳しい公式ドキュメント(英語)があります。インターネットの記事は古いバージョンの情報であることも多いので、最終的には公式ドキュメントで確かめる習慣をつけましょう |
| エラーメッセージを最後まで読む | エラーの原因は、たいていメッセージの中に書いてあります。長いメッセージでも慌てず、「Caused by」の行や ORA- で始まる番号を探しましょう |
| よくあるエラーを調べる | 付録B よくあるエラーと対処法 に、この教材で出会いやすいエラーをまとめています |
| チームの決まりに従う | 現場には、クラスの名前の付け方やパッケージの分け方などのコーディング規約があります。この教材の書き方と違っていても、チームの決まりを優先しましょう |
| わからないことは早めに相談する | 一人で長時間悩むより、状況(何をしたら、どんなエラーが出たか)を整理して周りの人に相談するほうが、早く解決できます |
15.8 おわりに
ここまで、おつかれさまでした。開発環境の準備から始めて、データベースとつながり、一覧・削除・登録・変更・入力チェックを備えた Web アプリケーションを完成させました。
最初は「アノテーションを1つ付けるだけで、なぜ動くのか」と不思議に感じたかもしれません。その裏側では、Spring が決まりきった処理を引き受けてくれていました。仕組みを知ったうえでフレームワークを使えば、新しい機能を作るときにも、エラーに出会ったときにも、落ち着いて対処できるはずです。
このアプリを土台に、検索機能を付けたり、Service 層に書き直したりと、自分で手を加えてみてください。作ってみることが、いちばんの上達の近道です。
15.9 総合確認問題
問1 社員の削除を GET ではなく POST で行うのはなぜですか。
解答を見る
削除はデータを変える操作だからです。GET はリンクをたどったりページを再読み込みしたりするだけで送られてしまうため、意図せず削除が実行されるおそれがあります。データを変える操作は POST で行います(第1章・第9章)。
問2 Controller で new EmployeeRepository() と書かずに Repository を使えるのはなぜですか。
解答を見る
DI の仕組みにより、Spring が Repository の Bean を作り、Controller の @Autowired を付けたフィールドに代入してくれるからです(第5章・第7章)。
問3 新規登録の完了後、完了画面を直接返さずに redirect:/employees/register/done とするのはなぜですか。
解答を見る
完了画面でページを再読み込みしても、POST(登録)が再送されず GET で完了画面を表示し直すだけになり、二重登録を防げるからです(PRG パターン。第9章・第11章)。
問4 同じ save メソッドで、新規登録では INSERT、変更では UPDATE が実行されるのはなぜですか。
解答を見る
主キー(社員ID)の値で判断しているからです。新規登録では社員ID が null のため INSERT になり、シーケンスから取り出した番号が社員ID になります。変更では社員ID が入った Entity を渡すため UPDATE になります(第7章・第11章・第12章)。
問5 入力チェックのエラーメッセージを、Java のコードではなく messages.properties に書くと、どんな良いことがありますか。
解答を見る
メッセージの文言を変えたいときに、Java のコードを直さずに properties ファイルだけを直せばよくなります。また、新規登録と変更のようにフォームを継承している画面で、同じメッセージをそのまま使い回せます(第13章・第14章)。

