これまでの振り返り【Spring Boot編】

初めまして。
miyaと申します。
某プログラミングスクールのJava講座のカリキュラムが一通り終了し、Spring Boot学習第一章完といった状況になったので、これまでの学びを記事として記録することにしました。
全部を書いてしまうととんでもない量になるため、約2か月半にわたる学習で特に私が勉強になったと感じたことを6個抜粋して記載していきます。

## ①リポジトリクラスでDB情報の取得
私がSpring Boot学習の中で一番感銘を受けたというか衝撃を受けたというかとにかくめっちゃ便利だなと感じたもの。
JpaRepository<>を継承させることで、対象のエンティティをSQLを定義することなくデータの抽出を可能にする。
これあるだけでSpring Bootを使う価値があると思った。
（他のフレームワークも似たようなのあるのかもしれんけど。）
私が今回使ってみたのはこんな感じ。

**List&lt;Tasks> findAllByName(String name)**
指定したnameのタスクをすべて抽出し、リストに格納する。
引数にuserを指定したとすると、name = userのタスクをすべて取り出し、リストに格納してくれる。

**List&lt;Tasks>findByDateBetween(LocalDateTime start, LocalDateTime end)**
指定したstart、endに対してstart～endまでの期間のタスクをすべて抽出し、リストに格納する。
start = 2025/03/01、end = 2025/3/31を指定したとすると、2025/03/01～2025/03/31までのタスクをすべて取り出し、リストに格納してくれる。

**List&lt;Tasks>findByDateBetweenAndName(LocalDateTime start, LocalDateTime end, String name)**
指定したstart、end間かつ指定したnameのタスクを抽出する。

タスクエンティティのコードはこんな感じです。

```Tasks.java
import java.time.LocalDateTime;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.Data;

@Data
@Entity
public class Tasks {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String title;
	private String name;
	private String text;
	private LocalDateTime date;
	private boolean done;
	

}
```

## ②Spring Bootでカレンダーを画面に表示させる方法
カレンダーからタスクを登録するというアプリ制作にあたり、カレンダー用の2次元表を作る必要があったため、LocalDateのリストのリストをまずは作成。
(1週間分のLocalDateを格納するリストを作成後、そのリストを格納するリストを作成し、1月分のリストとする感じ。)

**List&lt;LocalDate> weekList**：1週間分を格納
**List&lt;List&lt;LocalDate>> monthList**：カレンダー表示分を格納

DayOfWeekを使って日曜日～土曜日までの1週間分をweekListに格納後、weekListをmonthListに格納し、weekListを空にしてまた日曜日～土曜日までを格納して～を繰り返すという流れ。
※一部を抜粋
```MainController.java
for(int i = 0; i < daysBetween; i++) {
			nextDay = firstDay.plusDays(i);
			weekList.add(nextDay);
			nextDate = nextDay.getDayOfWeek();
			if (nextDate.getValue() == 6) {
				monthList.add(weekList);
				weekList = new ArrayList<>();
			}
```
縦5×横7のカレンダーとするため、初週で前月分、最終週で次月分が一部必要となるため、同じくDayOfWeekを使って取得。
（初日、最終日が何曜日かによって、取得する日数が異なるため、if文で場合分けした。）
※一部を抜粋
```MainController.java
LocalDate zengetsuDay = firstDay;
		LocalDate zengetsuDayofMaster = firstDay;
		DayOfWeek zengetsuDate = zengetsuDay.getDayOfWeek();
		int zengetsuValue = zengetsuDate.getValue();
		
		int j;
		//当月初日が月曜日の場合
		if(zengetsuValue == 1) {
			j = 1;
			zengetsuDayofMaster = firstDay.minusDays(j);
			while(j > 0) {
				zengetsuDay = firstDay.minusDays(j);
				//先月分(1日分)をweekListに格納
				weekList.add(zengetsuDay);
				j--;
			}
		//当月初日が火曜日の場合
		} else if(zengetsuValue == 2) {
			j = 2;
			zengetsuDayofMaster = firstDay.minusDays(j);
			while(j > 0) {
				zengetsuDay = firstDay.minusDays(j);
				//先月分(2日分)をweekListに格納
				weekList.add(zengetsuDay);
				j--;
			}
		//当月初日が水曜日の場合
		} else if(zengetsuValue == 3) {
			j = 3;
			zengetsuDayofMaster = firstDay.minusDays(j);
			while(j > 0) {
				zengetsuDay = firstDay.minusDays(j);
				//先月分(3日分)をweekListに格納
				weekList.add(zengetsuDay);
				j--;
			}
		//当月初日が木曜日の場合
		} else if(zengetsuValue == 4) {
			j = 4;
			zengetsuDayofMaster = firstDay.minusDays(j);
			while(j > 0) {
				zengetsuDay = firstDay.minusDays(j);
				//先月分(4日分)をweekListに格納
				weekList.add(zengetsuDay);
				j--;
			}
		//当月初日が金曜日の場合
		} else if(zengetsuValue == 5) {
			j = 5;
			zengetsuDayofMaster = firstDay.minusDays(j);
			while(j > 0) {
				zengetsuDay = firstDay.minusDays(j);
				//先月分(5日分)をweekListに格納
				weekList.add(zengetsuDay);
				j--;
			}
		//当月初日が土曜日の場合
		} else if(zengetsuValue == 6) {
			j = 6;
			zengetsuDayofMaster = firstDay.minusDays(j);
			while(j > 0) {
				zengetsuDay = firstDay.minusDays(j);
				//先月分(6日分)をweekListに格納
				weekList.add(zengetsuDay);
				j--;
			}
		//当月初日が日曜日の場合
		} else if(zengetsuValue == 7) {
			//先月分の格納は無し
		}
```
## ③datetime型のDB情報をSpring Bootで登録する方法
DBテーブルにデータを登録しようとしたところ、登録エラー。どうしてもわからずメンターさんに質問したとこ。
テーブルにdatetime型を登録するように設定したカラムはjavaではLocalDateTime型にして登録するようにしないといけなかったんだと。
登録済みのタスクを画面から編集するため、タスク情報を取ってくる必要があるが、取ってきてそのままの状態だとLocalDate型のため、コントローラクラスで登録時にLocalDateTime型に変換する必要があるという。
変換には、00:00:00をデフォルトで登録してくれるatStartOfDayメソッドを使いました。

## ④URLパス情報をコントローラで受け取る方法
これも中々感銘を受けたもの。
既存タスク編集するためにURLパスの情報をコントローラで受け取りたいけど方法がわからない！タスク登録するときと同じようにフォームクラス作って受け取るしかないのか。。？と考えていたところ、chatGPT様からナイスな回答を頂きました。
@PathVariableというアノテーションをURLパスを受け取りたい引数の前に入れることで、その引数にURLパスが入るという仕組み。
Spring Bootって便利やね。。。
```EditController.java
@PostMapping("/main/edit/{id}")
	public String edit(@PathVariable("id") Long id, @Validated TaskEditForm taskEditForm, 
			@AuthenticationPrincipal AccountUserDetails user, Model model) {
		
		Tasks task = new Tasks();
		String title = taskEditForm.getTitle();
		if(title == null) {
			throw new IllegalArgumentException();
		}
		
		String name = user.getName();
		String text = taskEditForm.getText();
		if(text == null) {
			throw new IllegalArgumentException();
		}
		
		LocalDate date = taskEditForm.getDate();
		LocalDateTime localDateTime = date.atStartOfDay();
		if(localDateTime == null) {
			throw new IllegalArgumentException();
		}
		
		boolean done = taskEditForm.isActive();
		
		task.setId(id);
		task.setTitle(title);
		task.setName(name);
		task.setText(text);
		task.setDate(localDateTime);
		task.setDone(done);
		
		tasksRepository.save(task);
		return "redirect:/main";
	}
```
## ⑤ログインユーザにおける表示範囲の選択
タスクアプリの要件として、adminユーザでログインした場合は、adminユーザ含むすべてのユーザが登録したタスクの確認、編集、削除がすべて行えること、それ以外のユーザでログインした場合はログインしたユーザが登録したタスクのみ確認、編集、削除を行えるというものがあり、
「adminユーザ」と「それ以外」という切り分けはログインユーザ名さえ取得できればよかったのですが、「ログインしたユーザが登録したタスク」を抽出し、それらをすべてリストに格納するというのが①で書いたリポジトリクラスを使う必要がありました。
また、「ログインしたユーザが登録したタスク」をすべて抽出してしまうと、タスクアプリの運用が1年以上経ったとき、取ってくるタスクの量が膨大になるため、カレンダーに表示するひと月分に絞ってタスクを登録するようメンターさんより、指摘がありました。これも①で書いたリポジトリを応用すれば簡単に取得できました。
書いてしまえばそれだけなのですが、当時はどうするのかかなり悩みながらやってました。。。
```MainController.java
if(user.getName().equals("admin-name")) {
    		Map<LocalDate, List<Tasks>> tasksGroupedByDay = getTasksGroupedByDay(zengetsuDateTimeofMater, lastSaturdateTime);
    		model.addAttribute("tasks", tasksGroupedByDay);
    	} else {
    		Map<LocalDate, List<Tasks>> tasksGroupedByName = getTasksGroupedByName(zengetsuDateTimeofMater, lastSaturdateTime, user.getName());
    		model.addAttribute("tasks", tasksGroupedByName);
    	}
```

## ⑥date要素を持つモデルに対して、日付ごとにグループ化する方法
タスクアプリ制作において一番難しいとこなんじゃないかと感じているところ。
以下のhtmlファイルをもとにjavaコードを書くよう提示されていたのですが
```main.html
<div th:each="t : ${tasks.get(day)}">
       <a th:if="${t}" th:href="@{/main/edit/{id}(id=${t.id})}"> <span
        th:if="${t.done}">✅</span><span th:text="${t.title}"></span></a>
      </div>
```
単にTasksのリストをtasksに渡せばいいだろうと思っていたら全く通らない。
たまらずメンターさんにきいてみたところ「タスクに日付が紐づいていない」と回答頂きました。
これは、Tasksのリストを、Tasksが持つ要素でもあるLocalDateと紐づける必要があるため、tasksには、Map&lt;LocalDate, List&lt;Tasks>>を渡さなければいけないということでした。
これは初見じゃわからない。。。
```MainController.java
@Autowired
	private TasksRepository tasksRepository;
	
	public Map<LocalDate, List<Tasks>> getTasksGroupedByDay(LocalDateTime firstDay, LocalDateTime lastSaturday) {
        // タスクのリストを取得
		List<Tasks> tasks = tasksRepository.findByDateBetween(firstDay, lastSaturday);

        // dateフィールドを基に日付でグループ化
        return tasks.stream()
                .collect(Collectors.groupingBy(task -> task.getDate().toLocalDate()));
    }
	
	public Map<LocalDate, List<Tasks>> getTasksGroupedByName(LocalDateTime firstDay, LocalDateTime lastSaturday, String name) {
		List<Tasks> tasks = tasksRepository.findByDateBetweenAndName(firstDay, lastSaturday, name);
		
		// dateフィールドを基に日付でグループ化
        return tasks.stream()
                .collect(Collectors.groupingBy(task -> task.getDate().toLocalDate()));
	}
    .
    .
    .
    if(user.getName().equals("admin-name")) {
    		Map<LocalDate, List<Tasks>> tasksGroupedByDay = getTasksGroupedByDay(zengetsuDateTimeofMater, lastSaturdateTime);
    		model.addAttribute("tasks", tasksGroupedByDay);
    	} else {
    		Map<LocalDate, List<Tasks>> tasksGroupedByName = getTasksGroupedByName(zengetsuDateTimeofMater, lastSaturdateTime, user.getName());
    		model.addAttribute("tasks", tasksGroupedByName);
    	}
```