[Spring Boot2.4.2] Thymeleafを使おう

SpringBootのサポートするテンプレートエンジン
– Groovy, Thymeleaf, FreeMaker, Mustache

Tymeleaf利点
– 独自タグなし、ブラウザ表示

src/main/resources/templates/index.html

<!Doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>SpringBoot</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta charset="UTF-8">
<style type="text/css">
  form {
  	border-style: solid;
  	border-color: black;
  	border-width: 1px;
  	margin: 5px;
  	padding: 10px;
  }
</style>
</head>
<body>
<h1 th:text="'これはTypeleafですか?'">html-見出し1</h1>
Spring bootで推奨されています。
</body>
</html>

Run As -> SpringBoot app
http://localhost:8080/

WhatTimeIsItController.java

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("view/what-time-is-it")
@Controller
public class WhatTimeIsItController {

}

– @RequestMappingを設定
– Controllerで動的な値を生成
– 設定した動的な値をThymeleafのviewで参照

what time is it

package com.example.demo;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("view/what-time-is-it")
@Controller
public class WhatTimeIsItController {
	
	@GetMapping()
	public String view(Model model) {
		
		String now = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
		
		model.addAttribute("datetime", now);
		return "wtii"; // viewを返す
	}

}

Run As -> SpringBoot app

やべ、だいぶわかってきた。
ちょっと理解すると、アプリ作りたい衝動が抑えられなくなってくる

MVC:ControllerがMVC:Viewにデータを渡すのにui:Modelを使用する
model.addAttribute(“”,)で値を設定している
SpEL(Spring Expression Language)とうい方式を使っている
${変数式}、*{選択変数式}、#{メッセージ式}、@{リンク式} などがある

[Spring Boot2.4.2] API取得

pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

WebApiController.java

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.apache.commons.text.StringEscapeUtils;


@RestController
@RequestMapping("/api")
public class WebApiController {
	
	@RequestMapping(value="weather/tokyo"
			, produces=MediaType.APPLICATION_JSON_VALUE
			, method=RequestMethod.GET)
	private String call() {
		RestTemplate rest = new RestTemplate();
		
		final String cityCode = "130010";
		final String endpoint = "http://weather.livedoor.com/forecast/webservice/json/v1";
	    
		final String url = endpoint + "?city=" + cityCode;
		
		ResponseEntity<String> response = rest.getForEntity(url, String.class);
		
		String json = response.getBody();
		
		return decode(json);
	}
	
	private static String decode(String string) {
		return StringEscapeUtils.unescapeJava(string);
	}
	
}

うお、Jsonとして機能してないが、やりたいことやpom.xmlの使い方、importの意味などはわかってきた。
早くデータアクセスに行きたい。

[Spring Boot2.4.2] 戻り値

String以外の戻り値にしてみる
java beanはデータを出し入れする倉庫

@RestController
@RequestMapping("/api")
public class WebApiController {
	private static final Logger log = LoggerFactory.getLogger(WebApiController.class);
	
	
	public static class HogeMogeBean {
		public HogeMogeBean(String string, int i) {
			// TODO Auto-generated constructor stub
		}
    }

    @RequestMapping("hogemoge")
    public HogeMogeBean hogemoge() {
        return new HogeMogeBean( "ほげ", 1234 );
    }
}

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sun Jan 31 19:21:10 JST 2021
There was an unexpected error (type=Internal Server Error, status=500).

$ curl localhost:8080/api/hogemoge
{“timestamp”:”2021-01-31T10:23:31.543+00:00″,”status”:500,”error”:”Internal Server Error”,”message”:””,”path”:”/api/hogemoge”}

なんやこれは。。
いきなりよくわからん。

[Spring Boot2.4.2] 新しいプロジェクトを作る

まず、Spring Starter Projectでプロジェクトを作ったら、packageを作ります。
com.example.demo.controller

WebApiController.java

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
@RequestMapping("api")
public class WebApiController {
	
