いよいよ社員管理アプリの画面を作ります。第7章で作った Repository を使ってデータベースから社員を取り出し、Thymeleaf で一覧表として表示します。
この章で学ぶこと
- Controller に Repository を注入して、データベースのデータを画面に渡す方法
- Thymeleaf の
th:eachで、リストの件数分だけ行を繰り返す方法 - 画面に表示しやすい形にデータを変換する方法(性別・日付・部署名)
- CSS などのファイルを置く
staticフォルダーの役割
8.1 この章で作るもの
この章が終わると、Chrome で http://localhost:8080/employees を開いたときに、次のような社員一覧が表示されるようになります。表示されているのは、第3章でデータベースに登録した3人の社員です。
「新規登録」「変更」「削除」のボタンは、第9章以降で順番に追加していきます。この章では、一覧を表示するところまでを作ります。
この章で作成・変更するファイルは次のとおりです。
| ファイル | 作業 | 内容 |
|---|---|---|
controller/EmployeeListController.java |
新規 | 社員一覧画面を表示する Controller |
static/css/app.css |
新規 | 画面の見た目を整える CSS |
templates/employee/list.html |
新規 | 社員一覧画面のテンプレート |
8.2 EmployeeListController を作る
次に、社員一覧画面を表示する Controller を作ります。
-
src/main/java→com.example.employeeapp.controllerパッケージ(第4章で作成)を右クリックし、新規 → クラス を選びます。 - 「名前」に
EmployeeListControllerと入力し、完了 をクリックします。 - エディターが開くので、次のように入力して保存します。
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 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";
}
}
第5章で予告したとおり、@Autowired で EmployeeRepository を受け取る 形になっています。new を書いていないのに employeeRepository が使えるのは、Spring が第7章で作った EmployeeRepository の実体(Bean)を注入してくれるからです。
【再確認】クラス名は補完で入力する
List や Employee などのクラス名、@GetMapping などのアノテーションは、途中まで打って Ctrl + Space で補完すると、import 文も自動で追加されます(第4章「入力は補完で行う」)。List のように同じ名前のクラスが複数ある場合は、List - java.util を選んでください。
8.3 CSS ファイルを作る
表に罫線を付けるなど、画面の見た目を整えるための CSS ファイルを作ります。CSS ファイルは src/main/resources/static フォルダーの中に置きます。
-
src/main/resources→staticフォルダーを右クリックし、新規 → フォルダー を選びます。 - 「フォルダー名」に
cssと入力し、完了 をクリックします。 - 作成した
cssフォルダーを右クリックし、新規 → ファイル を選びます。 - 「ファイル名」に
app.cssと入力し、完了 をクリックします。 - エディターが開くので、次のように入力して保存します。
body { /* ページ全体 */
margin: 24px;
color: #222;
}
h1 { /* 見出し */
font-size: 24px;
}
table { /* 表 */
border-collapse: collapse;
margin-bottom: 16px;
}
th, td { /* 表のマス */
border: 1px solid #999;
padding: 6px 12px;
text-align: left;
}
th { /* 表の見出しのマス */
background-color: #eee;
}
a.button { /* リンクをボタンの見た目にする(新規登録・変更・戻るボタン用) */
display: inline-block;
padding: 1px 6px;
border: 1px solid #767676;
border-radius: 3px;
background-color: #efefef;
color: #000;
font-size: 13px;
text-decoration: none;
}
form.inline { /* フォームを横に並べる(削除ボタン用) */
display: inline;
}
.error { /* 入力チェックのエラーメッセージ */
color: #c00;
font-size: 14px;
}
最後の a.button、form.inline、.error は、この章ではまだ使いません。a.button はリンクをボタンの見た目にするため(第10章以降)、form.inline はボタンを横に並べるため(第9章以降)、.error は入力チェックのエラーメッセージを赤く表示するため(第13章以降)に使います。
static フォルダーの役割
static フォルダーに置いたファイルは、Spring Boot が そのまま ブラウザに送ります。CSS や画像、JavaScript のファイルなど、内容が変わらないファイルを置く場所です。
URL は、static フォルダーの中の場所がそのまま使われます。今回作ったファイルは、http://localhost:8080/css/app.css という URL でアクセスできます。
| フォルダー | 置くもの | ブラウザへの送られ方 |
|---|---|---|
templates |
Thymeleaf のテンプレート(HTML) | Controller から呼ばれ、データが埋め込まれてから送られる |
static |
CSS、画像、JavaScript など | ファイルの中身がそのまま送られる |
8.4 一覧画面のテンプレートを作る
Controller の list メソッドは、"employee/list" を返しています。これは templates/employee/list.html を使う、という意味でした(第4章)。まず employee フォルダーを作り、その中にテンプレートを作ります。
-
src/main/resources→templatesフォルダーを右クリックし、新規 → フォルダー を選びます。 - 「フォルダー名」に
employeeと入力し、完了 をクリックします。 - 作成した
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>
</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>
</tr>
</table>
</body>
</html>
【ご注意】フォルダーの場所に注意
list.html は、templates の 中の employee フォルダーに作ってください。templates の直下に作ると、Controller が返す "employee/list" と場所が合わず、エラーになります。
8.5 動作確認
- アプリを起動している場合は、コンソールの赤い四角(終了)ボタンで一度停止してから、プロジェクトを右クリック → 実行 → Spring Boot アプリケーション で起動し直します(Boot ダッシュボードの再起動ボタンでもかまいません)。停止している場合は、そのまま起動します。保存しただけでは、動いているアプリには反映されません。
- コンソールに
Started EmployeeAppApplicationと表示されたら、Chrome でhttp://localhost:8080/employeesを開きます。
第3章で登録した3人の社員が、社員IDの順に表示されれば成功です。性別が「男」「女」、生年月日が「2000/06/22」の形式、部署が部署名で表示されていることを確認してください。
【便利な使い方】データベースと見比べてみよう
SQL Developer で第3章の結合 SELECT を実行し、画面に表示された内容と同じであることを確かめてみましょう。アプリはデータベースの内容をそのまま表示しているので、SQL Developer でデータを変更して COMMIT すると、Chrome で画面を再読み込みしたときに一覧にも反映されます。
8.6 解説
Controller の処理
list メソッドがしていることは、次の3つだけです。
-
employeeRepository.findAllByOrderByEmpIdAsc()で、全社員を社員IDの順に取り出す(第7章で作ったメソッド) -
model.addAttribute("employees", employees)で、取り出した社員のリストにemployeesという名前を付けて Model に入れる -
"employee/list"を返して、templates/employee/list.htmlで画面を作るよう指示する
データベースからデータを取り出す処理は Repository に、画面の見た目はテンプレートに任せ、Controller は「何を取り出して、どの画面に渡すか」だけを決めています。第1章で学んだ MVC の役割分担のとおりです。
Thymeleaf の書き方
| 書き方 | 意味 |
|---|---|
th:each="employee : ${employees}" |
Model の employees(社員のリスト)から1人ずつ取り出して employee という名前を付け、その要素(ここでは <tr>)を人数分繰り返す。Java の拡張 for 文 for (Employee employee : employees) と同じ考え方 |
th:text="${employee.name}" |
タグの中身を、employee の name の値に置き換える |
th:switch="${employee.gender}"th:case="1"
|
th:switch に書いた値(性別)と同じ th:case の要素だけを表示する。1 なら「男」、2 なら「女」が表示される |
${employee.department.deptName} |
社員が所属する部署の部署名 |
${#dates.format(employee.birthDate, 'yyyy/MM/dd')} |
日付(Date)を「2000/06/22」の形式の文字に変換する |
th:href="@{/css/app.css}" |
アプリの中の URL を作る。@{…} は URL を表す書き方 |
${employee.name} はゲッターを呼んでいる
${employee.name} と書くと、Thymeleaf は employee.getName() を呼び出して値を取り出します。フィールドを直接見ているのではなく、「get + 先頭を大文字にした名前」のメソッド を呼んでいます。
| テンプレートの書き方 | 呼び出されるメソッド |
|---|---|
${employee.empId} |
employee.getEmpId() |
${employee.gender} |
employee.getGender() |
${employee.department.deptName} |
employee.getDepartment().getDeptName() |
部署名が取れる仕組み
社員テーブルには部署IDしか入っていませんが、画面には部署名が表示されています。これは第6章で、Employee クラスに @ManyToOne を付けた Department 型のフィールドを持たせたからです。
JPA は社員を取り出すときに、部署IDをもとに部署のデータも一緒に取り出し、employee.getDepartment() で使えるようにしてくれます。第3章で SQL Developer から実行した「社員テーブルと部署テーブルの結合」と同じことを、JPA が自動で行っているのです。
8.7 コンソールで SQL を確認する
第4章で application.properties に spring.jpa.show-sql=true を設定したので、JPA が実行した SQL がコンソールに表示されます。Chrome で一覧画面を開いたあと、Eclipse のコンソールを見てみましょう。
Hibernate:
select
e1_0.emp_id,
e1_0.address,
e1_0.age,
e1_0.birth_date,
e1_0.dept_id,
e1_0.gender,
e1_0.name
from
employee e1_0
order by
e1_0.emp_id
Hibernate:
select
d1_0.dept_id,
d1_0.dept_name
from
department d1_0
where
d1_0.dept_id=?
(部署を取り出す select が、社員が所属する部署の数だけ続く)
まず社員テーブル(employee)を社員IDの順に取り出し、続いて部署テーブル(department)から、社員が所属する部署を1件ずつ取り出していることがわかります。表示される SQL の細かい形は、バージョンなどによって変わることがあります。
【知識メモ】SQL を書かなくても SQL は動いている
私たちは SQL を1行も書いていませんが、裏側ではこのように SQL が実行されています。うまく動かないときは、コンソールの SQL を見ると原因がわかることがあります。
【ご注意】現場では
この教材のデータは数件なので問題になりませんが、上のように「社員を取り出したあと、部署を1件ずつ取り出す」動きは、データ量が多い実際のシステムでは SQL の回数が増え、性能に大きく影響することがあります。現場では、どんな SQL が何回実行されているかを確認し、必要に応じてチームで取り出し方を検討するようにしましょう。
8.8 よくあるエラー
| 症状 | 原因 | 対処 |
|---|---|---|
500 エラーになり、コンソールに TemplateInputException と表示される |
Controller が返すテンプレート名と、テンプレートの場所・ファイル名が合っていない |
return "employee/list"; と、templates/employee/list.html の場所・つづりを確認する |
| 表の見出しだけが表示され、社員が1人も表示されない |
${employees} のつづりが、Controller の model.addAttribute("employees", …) の名前と違う |
Controller とテンプレートで同じ名前になっているか確認する |
500 エラーになり、Property or field 'xxx' cannot be found と表示される |
テンプレートに書いた名前に対応するゲッターがない(例:${employee.gendername} と小文字で書いた) |
名前のつづりと大文字・小文字を、Entity のゲッター名と見比べる |
| 表に罫線が付かない(CSS が効いていない) |
app.css の場所が違う、または th:href のパスが違う |
src/main/resources/static/css/app.css にあるか、th:href="@{/css/app.css}" と書いたかを確認する。Chrome で http://localhost:8080/css/app.css を開いて中身が表示されるかも確かめる |
| 404 エラー(Whitelabel Error Page)になる | URL の打ち間違い、または @GetMapping の URL の間違い |
Chrome の URL が /employees になっているか、@GetMapping("/employees") と一致しているか確認する |
8.9 まとめと確認問題
まとめ
- Controller は
@Autowiredで Repository を受け取り、取り出したデータを Model に入れてテンプレートに渡す -
th:eachを使うと、リストの件数分だけ HTML の要素を繰り返せる -
${employee.name}は、ゲッターgetName()を呼び出して値を取り出す - 性別の 1・2 は、
th:switchとth:caseで「男」「女」に切り替えて表示する -
@ManyToOneのおかげで、${employee.department.deptName}と書くだけで部署名を表示できる - 日付は
#dates.formatで表示用の形式に変換できる - CSS や画像は
staticフォルダーに置くと、そのままブラウザに送られる
確認問題
問1 Controller の list メソッドが "employee/list" を返すと、どのファイルが画面の作成に使われますか。
解答を見る
src/main/resources/templates/employee/list.html です。templates/ と拡張子 .html は自動で補われます。
問2 th:each="employee : ${employees}" は、Java のどんな書き方に近いですか。
解答を見る
拡張 for 文 for (Employee employee : employees) { … } に近い書き方です。リストから1人ずつ取り出して、要素を人数分繰り返します。
問3 社員テーブルの性別には 1・2 の数値が入っています。一覧画面で「男」「女」と表示するために、テンプレートで何を使いましたか。
解答を見る
th:switch と th:case です。th:switch="${employee.gender}" の値が 1 なら th:case="1" の「男」が、2 なら th:case="2" の「女」が表示されます。
問4 社員テーブルには部署IDしか入っていないのに、一覧画面に部署名を表示できるのはなぜですか。
解答を見る
Employee クラスが @ManyToOne で Department 型のフィールドを持っているため、JPA が部署IDをもとに部署のデータも取り出してくれるからです。テンプレートでは ${employee.department.deptName} と書くだけで部署名を取り出せます。
問5 src/main/resources/static/css/app.css に置いたファイルには、どの URL でアクセスできますか。
解答を見る
http://localhost:8080/css/app.css です。static フォルダーの中の場所が、そのまま URL になります。


