ページ遷移

ページ遷移について

この単元では、ページ遷移の作り方についてまとめます。
ページ遷移とは、WEBサイト上でユーザーがページ上のリンクボタンをクリックして、別のページに移動することです。

静的なページと動的なページと言う区分がありますが、サーバーサイド言語が加わることによって作れるのが動的なページです。
(例)静的なサイト→ コンパスのHP(いつ誰が見ても常に同じ内容が表示される)
(例)動的なサイト→ Amazon(人や時によって表示内容が変わる)
参考サイト:今さら聞けない「静的サイト」と「動的サイト」の仕組みとは?

GET通信とPOST通信のおさらい

GET通信とPOST通信のアクセスの仕方の違いを知ることは重要です。
Spring Bootの基本的な処理の流れ」でも触れた内容ですが、改めて確認しましょう。

GET通信 ~サーバからデータを受け取る~

GET通信は、基本的にWEBページを表示する際に使用します。(サーバからデータを取得)
アクセス方法は3パターンあります。
 ・URL(アドレスバー)に直接アドレスを入力する
 ・<a>タグによるアクセス
 ・タグでmethod=”GET”に設定している場合のフォーム送信
1つずつ確認しましょう。

URL(アドレスバー)に直接アドレスを入力する

まず、動きを確認するためのページを作成します。

【/src/main/resources/templates/employee/transition.html】

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>ページ遷移</title>
	<style>
		.block {
			border: 1px dotted #CCC;
			padding: 0 10px;
			max-width: 800px;
		}
	</style>
</head>
<body>
	<h1>ページ遷移の学習ページ</h1>
</body>
</html>

上記のViewを画面出力するメソッドを追記します。
【/com/cmps/spring/controller/EmployeeController.java】に追記

	/**
	 * 【ページ遷移】初期表示ページ
	 * @return String Viewファイル
	 */
	@GetMapping("/trans-index")
	public String transIndex() {
		
		return "employee/transition";
	}

ブラウザのアドレスバーに「localhost:8080/emp/trans-index」を直接入力してアクセスできます。

<a>タグによるアクセス

【/src/main/resources/templates/employee/transition.html】のbody閉じタグ前に追記

	<h2>GET通信</h2>
	<div class="block">
		<h3>aタグによる遷移</h3>
		<a href="/emp/all">employeesテーブル一覧</a>
	</div>

ページを更新し、リンクをクリックすると”/emp/all”へのページの遷移が確認できるでしょう。
なお、この場合のhref属性の書き方は「ルート相対パス」です。(「ファイル管理」を参照)

<form>タグでmethod=”GET”に設定している場合のフォーム送信

【/src/main/resources/templates/employee/transition.html】のbody閉じタグ前に追記

	<div class="block">
		<h3>formタグ(method="GET")でのフォーム送信による遷移</h3>
		<form method="get" action="/emp/find">
			<input type="text" name="name" value="GET通信">
			<input type="submit" value="findページへ遷移">
		</form>
	</div>

submitボタンをクリックすると、”/emp/find”に設定したページに遷移するでしょう。
ただし、アドレスバーは下記の画像のように表示されていると思います。

このように、GET通信でのフォーム送信ではURLの後ろにパラメータが付与されます(クエリパラメータ)。
今回の場合はフォーム部品に入れた <input type="text" name="name" value="GET通信"> です。


以上のように、GET通信には3パターンのアクセス方法があります。
ページから別のページへの遷移としては、aタグとformタグを使用していくことになります。

POST通信 ~サーバへデータを送信~

POST通信は、基本的にWEBサーバーへデータを送信して処理を行う際に使用します。

<form>タグでmethod=”POST”に設定している場合のフォーム送信

【/src/main/resources/templates/employee/transition.html】のbody閉じタグ前に追記

	<h2>POST通信</h2>
	<div class="block">
		<h3>formタグ(method="POST")でのフォーム送信による遷移</h3>
		<form method="post" th:action="@{/emp/post-sample}">
			<input type="text" name="text" value="POST通信">
			<input type="submit" value="送信">
		</form>
	</div>

このPOST通信の送信先のメソッドを作成します。
EmployeeControllerの上部に、以下のimport文 も追加してください。
import org.springframework.web.bind.annotation.PostMapping;
【/com/cmps/spring/controller/EmployeeController.java】に追記

	/**
	 * 【ページ遷移】POST通信のサンプル
	 * @param text String
	 * @return String リダイレクト
	 */
	@PostMapping("/post-sample")
	public String postSample(@RequestParam String text) {
		System.out.println("POST通信です。");
		System.out.println("入力値:「" + text + "」");
		
		return "redirect:/emp/trans-index";
	}

今回は簡単に、
パラメータを受け取ってコンソールに出力し、その後 初期表示ページにリダイレクト(=GET送信)する流れとしました。
同じページにリダイレクトしているのでページ上では変化が見られないと思いますが、コンソールには文字列が出力されているはずです。

・POST通信の場合、 <form method=”post” action=”/emp/post-sample”> のようにaction属性に直接記載するとエラーになります。
これは、Spring側の仕様で、POST通信の場合はCSRF対策が必須になっているためです。
thymeleafでは、th:action=”@{/emp/post-sample}” とリンク式の形で書けばCSRF対策が有効になりエラーになりません。
CSRFについては以下を参考
安全なウェブサイトの作り方 – 1.6 CSRF(クロスサイト・リクエスト・フォージェリ) | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構


POST通信となるのはこの1パターンであること、
およびPOST通信はサーバー処理を行うメソッドのため、処理の完了後はリダイレクトでGET通信のページへ遷移するのが基本的な流れであることを押さえておきましょう。

POST通信+フラッシュメッセージの追加

先ほどの例では、リダイレクトしているためページとしては変化が見られない状態でした。

リダイレクト後にのみ表示できるメッセージを紹介します。
特に、次の「CRUD」では実際に簡単なアプリを作っていきますが
このメッセージを表示することで、処理が問題なく通っているかどうかを確認できるので作業上も役立ちます。
参考サイト:バックエンドフレームワークにおける画面間のデータ引継ぎ(フラッシュスコープの適用方針整理) – Qiita

追記していきます。
【/com/cmps/spring/controller/EmployeeController.java】に追記
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.bind.annotation.ModelAttribute;

	/**
	 * 【ページ遷移】初期表示ページ
	 * @return String Viewファイル
	 */
	@GetMapping("/trans-index")
	public String transIndex(@ModelAttribute("successMessage") String successMessage) {////引数を追記
		
		return "employee/transition";
	}

	/**
	 * 【ページ遷移】POST通信のサンプル
	 * @param text String
	 * @return String リダイレクト
	 */
	@PostMapping("/post-sample")
	public String postSample(@RequestParam String text, RedirectAttributes redirectAttributes) {////引数を追記
		System.out.println("POST通信です。");
		System.out.println("入力値:「" + text + "」");

		////追記
		// フラッシュメッセージをredirectAttributesに登録
		redirectAttributes.addFlashAttribute("successMessage", "処理が完了しました。");
		
		return "redirect:/emp/trans-index";
	}

【/src/main/resources/templates/employee/transition.html】のbody閉じタグ前に追記

	<div th:if="${successMessage}"><!-- 完了メッセージ -->
		<p th:text="${successMessage}"></p>
	</div>

もう一度POST通信を実行していただくと、リダイレクト時にメッセージが表示されるのが確認できるでしょう。
今後はこの機能もうまく活用していきましょう。

パスパラメータ

パスパラメータ

パスパラメータとは、クエリパラメータとは異なり、パス(URL)内に含まれるパラメータのことです。

具体的には下図のような例です。
クリックしたリンクに応じて、URLにidの数値が付与され、遷移後のページの内容もidに応じたものになっています。このidがパスパラメータにあたります。
また、右側の表示は1つのメソッドで実行できます。これが冒頭に説明した動的な表示です。


具体的なコードで見ていきましょう。
こちらは全件表示ページGetMapping(“/emp/all”)で使用しているViewファイルです。
【src/main/resources/templates/employee/list.html】の<tbody>内のidを表示する<td>を編集

<tbody>
	<tr th:if="${employees.size > 0}" th:each="employee : ${employees}" th:object="${employee}">
		<td><!--ここから-->
			<a th:href="@{/emp/findone/{id}(id=*{id})}" th:text="*{id}"></a>
		</td><!--ここまで編集-->
		<td th:text="*{code}"></td>

・th:href=”@{/emp/findone/{id}(id=*{id})}”
このリンク式の記述により、{id}の箇所を変数のように扱い、後続の()内でidに入る値を指定します。
{id}の箇所には、*{id}に応じた1,2,3..といったidが入っていき、各行ごとに
/emp/findone/1 、 /emp/findone/2 、 /emp/findone/3 といったURLが生成されます。