	@RequestMapping("hello")
	private String hello() {
		return "SpringBoot!";
	}
}

あれ、さっきgradleで作った時はRestControllerではなくRequestMethodだったけど、今回はRestControllerか。。ん、requestMappingってパスの事か。

application.properties

server.port=8080

Run As -> SpringBoot app
http://localhost:8080/api/hello

ほう、なるほど

パスパラメータ
-> requestMappingで仕込んで使用する

@RequestMapping("/hello/{param}")
	private String hello(@PathVariable("hoge") String param) {
		return "SpringBoot!";
	}
package com.example.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/sample/api")
public class WebApiController {
	private static final Logger log = LoggerFactory.getLogger(WebApiController.class);
	
	@RequestMapping("/test/{param}")
	private String testPathVariable(@PathVariable String param) {
		log.info(param);
		return "受け取ったパラメータ:" + param;
	}
	
	@RequestMapping("/test")
	private String testRequestParam(@RequestParam() String param) {
		log.info(param);
		return "受け取ったパラメータ:" + param;
	}
	
	@RequestMapping(value = "/test", method = RequestMethod.POST)
	private String testRequestBody(@RequestBody String body) {
		log.info(body);
		return "受け取ったbody:" + body;
	}
}

$ lsof -i:8080
$ kill hoge
Run As -> SpringBoot app
http://localhost:8080/sample/api/test/firstparam

なにこれ? GetParameter実装するの凄い簡単なのに、こんなにコード書かなきゃいけないの。。。

[Spring Boot] HelloWorldから始める

New Spring Starter Project

次の画面で、TymeleafとWebを選択

# MarvenとGradle
### Marven
– POM (Project Od Model) の考え方に基づく。
– ビルドファイルは、pom.xml
– プラグインによる拡張が可能

### Gradle
– AndroidStudioデフォルト
– 依存関係はGroovyに書いている

Run As -> SpringBoot app
. ____ _ __ _ _
/\\ / ___’_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
‘ |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.2)

### HTMLの作成
hello-world/src/main/resources/templates
index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Hello SpringBoot Sample</h1>
</body>
</html>

### Controllerの作成

HelloController.java

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HelloController {
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String index(Model model) {
		return "index";
	}
}

Run As -> SpringBoot app

やべえ、なんか出来てる….

[Spring Boot][java] ログイン機能

Spring Securityを使用する
pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity4</artifactId>
		</dependency>

ユーザ取得処理の実装
ReservationUserDetails.java

package mrs.domain.service.user;

import java.util.Collection;
import mrs.domain.model.User;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.secuirty.core.authority.AuthorityUtils;
import org.springframework.secuirty.core.userdetails.UserDetails;

public class ReservationUserDetails implements UserDetails {
	private final User user;
	
	public ReservationUserDetails(User user) {
		this.user = user;
	}
	
	public User getUser() {
		return user;
	}
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities(){
		return AuthorityUtils.createAuthorityList("ROLE_" + this.user.getRoleName().name());
	}
	
	@Override
	public String getPassword() {
		return this.user.getPassword();
	}
	
	@Override
	public String getUsername() {
		return this.user.getUserId();
	}
	
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}
	
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}
	
	@Override
	public boolean isCredentialsExpired() {
		return true;
	}
	
	@Override
	public boolean isEnabled() {
		return true;
	}
}

ん、認証って、一つ一つ書くんか。

UserRepository.java

package mrs.domain.repository.user;

import org.springframework.data.jpa.repository.JpaRepository;

import mrs.domain.model.User;

public interface UserRepository extends JpaRepository<User, String>{
	
}

ReservationUserDetailsService.java

package mrs.domain.service.user;

import mrs.domain.model.User;
import mrs.domain.repository.user.UserRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class ReservationUserDetailsService implements UserDetailsService {
	@Autowired
	UserRepository userRepository;
	
	@Override
	public UserDetails loadUserByUsername(String username)
		throws UsernameNotFoundException {
		User user = userRepository.findOne(username);
		if(user == null) {
			throw new UsernameNotFoundException(username + " is not found.");
		}
		return new ReservationUserDetails(user);
	}
}

