Closed14

SpringBootやる

まっきんとっしゅまっきんとっしゅ

セクション3 オブジェクト指向の復習

EclipseでCtrl + Dで一行削除

Command + ⬆️ + O でimportができる

@Override

をつけておくとこのメソッドはオーバーライドしたとプログラムにわからせる

シングルトン:1つのクラスに対して1つだけのインスタンスを生成することを保証する

  • service/MemberServiceImpl.java
public class MemberServiceImpl implements MemberService {
 
        //ここでインスタンスを生成する。
	private static MemberServiceImpl singleton =  new MemberServiceImpl();
        
  //外からnewできなくなる(インスタンスが一つしか生成できない)
	private MemberServiceImpl() {};
	
        //一つしか生成できないインスタンスを外部から呼び出すメソッド
	public static MemberServiceImpl getInstance() {
		return singleton;
	}
 ...
}
  • demo/Main.java(実行するjava)
public class Main {

	public static void main(String[] args) {
		MemberServiceImpl service =MemberServiceImpl.getInstance();
		System.out.println(service.greet(2));
まっきんとっしゅまっきんとっしゅ

セクション4 1章Springで画面出力やDB操作をする

@Configurationのアノテーションで設定ファイルだということをわからせる

@ControllerAdviceをつけておくとメソッドの前に前処理をさせたい場合につける。
例えばフォームに何も入力せずにsubmitした場合から文字をnullに変換させるとか。

.sqlは自動で実行されるらしい。どこにおいても大丈夫なんかな?

build.gradleに書いたdependenciesのimplementation(<URL>)はhttps://mvnrepository.com/で検索すると出てくる。

Command + ⬆️ + O : import文を勝手に書いてくれる。
Command + ⬆️ + F : インデントを整えてくれる。

@Autowiredはnewする必要がなくなるってやつかな。インスタンスを自動生成する。単体テストをしやすくするため。

まっきんとっしゅまっきんとっしゅ

セクション5 2章 MVCアーキテクチャ

  • form.html
<form th:object="${inquiryForm}">//inquiryForm.javaのこと
<input id="name" name="name" type="text" th:value="*{name}">//*{name}でinquiryForm.javaに格納された値を取り出す
<div th:if="${#fields.hasErrors("name")}" th:errors="*{name}"> //#fieldsは暗黙オブジェクト。hasErrors(<フィールド名>)でエラーがどうかの真偽値を返す

リダイレクトではリクエストをし直すので、リクエストスコープにデータを保管しておいても次のリクエスト時には失われてしまう。model.addAttribute()だとデータが渡せない。

addFlashAttribute()はリクエストを隔ててデータを保管する仕組みであるセッションという機能を内部的に使用している。メッセージのRegisteredが表示されるとセッションが破棄される。

redirectAttributes.addFlashAttribute("complete","Registered");
return "redirect:/inquiry/form";

フラッシュスコープのデータをGetMappingで受け取る方法

@GetMapping("/form")
public String form(InquiryForm inquiryForm , Model model ,  @ModelAttribute(<addFlashAttributeで指定したキー名>)){ // ここでは"complate"
}

まっきんとっしゅまっきんとっしゅ

セッション6 3章 DAOパターンを用いたデータベース操作

Dependency Injection(DI)

ソフトウェアのデザインパターンの一つ。DIコンテナを利用することでnewすることなくDIコンテナを介してインスタンスを取得できる。

  • @Component
    • @Controller:コントローラーの役割を担うコンポーネネント
    • @Service:コントローラーから呼び出されるビジネスロジックを提供するコンポーネント
    • @Repository:データの永続化に関わる処理を提供するコンポーネント

H2データベース

  • SpringBootに内蔵されているテスト用のデータベース
  • Java製のインメモリデータベース:アプリを起動するたびに初期化される
  • src/main/resouces直下のschema.sqlとdata.sqlは自動で読み込まれる
    • schema.sql:create文
    • data.sql:insert文

DAOクラスの作成

EntityのフィールドはDBのカラムと一致させる

  1. コントローラーからサービス(ビジネスロジック)に検索用の値や保存用のEntityを渡す。
  2. サービス(ビジネスロジック)がDAOにコントローラから受け取った値などを渡す。
  3. DAOが受け取ったEntityを受け取ってDBに値を保存する。また値を受け取って検索したりする。
  4. サービスがDAOの実行結果を受け取る
  5. コントローラーがサービス(ビジネスロジック)から検索結果をEntityとして受け取る

@Repository:データベースを扱う専用のクラスだとわかるアノテーシ

@Repository
public class InquiryDaoImpl implements InquiryDao {

	private final JdbcTemplate jdbcTemplate;

	@Autowired
	public InquiryDaoImpl(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

Serviceクラス

ControllerにはfefeFormに値が詰められて値がくる。それをEntityに詰め直す。(FormとEntityの値にズレがあることがよくあるらしい。フォームの内容は2つのテーブルにまたがっている場合など)

DAOに対して命令をするのがServiceクラス

まっきんとっしゅまっきんとっしゅ

4章 例外処理

共通の例外処理

@ControllerAdviceとついたクラスは全てのコントローラーに対して共通処理を組める。

  • WebMvcControllerAdvice.java
//@Exception(<処理したいエラーの名前>.class)
@ExceptionHandler(EmptyResultDataAccessException.class)
	public String handleException(EmptyResultDataAccessException e,Model model) {
		model.addAttribute("message", e);
		return "error/CustomPage";
	}

独自の例外処理

model.addAttribute()はリクエストスコープ内で使う

  • InquiryController.java

共通の例外処理でも書くこと一緒

	@ExceptionHandler(InquiryNotFoundException.class)
	public String handleException(InquiryNotFoundException e, Model model) {
		model.addAttribute("message", e);
		return "error/CustomPage";
	}
まっきんとっしゅまっきんとっしゅ

コンポーネント化

  • Inquiry/index.html(呼び出し元)
<body>
<div th:replace="~{block/header::headerA}"></div>
// <div th:replace="~{<コンポーネントのディレクトリ>::<呼び出される側でth:fragment="<名前>"の名前>}">
  • Block/header.html(呼び出される側)
<header th:fragment="headerA">
//<header th:fragment="<呼び出し元で使う名前>">
 <nav class="~~~">
 <nav>
</header>
まっきんとっしゅまっきんとっしゅ

セッション9 5章 ToDoアプリ開発

  • TaskDao.java
Optional<Task> findById(int id);

Optionalとは何か

値をラップしてその値がnullかもしれないことを表現するクラス。
nullの場合の処理を書かないとエラーになる。つまりヌルポの発生を防ぐことができる。

String str = null;
System.out.println(str.length()); // ヌルポになる。これを防ぎたい
Optional<String> strOpt = Optional.ofNullable(str);

// OptionalはisPresent()が使えてNullか否かを判断できる。
if (strOpt.isPresent()) {
	String message = strOpt.get();
	System.out.println(message.length());
} else {
	System.out.println("nullやんけ");
}

//ラムダ式でisPresentとかもできる
strOpt.ifPresent(v -> System.out.println(v.length()));
まっきんとっしゅまっきんとっしゅ

2つ以上のSQLを命令する場合のトランザクション処理は、Serviceクラスにて行う。@Transactionalをメソッドにつける。

データベースと連動したバリデーションなどの処理もServiceクラスの役割。
例)メールアドレスの登録時に既にメールが存在するかチェックする。

queryForMap()にてデータベースのデータが1件も取得できない場合、EmptyResultDataAccessExceptionという非チェック例外が発生する。

まっきんとっしゅまっきんとっしゅ

セクション10 ToDoアプリ コントローラを作り動作を確認する

/task GET
新規登録フォームとタスク一覧を表示

/task/insert POST
タスクの新規登録

/task/{id} GET
タスク個々の編集画面を表示

/task/update POST
編集画面から1件のタスクを更新

/task/delete POST
タスク1件を削除

まっきんとっしゅまっきんとっしゅ
  • TaskForm.java
// 小数なしの一桁の数
@Digits(integer = 1 , fraction = 0) //integer = 必要な桁数 , fraction = 小数点以下の桁数
private int typeId;

@NotNull(message = "期限を設定してください。")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")//htmlから値を受け取る時にフォーマットを変更する
@Future(message = "期限が過去に設定されています。")
private LocalDateTime deadline;

html側でinputのhidden属性で送ったものはコントローラーでは
@RequestParam("taskId") String idのように@RequestParam(<input属性でhiddenにしていたname属性の値>)で取得できる。

URLパラメタの受け取り方(/?id=3みたいな)

  • TaskController.java
    @PathVariableで受け取る。
@GetMapping("/{id}")
public String showUpdate(TaskForm taskForm, @PathVariable int id , Model model)
まっきんとっしゅまっきんとっしゅ

セクション11 5章 ユニットテストと結合テスト

ユニットテスト

  • ドライバ:実際の呼び出し側の代わりにテスト対象を呼び出す(JUnitが担当する)
  • テスト対象:
  • スタブ:@Mockをつけた代替を用意する(Daoとか)

@Mockをつけることでモック化したいインターフェースを決める。メソッドの動作はテストごとにその都度指定できる。

whenメソッドモックの動作を指定する。

  • TaskServiceImplUnitTest.java
// 空のリスト
List<Task> list = new ArrayList<>();

// モッククラスのI/Oをセット(findAll()の型と異なる戻り値はNG)
when(dao.findAll()).thenReturn(list);//dalo.findAll()を実行したら空のlistが返ってくる

@InjectMocksでテスト対象を指定する

@InjectMocks
private TaskServiceImpl testServiceImpl;

タスクが取得できない場合のテスト

  • TaskServiceImplUnitTest.java
  1. whenで期待した動きを書く(ここではエラーを返す動き)
  2. 実際にServiceを動かしてエラーを発生させる
  3. assertEqualsで1.(期待した動き)と2.(実際の動き)が一致しているかを見る
@Test // テストケース
@DisplayName("タスクが取得できない場合のテスト")
// テスト名	
void testGetTaskThrowException() {
	// モッククラスのI/Oをセット
	when(dao.findById(0)).thenThrow(new EmptyResultDataAccessException(1));
	// タスクが取得できないとTaskNotFoundExceptionが発生することを検査
	try {
		Optional<Task> task0 = taskServiceImpl.getTask(0);
	} catch (TaskNotFoundException e) {
		Assertions.assertEquals(e.getMessage(), "指定されたタスクが存在しません");
	}
}

結合テスト

データの受け渡しなど連携・動作ができているか確認するテスト

さきほどはDaoに@Mockをつけてスタブを利用していたが今回は本物を使う。結合テストでは実際にDBにもアクセスするのでサーバの起動も必要になる。(@SpringBootTestのアノテーションをつける)

  • TaskServiceImplTest.java
@SpringJUnitConfig //Junit5上でSpring TestContext Frameworkを利用することを示す
@SpringBootTest //毎回サーバ起動
@ActiveProfiles("unit")//application-unit.ymlのunitを対応(DBの設定を読み込む)
@DisplayName("TaskServiceImplの結合テスト")
このスクラップは2021/09/20にクローズされました