ITパスポート関連用語集
今回のお題
今回のお題はITパスポート関連用語集です。
実際に受験する予定は今の所はないのですが、ITパスポートの勉強を始めました。
一般常識として役立つものも多いので良いのではないかと考えています。
今後形式は変わるの可能性がありますが、記念すべき第一回の今回は、知らなかった用語についてまとめてみます。
BPR
BRPとはBusiness Process Reengineering の略です。
業務効率化のために、手順やルールを全面的・抜本的に改革することを指します。
SLA
Service Level Agreement の略です。
サービスの事業者がどの程度までサービスの品質を保証するのかを示したものになります。
SLAは通信サービスやクラウドサービスなどで、通信速度や利用停止時間などに対して設定されることが多いです。
例えば、一部の携帯キャリアでは、24時間以上の通信障害で全ユーザーにデータ通信量を一律支給する制度を設けていますが、これもSLAの一例ですね。
BBS
いわゆるインターネット掲示板のことですね。
IT用語としては、参加者が自由に書き込めるものを指します。
ちなみにBulletin Board System の略で、Bulletinは「速報」という意味です。
RFP
Request For Proporsal の略です。
ユーザーがベンダー、すなわちシステムの発注先候補に対して具体的なシステム提案を依頼することを指します。
また、実際に提案されたシステムの概要や調達条件を含める場合もあります。
レコード詳細機能の実装
今回のお題
今回のお題は、「レコード詳細機能の実装」です。
基本的なロジックは、レコードの主キーの情報を含むURLにリクエストを送ることでビューが返され、そこに対象のレコードの情報が含まれている、というものになります。
前提知識として、前回取り上げた「動的URL」の知識が必要になります。
よろしければ先にご一読ください。
目次
全体の流れ
先に全体の流れを説明しておきます。
今回は、"/user/detail/xx"(xxにはユーザーIDが入る)にリクエストを送るとUserDetailControllerのgetUserメソッドが呼び出されてuser/detail.htmlが返されるという流れになります。
DB操作関数の定義
まずは、DB操作関数を定義します。
ファイルは、引き続きUserMapper.javaを用います(参考)。
以下のコードを追加してください。
public MUser findOne(String userId);
userIdを与えるとMUserクラスのインスタンスを返すfindOneメソッドを定義しています。
次に、このメソッドで実行されるSQL文をUserMapper.xmlに定義していきます。
SQL文の記述
以下をUserMapper.xmlに追加してください。
// <mapper>タグのすぐ下に追加
<resultMap type="com.example.demo.domain.user.model.MUser" id="user">
<id column="user_id" property="userId"/>
<result column="password" property="password"/>
<result column="user_name" property="userName"/>
<result column="birthday" property="birthday"/>
<result column="age" property="age"/>
<result column="gender" property="gender"/>
<result column="department_id" property="departmentId"/>
<result column="role" property="role"/>
</resultMap>
// </mapper>タグの直前に追加
<select id="findOne" resultMap="user">
select * from m_user where user_id = #{userId}
</select>
こんなものをいきなり見せられても何が何だかわからないですね(少なくとも私はわかりませんでした)。
なので、重要な部分を順に順に解説していきます。
- <resultMap type="xxx" id="xxx">
今回はm_userテーブルからMUserクラスのインスタンスを生成するのですが、<resultMap>タグは、m_userテーブルのカラムとMUserクラスのフィールド名を対応させるために使用します。
設定するべき属性は2つで、type属性には、対象のモデル(MUser)をパッケージ名を含めて記述します。
id属性には、任意の名前をつけます。用途については後述します。 - <id column="xxx" property="yyy">, <result column="xxx" property="yyy">
ともに<resultMap></resultMap>の内部に配置します。
column属性で指定した"xxx"カラムとproperty属性で指定した"yyy"フィールドを対応させます。
つまり、m_userテーブルの"xxx"カラムに"a"と入っているレコードから、MUserクラスの"yyy"というインスタンスフィールドに"a"という値を持つインスタンスが生成されます。 - <id>タグと<result>タグの違い
基本的な使い分けとしては、値の重複を許したくないカラムや主キーに対応するカラムには<id>タグを使い、それ以外のカラムには<result>タグを使います。
この理由については、<id>タグ内で指定したカラムについては、値が一意になるようにレコードが取得されるからです。
つまり、仮に複数のレコードを取得する操作であっても、<id>タグ内で指定したカラムの値が重複するレコードは検索結果から除外されてしまうというわけですね。
(例えば、ageカラムの値が30以上のレコードを全て取得する処理を定義した際に、nameカラムが"太郎"である30歳以上のレコードが3つあったとする。
nameカラムを<result>タグで指定していれば3人の太郎は全て戻り値に含まれるが、<id>タグで指定していた場合には"太郎"は1人しか戻り値に含まれない。)
何はともあれ、これでカラムとフィールドのマッピングが完了しました。
次に、SQL文を書いていきます。 - <select id="findOne" resultMap="user">
SQL文を囲むタグはselectタグを用います(SQL文がselect文のため)。
id属性はメソッド名のfindOneですね。
また、戻り値の型に関してはresutlTypeではなくresultMap属性を用いて定義しています。
先程、<resultMap>タグを用いて戻り値の型を定義しました。
その<resultMapタグ>のidの値を<select>タグのresultMap属性の値一致させることで、間接的にresutlTypeの指定がなされています。 - where user_id = #{userId}
#{}で囲むことによって、insetOneメソッドの引数の値を参照できるようになります。
今回は引数として与えたuserIdがm_userテーブルのuser_idカラムと一致する箇所を調べたいので、このように記述しています。
さて、これでSQL文が完成し、m_userテーブルのレコードをMUserモデルのインスタンスに変換できるようになりました。
本来であればこの後MUserモデルのインスタンスを生成する処理を書くのですが、最終的にはそれをさらにフォームにバインドしたクラスに変換するので、先にフォームのクラスを用意しておきます。
フォームの作成
フォームにバインドしたクラスを作成します。
UserDetailForm.javaを作成し、以下のように記述します。
@Data
public class UserDetailForm {
private String userId;
private String password;
private String userName;
private Date birthday;
private Integer age;
private Integer gender;
}
基本的にはMUser.javaとほぼ同じです。
MUser.javaに存在していたカラムの中で"role"と"department_id"については表示する予定がなかったのでUserDetailForm.javaには定義していません。
この辺りについてはもちろん自由度が認められる部分です。
では少し寄り道しましたが、次にMUserのインスタンスを作っていきます。
インスタンス生成関数の定義
まずはインターフェースを編集して関数の名前を定義していきます。
UserService.javaに以下を追記してください。
public MUser getUserOne(String userId);
userIdという引数からMUSerクラスのインスタンスを返すgetUserOneメソッドを定義しました。
次に、このメソッドの中身を定義していきます。
インスタンス生成関数の中身の記述
UserServiceImpl.javaに以下を追記します(UserServiceImpl.javaはUserService.javaを実装したクラスです)。
@Override
public MUser getUserOne(String userId) {
return mapper.findOne(userId);
}
中身自体は大したことがなくて、getUserOneメソッドを行うとModelMapperクラスのインスタンスであるmapperに対してfindOneメソッドが実行されてMUserのインスタンスが返ってくるというものになります。
これでインスタンスが用意できました。
これをコントローラからビューに渡せるようにしていきます。
コントローラの編集
UserDetailControllerを作成し、以下のように記述します。
// import部分
package com.example.demo.controller;
import org.modelmapper.ModelMapper;
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.RequestMapping;
import com.example.demo.application.service.UserService;
import com.example.demo.domain.user.model.MUser;
import com.example.demo.form.UserDetailForm;
@Controller
@RequestMapping("/user")
public class UserDetailController {
// Userserviceをインスタンス化
@Autowired
private UserService userService;
// ModelMapperをインスタンス化
@Autowired
private ModelMapper modelMapper;
@GetMapping("/detail/{userId:.+}")
public String getUser(UserDetailForm form, Model model, @PathVariable("userId")String userId) {
// userIdを元にMUserクラスのインスタンスを生成
MUser user = userService.getUserOne(userId);
// passwordを一旦リセット(フォームで表示することを考えての処理なので必須ではない)
user.setPassword(null);
// MUserインスタンスをUserDetailFormのインスタンスに変換
form = modelMapper.map(user, UserDetailForm.class);
// 生成したUserDetailFormインスタンスをモデルに追加
model.addAttribute("userDetailForm", form);
return "user/detail";
}
}
ビューの表示
ビューの表示については割愛します。
基本的に特別なことはなく、<form>タグにth:object="${userDetailForm}"を指定したのち<input th:field="*{userId}>などとフィールド名を指定していくだけです。
ここについても不明点があれば(参考)をご一読ください。
動的URLの表示方法
今回のお題
今回のお題は「動的URLの表示方法」です。
次は順番的には詳細表示なのですが、その準備段階として「動的URL」についてまとめておきます。
目次
- 動的URLとは
- 動的URLの具体例
- thymeleafにおける変数と文字列の結合方法
- 動的URLの表示
動的URLとは
動的URLとは、「レコードによってURLの一部が変化するようなもの」を指します。
webサイトで、ユーザーの投稿内容を反映して表示されるものが変化するサービスを動的サイト、変化しないものを静的サイトと呼びますよね。
同じように、対応しているレコードに応じてURLが変化するものを動的URLと呼びます。
動的URLの具体例
動的URLの具体例は、ユーザー詳細・編集・削除などへのリンクですね。
例えば、Railアプリのresourcesオプションでユーザー詳細へのURLを自動生成すると、"/users/user_id"となります。
これをprefixやヘルパーメソッドなどを使わずに実装すると、
<a href="/users/#{@user.id}">
というように、文字列と変数を結合させた表記を使うことになります。
URLの中にカラムの値が含まれているので当然と言えば当然ですよね。
なので、次項ではthymeleaf内で変数と文字列を結合させる方法を見ていきます。
thymeleafにおける変数と文字列の結合
変数と文字列の結合は以下のように行います。
<label th:for="'user_'+${user.id}">
固定文字列として扱いたい部分(user_)をシングルクォートで、変数部分をいつも通りに${}で囲めば変数と文字列が結合されます。
動的URLの表示
ここまでくれば、ほとんど完成したも同然ですね。
<a th:href="@{'/user/'+${user.userId}}">
とすれば完成です。
{}が二重になることにだけ注意してください。
レコードの一覧表示機能の実装
今回のお題
今回のお題は「レコードの一覧表示機能の実装」です。
前回が保存機能だったので、その続きですね。
具体的には、UserコントローラのuserListメソッドを介してuser/list.htmlを表示した際にm_userテーブルに保存されているレコードを一覧表示するという流れに沿って、一覧表示機能の実装手順を解説していきます。
目次
全体の流れ
m_userテーブルのレコードを一覧表示する際にコンピュータの内部で行われていることは、
以上になるので、こちら側で記述するべき内容としては
- インターフェースを作成し、DB操作関数を定義する。
- UserMapper.xmlに、1で作成した関数に対応するSQL文を記述する。
- 1とは別のインターフェースを作成し、MUserモデルのインスタンス作成のための関数を定義する。
- 3で定義した関数の中身を記述する。
- 4の関数を用いてコントローラ内でインスタンスを作成できるようにする。
- 作成したインスタンスをビューで表示する。
ということになります。
ですが、その前に少し下準備があるので、まずはそちらを行っていきます。
application.propertiesの編集
まずは、application.propertiesに以下の内容を追加します。
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.example.demo.domain.user.model
1行目については、ModelMapperクラスのmapメソッドの補助をする設定になります。
この記述があると、アンダースコア表記のフィールド(user_idなど)とキャメルケース表記のフィールド名(userId)を同一と見做してmapメソッドを実行してくれます。
Javaクラスのインスタンスフィールド名は基本的にキャメルケースで記述しますが、DBのカラム名は慣例としてスネークケースもあり得るので、そこを気にしなくてよくなるのはありがたいですね。
2行目については、ModelMapperのパッケージ名を省略するための記述になります。
この後ModelMapper(今回でいうとUserMapper)のパッケージ名を記述する場面があるのですが、application.propertiesにこのように記述しておくことでファイル名のみの記述で済むようになります。
では、準備が済んだところでインターフェースにDB操作関数を定義していきます。
DB操作関数の定義
まずはインターフェース内にDB操作関数を定義していきます。
インターフェースはなければ作成しますが、あれば既存のものでも構いません。
今回は、以前にユーザー保存機能を実装した際のインターフェースを使用します。
UserMapper.javaに以下を追加してください。
public List<MUser> findMany();
MUserを要素とする配列を取得するfindMany関数を定義しました。
次は、UserMapper.xmlを編集してこの関数に対応するSQL文を書いていきます。
UserMapper.xmlの編集
UserMapper.xmlに、以下の内容を追加してください。
<select id="findMany" resultType="MUser">
select * from m_user
</select>
注意点は以下の通りです。
- SQL文がselectで始まる時は<select>タグで囲む。
前回はinsert文だったのでinsertタグを用いましたが、今回はselect文なのでタグもそれに合わせます。 - id属性の値としてDB操作関数の名前を指定する。
- select文を使用する場合には、resultType属性を用いて戻り値の型を指定する。
今回はMUserモデルを指定。
戻り値が一つの場合には、StringやIntegerも指定可能。
ちなみに、上記のapplication.propertiesでpackageのエイリアスを指定していない場合には、MUserの部分にパッケージ名も含める必要があります。
今回は、m_userテーブルかに登録されているレコードを全件取得しています。
これで、レコードを配列の形で取得することができました。
次に、ここからMUserクラスのインスタンスを作成していきます。
インスタンスを作成する関数の定義
インターフェースを用意し、インスタンスを作成する関数を定義していきます。
今回は、レコード保存に使用したUserService.javaを使用します。
以下を記述してください。
public List<MUser> getUsers();
MUserのインスタンスを要素にもつ配列を返すgetUserメソッドを定義しています。
次に、このインターフェースを継承したクラスを用いてメソッドの中身を定義していきます。
UserServiceImplの編集
UserServiceImpl.javaに以下の内容を追加します。
@Autowired
public List<MUser> getUsers(){
return mapper.findMany();
}
ModelMapperクラスであるUserMapperに定義した、findMany()メソッドを用いてm_userテーブルから配列を取得しています。
次に、この配列をコントローラに渡します。
コントローラの編集
UserListControllerを編集します。
// 追加
@Autowired
private UserService userService;
// 編集
public String getUserList(Model model){
List<MUser> userList = userService.getUsers();
model.addAttribute("userList", userList);
return "user/list";
}
getUserメソッドを用いて取得したレコードをuserListという変数に代入し、"userList"というキー名でmodelに登録しています。
最後に、modelの中身をビューで表示します。
ビューの編集
userListは配列なので、基本的には以下のようにeachを用いて展開します。
<tr th:each="item:${userList}">
<td th:text="${item.name}"></td>
<td th:text="${item.gender == 1}?'男性':'女性'"></td>
<td th:text="${#dates.format(item.birthday, 'YYYY/MM/dd')}"></td>
</tr>
以下、赤字部分の解説です。
"${item.gender == 1}?'男性':'女性'"
Javaの3項式をth表記に直したものですね。
条件式?"a":"b"の形で、条件式がtrueの場合には"a"、そうでなければ"b"が表示されます。
3項式の例)String message = hour <= 12 ? "午前": "午後"
この例だと、hourという変数が12以下であればmessageという変数に"午前”という文字列が、hourが12より大きければ"午後"という文字列が代入されます。
genderの例だと、genderカラムが1であれば"男性", 2であれば"女性"と表示されます。
"${#dates.format(item.birthday, 'YYYY/MM/dd')}"
に関しては、birthdayカラムの値をYYYY/MM/dd形式に変換して表示しています。
レコードの保存機能の実装について
本日のお題
本日のお題は「レコードの保存機能の実装」です。
以前にフォームのバインド機能を実装してからというもの、バリデーション(こちらとこちら)やテンプレートなど脇道に逸れた話が多くなっていました。
今回は、ようやく元に戻ってレコード保存機能を実装していきます。
というと、「丸々ひとつの記事を使ってまとめるほどのことか?」と疑問を持たれる方も多くいらっしゃると思います。
確かに、RailsやLaravelなどの言語であればわずか数行で書けてしまうこの工程、さほど手間のかかるものとは思えません。
しかし、Spring Bootではやることが思いの外多かったので、こうして記事として取り上げることにした次第です。
以下、SignupControllerのpostSignupメソッドでサインアップを行う際のレコード保存機能の実装を例にとり、話を進めていきます。
目次
- 登場人物紹介
- ライブラリのインストール
- MUser.javaの編集
- UserMapper.javaの編集
- UserMapper.xmlの編集
- Userservice.javaの編集
- UserServiceImpl.javaの編集
- JavaConfig.javaの編集
登場人物紹介
今回の登場人物及びその役割は以下の通りです。
- MyBatis, ModelMapper
ともに今回使用するライブラリです。
MyBatisはDB操作のために使用します。
ModelMapperはインスタンスの内容をコピーしてくれるものになります。 - MUser.java
postSignupメソッドにより作成されるレコードの雛形を定義しているファイルです。
インスタンスフィールドなどを記述していきます。 - UserMapper.java
生成されたインスタンスをDBに追加する処理が記述されているインターフェースです。 - UserMapper.xml
UserMapper.javaで定義された処理に対応するSQL文を記述するためのファイルです。 - UserService.java
サインアップ処理の雛形を作るためのインターフェースです。 - UserserviceImpl.java
サインアップ処理の具体的な中身が記述されているクラスです。
上記のUserService.javaを継承して作られています。 - JavaConfig.java
ModelMapperをDIコンテナに登録して@Autowiredアノテーションを使用できるようにするためのjavaクラスです。 - SignupController.java
サインアップ処理を担当しているクラスです。
他の全ての登場人物を処理した後で、最後にこのファイルを編集します。
以上が今回の登場人物です。
めちゃくちゃ多いですね。
とりあえず、一つずつ見ていきます。
ライブラリのインストール
まず最初に、ライブラリをインストールします。
例によってpom.xmlを開き、以下の内容を追加します。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.modelmapper.extensions</groupId>
<artifactId>modelmapper-spring</artifactId>
<version>2.3.9</version>
</dependency>
これで、今回必要なMyBatisとModelMapperが導入されます。
次に、今回DBに保存するレコードの雛形を作成します。
MUser.javaの編集
MUser.javaというファイルを作り、その中でインスタンスフィールドを設定していきます。
package com.example.demo.domain.user.model;
import java.util.Date;
import lombok.Data;
@Data
public class MUser {
privateString userId;
private String password;
private String userName;
private Date birthday;
privateInteger age;
privateInteger gender;
private Integer departmentId;
privateString role;
}
特に説明することもないですね。
次に、このMUserクラスのインスタンスをDBに保存する処理を作って名前をつけます。
UserMapper.javaの編集
UserMapper.javaというインターフェースを作成し、そこにDB保存のためのメソッドを定義します。
実際の保存のロジックについては、この後のUserMapper.xmlに記述するので、ここでは名前だけで十分です。
UserMapper.javaに、以下のように記述してください。
package com.example.demo.repository;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.domain.user.model.MUser;
@Mapper
public interface UserMapper {
public int insertOne(MUser user);
}
UserMapperというインターフェースの中に、insertOneメソッドを定義しています。
引数は、先ほど作成したMUserクラスのインスタンスです。
次に、UserMapper.xmlで、このinsertOneメソッドの中身を定義していきます。
UserMapper.xmlの編集
ファイルは最初は空のはずなので、以下のように編集します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org.//DTD Mapper 3.0//EN" "http://mybatis.rog/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.UserMapper">
<insert id="insertOne">
insert into m_user(user_id, password, user_name, birthday, age, gender, department_id, role)values(#{userId}, #{password}, #{userName}, #{birthday}, #{age}, #{gender}, #{departmentId}, #{role})
</insert>
</mapper>
以下、重要な部分の解説です。
<mapper namespace="com.example.demo.repository.UserMapper">
前項で定義したインターフェースへのパスをnamespace属性の値として記述します。
<insert id="insertOne">
UserMapperインターフェースのどのメソッドの中身を定義するのかを指定しています。
insert into m_user(user_id, password, user_name, birthday, age, gender, department_id, role)values(#{userId}, #{password}, #{userName}, #{birthday}, #{age}, #{gender}, #{departmentId}, #{role})
insertOneメソッドの中身を定義しています。
基本文型はSQL文よろしく、「insert into テーブル名(カラム名1, カラム名2, .....) values(値1, 値2, .....)」です。
今回は、m_userテーブルのそれぞれのカラムにデータを保存する処理を書いています。
ポイントは、values以下の#{userId} などという部分ですね。
これは、#{インスタンスフィールド名}とすることで、insertOneメソッドの引数(MUserモデルのインスタンス)のインスタンスフィールドの値を参照できるようにするというものです。
これで、MUserの内容をm_userテーブルに保存することはできるようになりました。
次に、サインアップ処理を書いていきます。
UserService.javaの編集
package com.example.demo.application.service;
import com.example.demo.domain.user.model.MUser;
public interface UserService {
public void signup(MUser user);
}
ここでは、MUserを使ってサインアップするという処理の雛形のみを書いています。
次に、UserSerViceImpl.javaを編集してこのメソッドの詳細を記述していきます。
UserServiceImpl.javaの編集
package com.example.demo.domain.user.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.application.service.UserService;
import com.example.demo.domain.user.model.MUser;
import com.example.demo.repository.UserMapper;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper mapper;
@Override
public void signup(MUser user) {
user.setDepartmentId(1);
user.setRole("ROLE_GENERAL");
mapper.insertOne(user);
}
}
やっていることは単純で、UserMapperクラスをインスタンス化した上で、インスタンスメソッドであるinsertOneメソッドを実行しています。
その直前のセッターに関しては、フォームで入力されないカラムの情報を補っているだけなので、今回の本題ではないですね。
さて、これでMUserモデルのインスタンスの情報をDBに保存するという処理が書けました。
残るはフォームの入力内容からMUserのインスタンスを作成する部分のみなのですが、もう一つやることがあります。
ModelMapperをDIコンテナに登録することです。
ModelMapperは自分で勝手に作ったクラスなので、DIコンテナへの登録も自分で行う必要があります。
というわけで、JavaConfig.javaを作成してDIコンテナへ登録していきます。
JavaConfig.javaの編集
以下のように編集します。
package com.example.demo.config;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JavaConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
最後に、コントローラを編集します。
前項で言及していた、「フォームの内容からMUserインスタンスを作る」という部分ですね。
SignupControllerの編集
SignupControllerメソッドを以下のように編集します。
赤字部分が今回追加される箇所です。
@Autowired
private UserService userService;
@Autowired
private ModelMapper modelMapper;
public String postSignup(Model model, Locale locale,@Validated(GroupOrder.class) @ModelAttribute SignupForm form, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return getSignup(model, locale, form);
}
log.info(form.toString());
MUser user = modelMapper.map(form, MUser.class);
userService.signup(user);
return "redirect:/login";
}
MUser user = modelMapper.map(form, MUser.class);
まず、mapメソッドを用いてフォームの内容からMUserクラスのインスタンスを作成しています。
mapメソッドはModelMapperクラスのインスタンスメソッドでインスタンスフィールドの値をコピーしてくれる機能を持っています。
具体的に書くと、
- 第二引数(今回はMUserクラス)のインスタンス(user)を新しく生成。
- userの各インスタンスフィールドについて、同名のものが第一引数(今回であればform)に存在するかどうかをチェック。
- もし存在していれば、formからuserに値をコピーする。
ちなみに、この処理は内部的にはゲッターとセッターを用いて行っているそうなので、あらかじめ両者(formとuser)にゲッターとセッターが定義されている必要があります。
何はともあれ、これでフォームの値を元にインスタンスを作成することができました。
後の、足りない部分の情報を補う処理とDBへの保存は
userService.signup(user);
で行われるので、これでめでたく保存完了です。
終わりに
以上で、フォームに入力された値をDBに保存する仕組みが実装できました。
どうしても他の言語よりも手間がかかりますが、少しづつ覚えていきたいですね。
Spring Bootにおけるテンプレートの利用
本日のお題
本日のお題は、「Spring Bootでのテンプレートの利用」です。
要するに、あるhtmlファイルの内容を他のhtmlに埋め込む方法ですね。
一口にテンプレートといっても、「一つのファイルに複数のファイルを埋め込む」のか逆に「一つのファイルを複数のファイルに埋め込む」のか、あるいは変数の受け渡しをするのかしないのか、でやり方が変わります。
今回は、「変数の受け渡しなし」で「一つのファイルに他の複数のファイルを集約する方法」について扱います。
他の方法は機会があれば。
目次
- 他のファイルの中身を表示する仕組み
- ライブラリの追加
- xmlns属性の追加
- 埋め込まれる側の編集
- 埋め込む側の編集
他のファイルの中身を表示する仕組み
まずは、他のファイルの中身を表示する仕組みについて話します。
まず、読み込まれる(追加元)のファイルの側で、他のファイルに渡す範囲と、そこの部分につけるキー名を設定します。
次に、読み込み先のファイルで読み込み元のファイルのパスと先ほど決めたキー名を指定すれば、そのファイルが読み込まれます。
文字だけだとわかりにくいと思うので、実例を使って説明します。
読み込み元はtemplates/layout/header.html, 読み込み先はtemplates/layout/layout.htmlとします。
ライブラリの追加
まず、必要なライブラリを追加します。
pom.xmlに以下を追加してください。
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
xmlns属性の追加
次に、読み込み元・読み込み先それぞれにxmlns属性を追加します。
htmlタグの中に、以下の属性を追記してください。
xmlns:layout="http://ultraq.net.nz/thymeleaf/layout"
これを追加することで、テンプレート機能が利用できるようになります。
次に、埋め込み元を編集していきます。
埋め込み元の編集
埋め込み元、すなわち今回で言うとheader.htmlにおいて行うことは2つです。
一つは、埋め込み先の指定、もう一つは埋め込み範囲とキー名の定義です。
埋め込み先の指定
埋め込み先の指定に関しては、htmlタグの中にlayout:decorate="~{layout/layout}"と記述するだけです。
" "の中身はtemplates以下の相対パスになるので、ファイル名に合わせて適宜変更してください。
埋め込み範囲とキー名の指定
埋め込み範囲とキー名については、以下のように行います。
<div layout:fragment="header">
......省略......
</div>
他のファイルに渡したい範囲の一番外側にlayout:fragment属性を付与することで、その範囲が読み込まれます。
また、=以下の部分が読み込む際のキー名になります。
読み込む側の編集
最後に、読み込む側を編集します。
今回で言うと、layout.htmlの側ですね。
やり方は簡単で、ある要素にlayout:replace属性を付与すると、その要素が他のファイルの中身に置き換わります。
例えば、<div layout:replace="~{layout/header::header}"></div>とすると、header.htmlの中のheaderと言うキー名をつけた部分が、上記のdiv要素と置き換わります。
終わりに
以上がテンプレートの利用方法でした。
冒頭で述べたように、一口にテンプレートといっても色々なパターンがあります。
他のパターンについても、機会があれば取り上げていこうと思います。
バリデーションのグループ化
本日のお題
今回は息抜き回です。
バリデーションのグループ分けというものをやっていきます。
前回まで作成していたバリデーションですが、少し数が多くなってしまいました。
あまりないかもしれませんが、全てのバリデーションに同時に引っかかった場合などに、全てのメッセージが同時に表示されるとかなり見づらくなってしまいます。
なので、バリデーションを2つのグループに分けて第一グループを全てクリアした場合にのみ2つ目のバリデーションが実行されるようにしたいと思います。
こうすれば、同時に表示されるエラーメッセージは最大でも半分になるので、それまでよりはマシになるのかなと思います。
目次
- インターフェースの作成
- インターフェースの編集
- フォームを定義しているJavaクラスの編集
- コントローラの編集
インターフェースの作成
まずは、formディレクトリの中にインターフェース用のファイルを3つ作成します。
インターフェースについては、こちらに詳しくまとめてあるので今回は説明しませんが、簡単に言うと抽象クラスの親戚みたいなものです。
今回は、バリデーションを2つのグループに分けるので、グループ1, 2それぞれに対応したインターフェースと、両者の実行順序を管理するためのインターフェースの計3つを作成します。
ファイル名については何でも大丈夫ですが、今回は
としておきます。
次に、インターフェースを編集していきます。
インターフェースの編集
インターフェースの編集とはいっても、ValidGroup1, 2に関してはほとんどすることはありません。
ValidGroup1.java
package com.example.demo.hello.form;
public interface ValidGroup1{
// 何も書かない
}
ValidGroup2に関しても同じなので省略します。
GroupOrder.javaには、両者の実施順序を書いていきます。
GroupOrder.java
package com.example.demo.hello.form;
import javax.validation.GroupSequence;
@GroupSequence(ValidGroup1.class, ValidGroup2.class)
public interface GroupOrder{}
@GroupSequenceアノテーションの中でバリデーションの実行順序を規定しています。
.classを忘れないように注意しましょう。
これで、ValidGroup1に所属しているバリデーションを先に実行することに決まりました。
ただ、どのバリデーションがValidGroup1に所属しているのかはまだ決まっていません。
なので、SignupForm.javaを編集して、バリデーションをグループと対応させていきます。
SignupForm.javaの編集
では、バリデーションをグループ分けしていきます。
方法は単純で、以下のようにカッコ内でグループを指定するだけです。
例)
@NotBlank(groups = ValidGroup1.class)
@Min(value = 4, groups = ValidGroup2.class)
上記の記述を追加するに当たって、importなどは特に必要ありません。
今回も末尾の.classを忘れないようにしましょう。
注意点があるとすれば、@Minや@Maxなどの引数が一つだけあるバリデーションをグループ分けする場合ですね。
この場合は、@Min(4)などと書いていた部分をvalue = 4 の形に直して、何の引数であることを明示してあげてください。
ここまできたらグループ分けはOKです。
最後にコントローラを編集します。
コントローラの編集
// GroupOrder.javaのimport
import com.example.demo.hello.form.GroupOrder;
// GroupOrder.javaの適用
public String postSignup(Model model, Locale locale, @ModelAttribute @Validated(GroupOrder.class) SignupForm form, BindingResult bindingResult){
.....以下変更なしのため省略....
}
@Validated アノテーションの引数としてGroupOrder.classを与えることで、このインターフェースで定義した順番を適用しています。