LoginController.java

package mrs.app.login;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {
	@RequestMapping("loginForm")
	String loginForm() {
		return "login/loginForm";
	}
}

プロジェクトルートで ./mvnw package で.jarファイルを作成する
うむ、何がわかってないかがわかってない状態だ。

[Spring Boot][java] app開発4

ReservationsController.java

@RequestMapping(method = RequestMethod.POST)
	String reserve(@Validated ReservationForm form, BindingResult bindingResult,
			@DateTimeFormat(iso=DateTimeFormat.ISO.DATE) @PathVariable("date") LocalDate date,
			@PathVariable("roomId") Integer roomId, Model model) {
		if(bindingResult.hasErrors()) {
			return reserveForm(date, roomId, model);
		}
		
		ReservableRoom reservableRoom = new ReservableRoom(
				new ReservableRoomId(roomId, date));
		Reservation reservation = new Reservation();
		reservation.setStarttime(form.getStartTime());
		reservation.setEndTime(form.getEndTime());
		reservation.setReservableRoom(reservableRoom);
		reservation.setUser(dummyUser());
		
		try {
			reservationService.reserve(reservation);
		}
		catch (UnavailableReservationException | AlreadyReservedException e) {
			model.addAttribute("error", e.getMessage());
			return reserveForm(date, roomId, model);
		}
		return "redirect:/reservations/{date}/{roomId}";
	}
	
	@RequestMapping(method = RequestMethod.POST, params = "cancel")
	String cancel(@RequestParam("reservationId") Integer reservationId,
			@PathVariable("roomId") Integer roomId,
			@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @PathVariable("date") LocalDate date,
			Model model) {
		User user = dummyUser();
		try {
			reservationService.cancel(reservationId, user);
		}
		catch (IllegalStateException e) {
			model.addAttribute("error", e.getMessage());
			return reserveForm(date, roomId, model);
		}
		return "redirect:/reservations/{date}/{roomId}";
	}

ThirtyMinutesUnit.java

package mrs.app.reservation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.*;

import javax.validation.*;

