第2回/ToDo管理/管理者機能実装
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
]
開始行:
[[第2回/ToDo管理]]
* ToDo管理アプリケーションの管理者機能の実装 [#z226a8bb]
** 目次 [#xc9fec71]
#contents
** UI紙芝居 [#r144bfaf]
&attachref(admin_screen.png);
** 実装 [#jae00adf]
管理者のユースケースでは,メンバーの登録(UC02),取得(UC01...
よって,下記の順番で実装していく
- A1: メンバーのエンティティを定義
- A2: メンバーをCRUDするレポジトリを作成
- A3: メンバーの登録フォームを作成
- A4: サービスの例外を作成
- A5: メンバーを登録,取得,削除するサービスを作成
-- 存在チェックや重複チェックも行う
- A6: サービスを呼び出すコントローラを作成
-- リクエストマッピング
-- 画面生成・遷移
- A7: 画面用のHTMLテンプレートを作成する
*** A1: Entity - エンティティ [#r968568e]
- entity/Member.java
package jp.kobespiral.manda.todo.entity;//適宜変えること
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Member {
@Id
String mid; //メンバーID
String name; //氏名
}
- &color(red){適宜,import文を補うこと};
- 今回管理するメンバ情報は,IDと氏名のみ
- @Idは,midをエンティティの''識別子(ID)'' にするという...
-- 識別子とはそのエンティティを一意に識別できる文字列や数...
*** A2: Repository - レポジトリ [#ke3147c2]
- repository/MemberRepository.java
package jp.kobespiral.manda.todo.repository;//変える
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import jp.kobespiral.manda.todo.entity.Member;//変える
@Repository
public interface MemberRepository extends CrudRepository...
List<Member> findAll();
}
- Repositoryはinterfaceであることに注意
-- Spring Data JPAのCrudRepositoryを継承する
-- ジェネリクスは<管理するエンティティクラス, そのクラス...
- MemberのCRUDを行う一通りのメソッドが自動実装される [[Sp...
Optional<Member> findById(String mid); //null安全な1件取...
Iterable<Member> findAll(); //全メンバー取得 (R)
Member save(Member member); //メンバー保存・更新 (...
void deleteById(String mid); //メンバー削除 (D)
void deleteAll(); //メンバー全権削除 (D)
boolean existById(String mid); //存在チェック
Long count(); //登録件数
- カスタムメソッド List<Member> findAll(); を陽に定義して...
-- 定義しなければサービス層でIterableからListに入れなおさ...
*** A3: Form - 登録フォーム [#y92e415d]
- dto/MemberForm.java
package jp.kobespiral.manda.todo.dto;
import jp.kobespiral.manda.todo.entity.Member;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
public class MemberForm {
String mid; //メンバーID.
String name; //名前
public Member toEntity() {
Member m = new Member(mid, name);
return m;
}
}
- ブラウザからのリクエストを受けるために必要
-- エンティティと同じ属性であっても,違うクラスとして区別...
*** A4: Exception - アプリケーション例外 [#lcbabf9a]
- exception/ToDoAppException.java
package jp.kobespiral.manda.todo.exception;
public class ToDoAppException extends RuntimeException{
public static final int NO_SUCH_MEMBER_EXISTS = 101;
public static final int MEMBER_ALREADY_EXISTS = 102;
public static final int INVALID_MEMBER_INFO = 103;
public static final int INVALID_MEMBER_OPERATION = 104;
public static final int NO_SUCH_TODO_EXISTS = 201;
public static final int INVALID_TODO_INFO = 202;
public static final int INVALID_TODO_OPERATION = 203;
int code;
public ToDoAppException(int code, String message) {
super(message);
this.code = code;
}
public ToDoAppException(int code, String message, Exc...
super(message, cause);
this.code = code;
}
public int getCode() {
return code;
}
}
- 非検査例外(RuntimeException)として定義
-- 内部にエラーコードを含んでエラーの種類を区別している
-- 今回は投げるだけで拾わない.→エラーハンドリングは次回
*** A5: Service - サービス [#w3e6631a]
- service/MemberService.java
package jp.kobespiral.manda.todo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Auto...
import org.springframework.stereotype.Service;
import jp.kobespiral.manda.todo.dto.MemberForm;
import jp.kobespiral.manda.todo.entity.Member;
import jp.kobespiral.manda.todo.exception.ToDoAppExcepti...
import jp.kobespiral.manda.todo.repository.MemberReposit...
@Service
public class MemberService {
@Autowired
MemberRepository mRepo;
/**
* メンバーを作成する (C)
* @param form
* @return
*/
public Member createMember(MemberForm form) {
//IDの重複チェック
String mid = form.getMid();
if (mRepo.existsById(mid)) {
throw new ToDoAppException(ToDoAppException.M...
}
Member m = form.toEntity();
return mRepo.save(m);
}
/**
* メンバーを取得する (R)
* @param mid
* @return
*/
public Member getMember(String mid) {
Member m = mRepo.findById(mid).orElseThrow(
() -> new ToDoAppException(ToDoAppExcepti...
return m;
}
/**
* 全メンバーを取得する (R)
* @return
*/
public List<Member> getAllMembers() {
return mRepo.findAll();
}
/**
* メンバーを削除する (D)
*/
public void deleteMember(String mid) {
Member m = getMember(mid);
mRepo.delete(m);
}
}
- 管理者ユースケースに必要なメソッドを実装
-- 基本的にはリポジトリのメソッドをラップしているだけ
- ビジネスルールのチェック
-- 作成時(C)に重複チェック → 重複があれば例外スロー
-- 取得時(R)に存在チェック → 存在しなければ例外スロー
-- 削除時(D)はgetMember()を介して存在チェック
*** A6: コントローラ - Controller [#yb9a345e]
- controller/MemberController.java
package jp.kobespiral.manda.todo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Auto...
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttr...
import org.springframework.web.bind.annotation.PathVaria...
import org.springframework.web.bind.annotation.PostMappi...
import org.springframework.web.bind.annotation.RequestMa...
import jp.kobespiral.manda.todo.dto.MemberForm;
import jp.kobespiral.manda.todo.entity.Member;
import jp.kobespiral.manda.todo.service.MemberService;
@Controller
@RequestMapping("/admin")
public class MemberController {
@Autowired
MemberService mService;
/**
* 管理者用・ユーザ登録ページ HTTP-GET /admin/register
* @param model
* @return
*/
@GetMapping("/register")
String showUserForm(Model model) {
List<Member> members = mService.getAllMembers();
model.addAttribute("members", members);
MemberForm form = new MemberForm();
model.addAttribute("MemberForm", form);
return "register";
}
/**
* 管理者用・ユーザ登録確認ページを表示 HTTP-POST /ad...
* @param form
* @param model
* @return
*/
@PostMapping("/check")
String checkUserForm(@ModelAttribute(name = "MemberFo...
model.addAttribute("MemberForm", form);
return "check";
}
/**
* 管理者用・ユーザ登録処理 -> 完了ページ HTTP-POST /...
* @param form
* @param model
* @return
*/
@PostMapping("/register")
String createUser(@ModelAttribute(name = "MemberForm"...
Member m = mService.createMember(form);
model.addAttribute("MemberForm", m);
return "registered";
}
/**
* 管理者用・ユーザ削除処理 HTTP-GET /admin/delete/{...
* @param mid
* @param model
* @return
*/
@GetMapping("/delete/{mid}")
String deleteUser(@PathVariable String mid, Model mod...
mService.deleteMember(mid);
return showUserForm(model);
}
}
*** A7: HTMLテンプレート [#gfddad61]
- resources/templates/register.html (管理者・メンバー登録...
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>管理者画面</title>
</head>
<body>
<h1>管理者画面</h1>
<h2>メンバー新規登録</h2>
<p> メンバーIDと氏名を入力して,「確認する」を押して...
<ul>
<li>メンバーIDには,アルファベット小文字,数字,...
</ul>
<form role="form" th:action="@{/admin/check}" th:obj...
<table>
<tr>
<td><label>メンバーID: </label></td>
<td><input type="text" required th:field...
</tr>
<tr>
<td><label>氏名: </label></td>
<td><input type="text" required th:field...
</tr>
</table>
<p><input type="submit" value="確認する" /></p>
</form>
<h2>登録済みメンバー</h2>
<table border="1">
<tr>
<th>メンバーID</th>
<th>氏名</th>
<th>コマンド</th>
</tr>
<tr th:each="m: ${members}">
<td>[[${m.mid}]]</td>
<td>[[${m.name}]]</td>
<td><a th:href=@{/admin/delete/{m}(m=${m.mid...
</tr>
</table>
</body>
</html>
- resources/templates/check.html (管理者・メンバー登録確...
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登録確認</title>
</head>
<body>
<h1>登録確認</h1>
<p>
以下の情報でメンバーを登録します.よろしければ登...
</p>
<form role="form" th:action="@{/admin/register}" th:...
<p>
<label>メンバーID: </label>
[[*{mid}]]
</p>
<p>
<label>氏名: </label>
[[*{name}]]
</p>
<p>
<input type="button" value="戻る" onclick="h...
<input type="submit" value="登録する" />
</p>
<input type="hidden" th:field="*{mid}" />
<input type="hidden" th:field="*{name}" />
</form>
</body>
</html>
- resources/templates/registered.html (管理者・メンバー登...
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登録完了</title>
</head>
<body>
<h1>登録完了</h1>
<p>
メンバー登録を完了しました.
</p>
<div th:object="${MemberForm}">
<p>
<label>メンバーID: </label>
[[*{mid}]]
</p>
<p>
<label>氏名: </label>
[[*{name}]]
</p>
<p>
<a th:href=@{/admin/register}>続けて登録する...
<a th:href=@{/}>初めに戻る</a>
</p>
</div>
</body>
</html>
** ここまでの機能を実行&テストする [#j86754c1]
- VSCodeでTodoappApplication.javaを選択し,F5で実行
- ブラウザで http://localhost:18080/admin/register を開く
- ユーザの追加が行われているかチェックしよう
** データベースにデータが永続化されているかチェックしよう...
- C:\TAMP\apache-httpd を起動
- http://localhost/phpmyadmin
- ユーザ:todouser パス: todotodo でログイン
- データベース todoappのmemberテーブルを確認する
終了行:
[[第2回/ToDo管理]]
* ToDo管理アプリケーションの管理者機能の実装 [#z226a8bb]
** 目次 [#xc9fec71]
#contents
** UI紙芝居 [#r144bfaf]
&attachref(admin_screen.png);
** 実装 [#jae00adf]
管理者のユースケースでは,メンバーの登録(UC02),取得(UC01...
よって,下記の順番で実装していく
- A1: メンバーのエンティティを定義
- A2: メンバーをCRUDするレポジトリを作成
- A3: メンバーの登録フォームを作成
- A4: サービスの例外を作成
- A5: メンバーを登録,取得,削除するサービスを作成
-- 存在チェックや重複チェックも行う
- A6: サービスを呼び出すコントローラを作成
-- リクエストマッピング
-- 画面生成・遷移
- A7: 画面用のHTMLテンプレートを作成する
*** A1: Entity - エンティティ [#r968568e]
- entity/Member.java
package jp.kobespiral.manda.todo.entity;//適宜変えること
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Member {
@Id
String mid; //メンバーID
String name; //氏名
}
- &color(red){適宜,import文を補うこと};
- 今回管理するメンバ情報は,IDと氏名のみ
- @Idは,midをエンティティの''識別子(ID)'' にするという...
-- 識別子とはそのエンティティを一意に識別できる文字列や数...
*** A2: Repository - レポジトリ [#ke3147c2]
- repository/MemberRepository.java
package jp.kobespiral.manda.todo.repository;//変える
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import jp.kobespiral.manda.todo.entity.Member;//変える
@Repository
public interface MemberRepository extends CrudRepository...
List<Member> findAll();
}
- Repositoryはinterfaceであることに注意
-- Spring Data JPAのCrudRepositoryを継承する
-- ジェネリクスは<管理するエンティティクラス, そのクラス...
- MemberのCRUDを行う一通りのメソッドが自動実装される [[Sp...
Optional<Member> findById(String mid); //null安全な1件取...
Iterable<Member> findAll(); //全メンバー取得 (R)
Member save(Member member); //メンバー保存・更新 (...
void deleteById(String mid); //メンバー削除 (D)
void deleteAll(); //メンバー全権削除 (D)
boolean existById(String mid); //存在チェック
Long count(); //登録件数
- カスタムメソッド List<Member> findAll(); を陽に定義して...
-- 定義しなければサービス層でIterableからListに入れなおさ...
*** A3: Form - 登録フォーム [#y92e415d]
- dto/MemberForm.java
package jp.kobespiral.manda.todo.dto;
import jp.kobespiral.manda.todo.entity.Member;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
public class MemberForm {
String mid; //メンバーID.
String name; //名前
public Member toEntity() {
Member m = new Member(mid, name);
return m;
}
}
- ブラウザからのリクエストを受けるために必要
-- エンティティと同じ属性であっても,違うクラスとして区別...
*** A4: Exception - アプリケーション例外 [#lcbabf9a]
- exception/ToDoAppException.java
package jp.kobespiral.manda.todo.exception;
public class ToDoAppException extends RuntimeException{
public static final int NO_SUCH_MEMBER_EXISTS = 101;
public static final int MEMBER_ALREADY_EXISTS = 102;
public static final int INVALID_MEMBER_INFO = 103;
public static final int INVALID_MEMBER_OPERATION = 104;
public static final int NO_SUCH_TODO_EXISTS = 201;
public static final int INVALID_TODO_INFO = 202;
public static final int INVALID_TODO_OPERATION = 203;
int code;
public ToDoAppException(int code, String message) {
super(message);
this.code = code;
}
public ToDoAppException(int code, String message, Exc...
super(message, cause);
this.code = code;
}
public int getCode() {
return code;
}
}
- 非検査例外(RuntimeException)として定義
-- 内部にエラーコードを含んでエラーの種類を区別している
-- 今回は投げるだけで拾わない.→エラーハンドリングは次回
*** A5: Service - サービス [#w3e6631a]
- service/MemberService.java
package jp.kobespiral.manda.todo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Auto...
import org.springframework.stereotype.Service;
import jp.kobespiral.manda.todo.dto.MemberForm;
import jp.kobespiral.manda.todo.entity.Member;
import jp.kobespiral.manda.todo.exception.ToDoAppExcepti...
import jp.kobespiral.manda.todo.repository.MemberReposit...
@Service
public class MemberService {
@Autowired
MemberRepository mRepo;
/**
* メンバーを作成する (C)
* @param form
* @return
*/
public Member createMember(MemberForm form) {
//IDの重複チェック
String mid = form.getMid();
if (mRepo.existsById(mid)) {
throw new ToDoAppException(ToDoAppException.M...
}
Member m = form.toEntity();
return mRepo.save(m);
}
/**
* メンバーを取得する (R)
* @param mid
* @return
*/
public Member getMember(String mid) {
Member m = mRepo.findById(mid).orElseThrow(
() -> new ToDoAppException(ToDoAppExcepti...
return m;
}
/**
* 全メンバーを取得する (R)
* @return
*/
public List<Member> getAllMembers() {
return mRepo.findAll();
}
/**
* メンバーを削除する (D)
*/
public void deleteMember(String mid) {
Member m = getMember(mid);
mRepo.delete(m);
}
}
- 管理者ユースケースに必要なメソッドを実装
-- 基本的にはリポジトリのメソッドをラップしているだけ
- ビジネスルールのチェック
-- 作成時(C)に重複チェック → 重複があれば例外スロー
-- 取得時(R)に存在チェック → 存在しなければ例外スロー
-- 削除時(D)はgetMember()を介して存在チェック
*** A6: コントローラ - Controller [#yb9a345e]
- controller/MemberController.java
package jp.kobespiral.manda.todo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Auto...
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttr...
import org.springframework.web.bind.annotation.PathVaria...
import org.springframework.web.bind.annotation.PostMappi...
import org.springframework.web.bind.annotation.RequestMa...
import jp.kobespiral.manda.todo.dto.MemberForm;
import jp.kobespiral.manda.todo.entity.Member;
import jp.kobespiral.manda.todo.service.MemberService;
@Controller
@RequestMapping("/admin")
public class MemberController {
@Autowired
MemberService mService;
/**
* 管理者用・ユーザ登録ページ HTTP-GET /admin/register
* @param model
* @return
*/
@GetMapping("/register")
String showUserForm(Model model) {
List<Member> members = mService.getAllMembers();
model.addAttribute("members", members);
MemberForm form = new MemberForm();
model.addAttribute("MemberForm", form);
return "register";
}
/**
* 管理者用・ユーザ登録確認ページを表示 HTTP-POST /ad...
* @param form
* @param model
* @return
*/
@PostMapping("/check")
String checkUserForm(@ModelAttribute(name = "MemberFo...
model.addAttribute("MemberForm", form);
return "check";
}
/**
* 管理者用・ユーザ登録処理 -> 完了ページ HTTP-POST /...
* @param form
* @param model
* @return
*/
@PostMapping("/register")
String createUser(@ModelAttribute(name = "MemberForm"...
Member m = mService.createMember(form);
model.addAttribute("MemberForm", m);
return "registered";
}
/**
* 管理者用・ユーザ削除処理 HTTP-GET /admin/delete/{...
* @param mid
* @param model
* @return
*/
@GetMapping("/delete/{mid}")
String deleteUser(@PathVariable String mid, Model mod...
mService.deleteMember(mid);
return showUserForm(model);
}
}
*** A7: HTMLテンプレート [#gfddad61]
- resources/templates/register.html (管理者・メンバー登録...
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>管理者画面</title>
</head>
<body>
<h1>管理者画面</h1>
<h2>メンバー新規登録</h2>
<p> メンバーIDと氏名を入力して,「確認する」を押して...
<ul>
<li>メンバーIDには,アルファベット小文字,数字,...
</ul>
<form role="form" th:action="@{/admin/check}" th:obj...
<table>
<tr>
<td><label>メンバーID: </label></td>
<td><input type="text" required th:field...
</tr>
<tr>
<td><label>氏名: </label></td>
<td><input type="text" required th:field...
</tr>
</table>
<p><input type="submit" value="確認する" /></p>
</form>
<h2>登録済みメンバー</h2>
<table border="1">
<tr>
<th>メンバーID</th>
<th>氏名</th>
<th>コマンド</th>
</tr>
<tr th:each="m: ${members}">
<td>[[${m.mid}]]</td>
<td>[[${m.name}]]</td>
<td><a th:href=@{/admin/delete/{m}(m=${m.mid...
</tr>
</table>
</body>
</html>
- resources/templates/check.html (管理者・メンバー登録確...
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登録確認</title>
</head>
<body>
<h1>登録確認</h1>
<p>
以下の情報でメンバーを登録します.よろしければ登...
</p>
<form role="form" th:action="@{/admin/register}" th:...
<p>
<label>メンバーID: </label>
[[*{mid}]]
</p>
<p>
<label>氏名: </label>
[[*{name}]]
</p>
<p>
<input type="button" value="戻る" onclick="h...
<input type="submit" value="登録する" />
</p>
<input type="hidden" th:field="*{mid}" />
<input type="hidden" th:field="*{name}" />
</form>
</body>
</html>
- resources/templates/registered.html (管理者・メンバー登...
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登録完了</title>
</head>
<body>
<h1>登録完了</h1>
<p>
メンバー登録を完了しました.
</p>
<div th:object="${MemberForm}">
<p>
<label>メンバーID: </label>
[[*{mid}]]
</p>
<p>
<label>氏名: </label>
[[*{name}]]
</p>
<p>
<a th:href=@{/admin/register}>続けて登録する...
<a th:href=@{/}>初めに戻る</a>
</p>
</div>
</body>
</html>
** ここまでの機能を実行&テストする [#j86754c1]
- VSCodeでTodoappApplication.javaを選択し,F5で実行
- ブラウザで http://localhost:18080/admin/register を開く
- ユーザの追加が行われているかチェックしよう
** データベースにデータが永続化されているかチェックしよう...
- C:\TAMP\apache-httpd を起動
- http://localhost/phpmyadmin
- ユーザ:todouser パス: todotodo でログイン
- データベース todoappのmemberテーブルを確認する
ページ名: