はじめに
開発未経験からJava→Spring Bootを学び、最初の成果物としてToDoアプリを作りました。
この記事では、実際に手を動かして学んだこと・つまずいた点・理解が深まったポイント・工夫した点をまとめています。
1. 作ったものの概要
Spring Boot + Thymeleafで構成したシンプルなToDoアプリを作成しました。
詳細はGitHubのREADMEを参照ください。

なぜJavaScriptを使わずThymeleafを採用したのか
今回の目的は「Spring BootでWebアプリの基本構造を理解すること」でした。
そのため、フロントエンドの非同期処理(JavaScript)はあえて使わず、テンプレートエンジンにThymeleafを採用しました。
Thymeleafは、Controllerから受け取ったデータをサーバーサイドでHTMLに埋め込む方式であり、Spring MVCの「Model → View」連携をシンプルに理解するのに最適であるためです。
また、学習ロードマップ全体の中での位置づけとしては、以下のように段階的にステップアップしていく方針です。
- フェーズ1:Spring Boot + Thymeleaf(MVC構造理解)
- フェーズ2:Spring Boot + REST API(バックエンド強化)
- フェーズ3:Vue.jsまたはReactによるフロント分離(SPA化)
まずはSpring MVCのリクエスト〜レスポンスの流れを明確に理解することを優先しました。
2. 学んだこと
Controller・Service・Repositoryの分離
「1つの処理を3層に分ける」意味を体感しました。
特に、Controllerではロジックを書かずにServiceへ委譲する構成が自然に理解できました。
Controllerでパラメータを受け取るときに引数及び各種アノテーションの使い方
過去に学んだハンズオンにてある程度アノテーションは覚えましたが、実際に一から作っていくと新たに覚えるアノテーションが非常に多かったです。
特に、アノテーションと組み合わせる引数の種類が多く、最初は混乱しました。
例えば以下のようなハンドラーメソッドのようなパラメータ受け取り用の引数のことです。
@PostMapping("/{id}/update")
public String update(
@PathVariable Long id,
@Valid @ModelAttribute("todoUpdateForm") TodoUpdateForm form,
BindingResult bindingResult,
RedirectAttributes ra,
Model model) {
//以降は処理部分
これらの引数に最初は混乱しました。
まず、各アノテーションが何の機能によるものなのかを調べ、その次にどのような機能を持っているのかを調べ・・・などなど、覚えることが一杯でしたが、何とか理解することができました。
OptionalやEnumなど
OptionalやEnumなど、今までの基礎学習やSpringBootハンズオンで学ばなかった機能を使い、理解することができました。
先日 Java Silver SE17 に合格しましたが、そこでも出なかった内容でしたので、まだ一から学ぶ基礎があるんかい・・・とちょっと頭が痛くなりましたが、なんとか。。
3. ChatGPTと一緒に開発してみて
今回、ChatGPTと一緒に開発を行いました。
ただ、答えを丸々書いてもらって写経するだけでは意味がないので、ヒントやアドバイスのみを私に与えるようにプロンプトを指示しました。
(このプロンプトについては別途Qiitaで記事しようと思っています。)
例えば以下のような感じでまずは方針だけ提示するようにさせました。
「MVP→リファクタリング→新機能追加の流れで作ろう!では、MVPとして、Repository、Controller、Viewを作ってみよう。まずはController内に処理を書いて、ビジネスロジックは後からServiceに分離しよう」
また、実際にコードを書いたあとのレビューもChatGPTにさせましたし、エラー対処の手伝いをしてもらうこともありました。不明点があれば教えてくれるし、学習スピードが加速します。
本当にいい時代だと思います。
Optionalでの葛藤と学び
ただ、質問の回答に疑問を感じる点もありました。
例えば、Service層の処理で悩んだとき、以下のようなコードをChatGPTが提案してくれました。
boolean updated = repo.findById(id) // Optional<Todo>
.map(todo -> { // Optional<Boolean> に変換
todo.setTitle(form.getTitle());
todo.setDueDate(form.getDueDate());
repo.save(todo);
return true; // ← mapは「戻り値」を包む
})
.orElse(false); // Optional<Boolean> を取り出す(空ならfalse)
ただ、上記コードについては個人的にかなり難易度が高く、いろいろ調べたりChatGPTに質問を重ねたりしましたが、結局いまだにちゃんと理解できていません。
Optional単体なら理解はできたし使い方も分かったのですが、上記のコードのような使い方は意味不明でした。
ということで、学習コストが高い時点で本当に実用的なコードなのか懐疑的になり、以下のような質問を投げかけてみました。
「Optionalとmapの組み合わせが難しすぎると感じています。本当に開発現場では一般的なのですか?」
すると、「Optional自体は非常に一般的だけど、mapを多用した関数型っぽい書き方は人・現場による。」という答えが返ってきました。
結局map関数は使用せず、以下のような書き方に変更しました。
Optional<Todo> optionalTodo = repo.findById(id);
if (optionalTodo.isPresent()) {
Todo todo = optionalTodo.get();
todo.setTitle(form.getTitle());
todo.setDueDate(form.getDueDate());
repo.save(todo);
return true;
}
return false;
このように、「なんかこれ変だな?」と感じるときは、ChatGPTと対話を重ねることが大事だと思っています。
4. まとめと今後の展望
今回はSpring BootのMVC構造を理解することを目的として、Thymeleafを用いたサーバーサイドレンダリングでToDoアプリを構築しました。
アプリとしては小規模ですが、Controller・Service・Repositoryの役割を実際に体験できたのは大きな収穫です。
今後はREST API化 → Vue.jsによるSPA化 → Dockerによる環境自動構築、と順に発展させていく予定です。
学習の次フェーズでは「バックエンドとフロントの分離」をテーマに取り組んでいきます。