@Documented
@Constraint(validatedBy = {ThirtyMinutesUnitValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RINTIME)
public @interface ThirtyMinutesUnit {
	String message() default "{mrs.app.reservation.ThirtyMinutesUnit.message}";
	
	Class<?>[]groups() default {};
	
	Class<? extends Playload>[]playload() default {};
	
	@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
	@Retention(RUNTIME)
	@Documented
	public @interface List {
		ThirtyMinutesUnit[]value();
	}
}

ThirtyMinutesUnitValidator.java

package mrs.app.reservation;

import java.time.LocalTime;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class ThirtyMinutesUnitValidator
	implements ConstraintValidator<ThirtyMinutesUnit, LocalTime>{
	@Override
	public void initialize(ThirtyMinutesUnit constraintAnnotation) {
		
	}
	
	@Override
	public boolean isValid(LocalTime value, ConstraintValidatorContext context) {
		if(value == null) {
			return true;
		}
		return value.getMinute() % 30 == 0;
	}
}

EndTimeMuustBeAfterStartTime.java

package mrs.app.reservation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.*;

import javax.validation.*;

@Documented
@Constraint(validatedBy = {EndTimeMustBeAfterStartTimeValidator.class})
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface EntTimeMustBeAfterStartTime{
	String message() default "{mrs.app.reservation.EndTimeMustBeAfterStartTime.message}";
	
	Class<?>[]groups() default{};
	
	Class<? extends Playload>[]payload() default {};
	
	@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
	@Retention(RUNTIME)
	@Documented
	public @interface List {
		EndTimeMustBeAfterStartTime[]value();
	}
}

EndTimeMustBeAfterStartTimeValidator.java

package mrs.app.reservation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class EndTimeMustBeAfterStartTimeValidator
	implements ConstraintValidator<EndTimeMustBeAfterStartTime, ReservationForm>{
	private String message;
	
	@Override
	public void initialize(EndTimeMustBeAfterStartTime constraintAnnotation) {
		message = constraintAnnotation.message();
	}
	
	@Override
	public boolean isValid(ReservationForm value, ConstraintValidatorContext context) {
		if(value.getStarttime() == null || value.getEndTime() == null) {
			return true;
		}
		boolean isEndTimeMustBeAfterStartTime = value.getEndTime().isAfter(value.getStartTime());
		if(!isEndTimeMustBeAfterStartTime) {
			context.disableDefaultConstraintViolation();
			context.buildConstraintViolationWithTemplate(message).addPropertyNode("endTime").addConstraintViolation();
		}
		return isEndTimeMustBeAfterStartTime;
	}
}

うーむ、ちょっと混乱してきたな。

[Spring Boot][java] app開発3

SpringBootのリポジトリとは?
-> DBへのアクセスを自動で作成する便利インターフェース
-> DBへのCRUDを自動で実装してくれる
なるほど

Listとは?
-> Javaのリスト(List)とは、重複した要素を含むことができる順序の付けられたコレクション
-> 配列とは異なる

ReservationRepository.java

package mrs.domain.repository.reservation;

import java.util.List;

import mrs.domain.model.ReservableRoomId;
import mrs.domain.model.Reservation;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ReservationRepository extends JpaRepository<Reservation, Integer>{
	List<Reservation> findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(
			ReservableRoomId reservableRoomId);
}

MeetingRoomRepository.java

package mrs.domain.repository.room;

import org.springframework.data.jpa.repository.JpaRepository;

import mrs.domain.model.MeetingRoom;

public interface MeetingRoomRepository extends JpaRepository<MeetingRoom, Integer>{
	
}

Serviceとは?
-> ビジネスロジックを実装するクラス
-> Controllerはリクエストの窓口になるクラス
ん、ビジネスロジックはcontrollerではなく、serviceで書くのか。ちょっと混乱するな。

ReservationService.java

package mrs.domain.service.reservation;

import java.util.List;

import mrs.domain.model.*;
import mrs.domain.repository.reservation.ReservationRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class ReservationService {
	@Autowired
	ReservationRepository reservationRepository;
	
	public List<Reservation> findReservations(ReservableRoomId reservableRoomId){
		return reservationRepository.findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(
				reservableRoomId);
	}
}

Serviceの中のpublic List<*>がJavaの構文でないような印象だが、どういう事なんだ?

package mrs.domain.service.reservation;

import java.util.List;

import mrs.domain.model.*;
import mrs.domain.repository.reservation.ReservationRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class ReservationService {
	@Autowired
	ReservationRepository reservationRepository;
	@Autowired
	ReservableRoomRepository reservableRoomRepository;
	
	public Reservation reserve(Reservation reservation) {
		ReservableroomId reservableRoomId = reservation.getReservableRoom().getReservableRoomId();
		ReservableRoom reservable = reservableRoomRepository.findOne(reservableRoomId);
		if(reservable == null) {
			
		}
		boolean overlap = reservationRepository.findByReservableRoom_ReservableRoomIdOrderByStartTimeAsc(reservableRoomId)
				.stream()
				.anyMatch(x -> x.overlap(reservation));
		if(overlap) {
			
		}
		reservationRepository.save(reservation);
		return reservation;
	}

ReservationsController.java

package mrs.app.reservation;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import mrs.domain.model.*;
import mrs.domain.service.reservation.*;
import mrs.domain.service.room.RoomService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("reservations/{date}/{roomId}")
public class ReservationsController {
	@Autowired
	RoomService roomService;
	@Autowired
	ReservationService reservationService;
	
	@ModelAttribute
	ReservationForm setUpForm() {
		RservationForm form = new ReservationForm();
		form.setStartTime(LocalTime.of(9, 0));
		form.setEndTime(LocalTime.of(10, 0));
		return form;
	}
	
	@RequestMapping(method = RequestMethod.GET)
	String reserveForm(@DateTimeFormat(iso=DateTimeFormat.ISO.DATE) @PathVariable("date") LocalDate date,
			@PathVariable("roomId") Integer roomId, Model model) {
		ReservableRoomId reservableRoomId = new ReservableRoomId(roomId, date);
		List<Reservation> reservations = reservationService.findReservations(reservableRoomId);
		
		List<LocalTime> timeList =
				Stream.iterate(LocalTime.of(0, 0), t -> t.plusMinutes(30))
				.limit(24 * 2)
				.collect(Collectors.toList());
		
		model.addAttribute("room", roomService.findMeetingRoom(roomId));
		model.addAttribute("reservations", reservations);
		model.addAttribute("timeList", timeList);
		model.addAttribute("user", dummyUser());
		return "reservation/reserveForm";
	}
	
	private User dummyUser() {
		User user = new User();
		user.setUserId("taro-yamada");
		user.setFirstName("taro");
		user.setLastName("Yamada");
		user.setRoleName(RoleName.USER);
		return user;
	}
}
package mrs.app.reservation;

import java.io.Serializable;
import java.time.LocalTime;

import javax.validation.constraints.NotNull;

import org.springframework.format.annotation.DateTimeFormat;

public class ReservationForm implements Serializable {
	@NotNull(message = "必須です")
	@DateTimeFormat(pattern = "HH:mm")
	private LocalTime startTime;
	
	@NotNull(message = "必須です")
	@DateTimeFormat(pattern = "HH:mm")
	private Localtime endTime;
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title th:text="|${#temporals.format(date, 'yyyy/M/d')}の${room.roomName}|">2021/01/29の豊洲</title>
</head>
<body>

<div>
<a th:href="@{'/rooms/' + ${date}">会議室一覧へ</a>
</div>

<form th:object="${reservationForm}"
	th:action="@{'/reservations/' + ${date} + '/' + ${roomId}}" method="post">
	会議室: <span th:text="${room.roomName}">銀座</span>
	<br>
	予約者名: <span th:text="${user.lastName + ' ' + user.firstName}">山田太郎</span>
	// 省略

うーむ、全体的な流れを掴むには良いが、もう少し入門的なところからやりたい。。

[Spring Boot][java] app開発2

src/main/resources/schema.sql

DROP TABLE IF EXISTS meeting_room CASCADE;
DROP TABLE IF EXISTS reservable_room CASCADE;
DROP TABLE IF EXISTS reservation CASCADE;
DROP TABLE IF EXISTS usr CASCADE;

CREATE TABLE IF NOT EXIST meeting_room(
	room_id SERIAL NOT NULL,
	room_name VARCHAR(255) NOT NULL,
	PRIMARY KEY (room_id)	
);


CREATE TABLE IF NOT EXIST reservable_room(
	reserved_data DATE NOT NULL,
	room_id INT4 NOT NULL,
	PRIMARY KEY(reserved_date, room_id)
);

CREATE TABLE IF NOT EXIST meeting_room(
	reservation_id SERIAL NOT NULL,
	end_tim TIME NOT NULL,
	start_time TIME NOT NULL,
	reserved_date DATE NOT NULL,
	room_id INT4 NOT NULL,
	user_id VARCHAR(255) NOT NULL,
	PRIMARY KEY(reservation_id)
);

CREATE TABLE IF NOT EXIST meeting_room(
	user_id VARCHAR(255) NOT NULL,
	first_name VARCHAR(255) NOT NULL,
	last_name VARCHAR(255) NOT NULL,
	password VARCHAR(255) NOT NULL,
	role_name VARCHAR(255) NOT NULL,
	PRIMARY KEY (user_id)
);

repository class

package mrs.domain.repository.room;

import java.time.LocalDate;
import java.util.List;

import mrs.domain.model.ReservableRoom;
import mrs.domain.model.ReservableRoomId;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ReservableRoomRepository extends JpaRepository<ReservableRoom, ReservableRoomId>{
	List<ReservableRoom> findByReservableRoom_reservedDateOrderByReservableRoomId_roomIdAsc(LocalDate reservedDate);
}

-> importで先ほど作ったmodelを読み込んでますね。なるほど、少しわかってきた。

domain/service/RoomService.java

package mrs.domain.repository.room;

import java.time.LocalDate;
import java.util.List;

import mrs.domain.model.ReservableRoom;
import mrs.domain.repository.room.ReservableRoomRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class RoomService {
	
	@Autowired
	ReservableRoomRepository reservableRoomRepository;
	
	public List<ReservableRoom> findReservableRooms(LocalDate date){
		return reservableRoomRepository.findByReservableRoomId_reservedDateOrderByReservableRoomId_roomIdAsc(date);
	}
}

app/room/RoomsController.java

package mrs.app.room;

import java.time.LocalDate;
import java.util.List;

import mrs.domain.model.ReservableRoom;
import mrs.domain.service.room.RoomService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("rooms")
public class RoomsController {
	@Autowired
	RoomService roomService;
	
	@RequestMapping(method = RequestMethod.GET)
	String listRooms(Model model)
		LocalDate today = LocalDate.now();
		List<ReservableRoom> rooms = roomService.findReservableRooms(today);
		model.addAttribute("date", today);
		model.addAttribute("rooms", rooms);
		return "room/listRooms";
}

-> ん? ControllerのRequestMappingでクエリ検索してる?

/main/resources/templates/room/listRooms.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title th:text="|${#temporals.format(date, 'yyyy/M/d')}の会議室|">2021/01/29の会議室</title>
</head>
<body>
<h3>会議室</h3>
<a th:href="@{'/rooms/' + ${date.minusDays(1)}}">&lt; 前日</a>
<span th:text="|${#temporals.format(date, 'yyyy/M/d')}の会議室|">2021/01/29の会議室</span>
<a th:href="@{'/rooms/' + ${date.plusDays(1)}}">翌日 &gt;</a>

<ul>
	<li th:each="room: ${rooms}">
		<a th:href="@{'/reservations/' + ${date}+ '/' + ${room.meetingRoom.roomId}}"
		 th:text="${room.meetingRoom.roomName}"></a>
	</li>
</ul>
</body>
</html>

-> thymeleafはthで変数入れてますね。th:eachはforeachっぽい。

RoomsController

	String listRooms(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @PathVariable("date") LocalDate date, Model model)

規約が多い印象だが、想像してたより複雑ではなさそう。。

[Spring Boot][java] データアクセス

pom.xml

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

jdbcTemplate使用
MessagesController.java

package com.example.demo;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("messages")
public class MessagesController {
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@RequestMapping(method = RequestMethod.GET)
	public List<Message> getMessages(){
		return jdbcTemplate.query("SELECT text FROM messages ORDER BY id", (rs, i)->{
			Message m = new Message();
			m.setText(rs.getString("text"));
			return m;
		});
	}
	
	@RequestMapping(method = RequestMethod.POST)
	public Message postMessage(@RequestBody Message message) {
		jbdcTemplate.update("INSERT INTO messages(text) VALUES (?)", message.getText());
		
	}
}

spring bootはクラスパス直下にschema.sqlが存在すると、起動時にそのsqlを実行する

CREATE TABLE messages (
	id INT PRIMARY KEY AUTO_INCREMENT,
	text VARCHAR(255)
);

### PostgreSQLを使う方法

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>

application.properties

string.datasource.username=root
string.datasource.password=hoge
spring.datasource.url=jdbc:postgresql://localhost:5432/spring

mysqlを使用する場合は、schema.sqlを用意するのが定石か