なお、ここではaタグのhref属性に記述していますが、formタグのaction属性ももちろん同様に記述できます。

【/com/cmps/spring/controller/EmployeeController.java】に追記
import org.springframework.web.bind.annotation.PathVariable;

	/**
	 * employeesテーブルからid1のデータを取得しブラウザに表示する
	 * 
	 * @param model Model
	 * @return "employee/index" String viewファイル
	 */
	@GetMapping("/findone/{id}")
	public String findOne(Model model, @PathVariable Integer id) {

		Employee employee = employeeService.findById(id);
		//Serviceのメソッドは各自の環境に合わせて変えてください(IDからEntityが取得できればOK)

		model.addAttribute("employee", employee);
		return "employee/index";
	}

・こちらでも、View側と似た記述で、@GetMapping内に{id}と記述しています。
このように書くことで「{id}には任意の値が入る」ことを表現でき、
先ほどの/emp/findone/1 、 /emp/findone/2 、 /emp/findone/3 といったURLがこのマッピングに該当することになります。

・さらに、引数に@PathVariableアノテーション@PathVariable Integer id と記述することで、パスパラメータidを受け取ってメソッド内で使用することが出来ます。
パスパラメータの注意点として、基本的には String型として受け取られます。
Integerに変換可能な1,2といった数値がパスパラメータとして渡される場合は上記の記述で問題なく処理が動きますが、Integerに変換できない値でアクセスしようとすると型変換に関する例外が発生します。
メソッド内で例外を補足したい場合には、String型で受け取ってメソッド内でtry-catch文内で型変換することも手段の1つです。

このように、「受け取ったidに応じたEntityを取得し、Viewに渡す」ことで動的なページを生成します。
今回のように、パスパラメータとして値を持ち回り(View→Controller)、その値を元にテーブルに対して処理を行う(ここではデータを取得)場合、主キーを使用するのが一般的です。
なぜなら、主キーはテーブルに対して一意にデータを定めることができるものだからです。

パスパラメータ・マッピングのその他の書き方

2つのパスパラメータを渡す場合

View側
<a th:href="@{/emp/findone/{id}/{otherId}(id=*{id}, otherId=*{code})}" th:text="*{id}"></a>
()は1つにまとめ、()内を「,」(カンマ)で区切ってパラメータの値を指定します。

Controller側
@GetMapping("/findone/{id}/{otherId}")
public String findOne(Model model, @PathVariable String id, @PathVariable String code) {
Mapping内の記述(変数名)は、View側と一致させます。
引数に@PathVariableにより2個目のパスパラメータも同様に受け取ります。

複数のパスを1つのメソッドに集約する場合

Controller側
@GetMapping({"/findone", "/findone/{id}"})
public String findOne(Model model, @PathVariable(required = false) String id) {
~Mapping内には複数のパスを指定することが出来ます。上記のように{}で囲み、パス同士を「,」(カンマ)で区切ります。
この場合の受け付けるURLとしては、/emp/findone 、 /emp/findone/1 、 /emp/findone/2 、 といったものになります。
「パスパラメータがなくても良い」ということになるので、この場合は@PathVariableに(required = false)を記載することが必須になります。

FormとEntityとDTOの使い分け

FormとEntityとDTOの違い

これら3つは使用目的が異なります。

  • Form  …入力フォームからデータの受け取り
  • Entity …DBへ受け渡し時に使用(登録時、取得時)
  • DTO  …画面表示など処理ごとに最適化したデータの容れ物


Webアプリケーションに置き換えて説明すると、
<ユーザー入力値をDBに登録する>(下図ではクライアント→DBまでの右方向の流れ)
①ユーザーがフォーム入力したデータをControllerで受け取る 【Form】
②ユーザーの入力したデータを元にDBに登録する 【Entity】
この場合、①から②の間にEntityオブジェクトを生成し、Formに受け取った値をEntityに移し替える操作が必要になります。

<DBのデータを取得しブラウザに表示する>(下図ではDB→クライアントまでの左方向の流れ)
①DBから情報を取得する【Entity】
②画面表示用に整形し、Viewに渡す【DTO】
こちらも①で受け取るのはEntityですが、その全情報をViewに渡す必要はなく(必要最低限の情報をViewに渡す方がより望ましい)、
他にも、DBから取り出したままではなくデータの加工が必要な場合には加工を兼ねて、EntityからDTOへの変換(値の詰め直し)を行います。

参考サイト:Spring Bootにおける DTO, Form, Entityの違い

各クラスごとに使い分けることで、必要最低限かつ最適化したデータの受け渡しが行えますので、上記のような形が最適な流れです。

学習内での使い分け

初学者の段階で、各クラス作成しすべて使い分けるのは高い障壁に感じることでしょう。
ですので、基本的には下記の(A)パターンで実装しましょう。今後のマニュアルでも(A)パターンを中心に採用します。

(A)DTOは考えず、FormとEntityで作成する。
上図で言うと、DTOの存在は忘れて、画面表示時はFormをViewに渡します。
「Spring Data JPA」の単元で画面表示したのと同じ要領で作成する形です。

(A)パターンが難しい場合は、次の(B)パターンでも構いません。
(B)Entityのみで全ての処理を作成する。
Formクラスの実装内容はEntityに付与することも可能です。つまり、Formクラスの役割をEntityに担わせます。

挑戦できる場合は(C)パターンとして、前述の説明通り、
(C)DTOを含めた3種を使い分け
してもいいでしょう。

Form→Entityへの変換例

例えば、「Spring Data JPA」で扱ったEmployeeで考えましょう。
主要なカラムとしては以下の4カラムがあり、
 private Integer id;// ID(自動採番)
 private String code;// 従業員コード
 private String name;// 名前
 private Integer age;// 年齢
他に 登録日時、更新日時の2カラムがあります。

例として、Employee(employeesテーブル)に情報を「新規登録する」フォームを考える場合、
以下のように整理できます。

DB上のカラム / Entityのメンバフォームの入力項目 / Formクラスのメンバ
・id
・code
・name
・age
・createdAt
・modifiedAt

・code
・name
・age

フォームの入力項目とは、つまりユーザーに入力してほしい情報で、
ユーザーに入力させる必要のないメンバ(id、日時の3カラムは自動登録)は基本的にFormクラスには用意する必要はありません。
(Viewへの表示も含めて考えると、他のフィールドを設定することになる場合もあり)

クラス同士の変換方法としては明確な決まりはありませんので、以下にいくつか例示します。

①Formクラスに定義

Formクラス(ここでは仮でEmployeeEntryForm)にEntityを返すメソッドを定義

public class EmployeeEntryForm implements Serializable {
    /**
     * EmployeeのEntityに移し替えする (1) オブジェクトを生成、セッターで値を格納
     * 
     * @return Employee
     */
    public Employee toEntity() {
        Employee employee = new Employee();
        employee.setCode(this.code);
        employee.setName(this.name);
        employee.setAge(this.age);

        return employee;
    }
}

Controllerでデータ登録する場合、このtoEntityメソッドを以下のように呼び出して使用できます。

// EmployeeEntryForm form で定義しているとして
// 登録処理
employeeRepository.save(form.toEntity());


なお、Employeeクラスに3変数を受け取るコンストラクタを作成していれば下記のように記述を短くすることもできます。

public class EmployeeEntryForm implements Serializable {
    /**
     * EmployeeのEntityに移し替えする (2) Emplyeeのコンストラクタを使用
     * 
     * @return Employee
     */
    public Employee toEntity() {
        return new Employee(this.code, this.name, this.age);
    }
}

②ControllerまたはServiceに定義

最終的に「Formクラスで受け取った値を移し替えたEntityを得る」ことが出来ればいいので、
FormクラスではなくServiceクラスなどに定義しても問題ありません。

    /**
     * EmployeeのEntityに移し替えする (2) Emplyeeのコンストラクタを使用
     * 
     * @param form EmployeeEntryForm
     * @return Employee
     */
    public Employee formToEntity(EmployeeEntryForm form) {
        return new Employee(form.getCode(), form.getName(), form.getAge());
    }


同じService内にて上記のメソッドを使用する場合は以下のような形になります

// EmployeeEntryForm form で定義しているとして
// 登録処理
employeeRepository.save(formToEntity(form));


①②のようなメソッドを作らずに都度値を詰め直す処理でも問題ありません。
繰り返し何度も使用する場合は、上記のようにメソッドを定義した方が記述をシンプルにできるのでおすすめです。

タイトルとURLをコピーしました