Spring徹底入門のバージョンアップメモ

SpringFrameworkを学ぶには良書と呼ばれる「Spring徹底入門」。
チュートリアルを実装しながら学ぼうとするも、
自分の環境とは異なってつまずくことがあったのでメモしておく。
わかる範囲で直してはみたものの、まだあるかも・・・？

## 環境
|                                  | 本         | 自分の環境 |
|:---------------------------------|:----------:|:--------:|
| Spring Framework                 | 4.2.6      | 5.2.3    |
| Spring Boot                      | 1.3.5      | 2.2.4    |
| Thymeleaf                        | 2.1.0      | 3.0.4    |
| thymeleaf-extras-springsecurity4 | 2.1.2      |    -     |
| thymeleaf-extras-springsecurity5 |   -        | 3.0.4    |
| SQL 　※これは個人的な環境の問題で変更  | PostgreSQL | MySQL    |


## 変更内容

### プロパティファイルの設定

p.643
MySQL用に変更

``` application.properties
spring.jpa.database=MYSQL
spring.datasource.url=jdbc:mysql://localhost:3306/spring_tutorial
spring.datasource.username=*****
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.format_sql=true
spring.datasource.sql-script-encoding=UTF-8
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.datasource.separator=/;
```
data.sql、schema.sqlもMySQL用に変更。

### インジェクションの変更
フィールドインジェクションをコンストラクタインジェクションに変更。
コンストラクタインジェクションは、3つあるDI方法の中で最も推奨されている方法。
記述は増えるが、フィールドをfinal化(Immutable)できる。

コンストラクタがクラス内に１つの場合、@Autowiredは省略可能(下記では省略している)

#### サービスクラス
``` RoomService.java
 // 変更前
 @Autowired
 ReservableRoomRepository reservableRoomRepository;
	
 @Autowired
 MeetingRoomRepository meetingRoomRepository;

 // 変更後
 private final ReservableRoomRepository reservableRoomRepository;
 private final MeetingRoomRepository meetingRoomRepository;
	
 public RoomService(ReservableRoomRepository reservableRoomRepository, 
				MeetingRoomRepository meetingRoomRepository) {
	this.reservableRoomRepository = reservableRoomRepository;
	this.meetingRoomRepository = meetingRoomRepository;
 }
```

```ReservationUserDetailsService.java
 // 変更前
 @Autowired
 UserRepository userRepository;

 // 変更後
 private final UserRepository userRepository;
	
 public ReservationUserDetailsService(UserRepository userRepository) {
	this.userRepository = userRepository;
 }
```

```ReservationService.java
 // 変更前
 @Autowired
 ReservationRepository reservationRepository;

 @Autowired
 ReservableRoomRepository reservableRoomRepository;

 // 変更後
 private final ReservationRepository reservationRepository;
 private final ReservableRoomRepository reservableRoomRepository;

 public ReservationService(ReservationRepository reservationRepository, 
			ReservableRoomRepository reservableRoomRepository) {
	this.reservationRepository = reservationRepository;
	this.reservableRoomRepository = reservableRoomRepository;
 }	
```

#### コントローラークラス

``` RoomsController.java
 // 変更前
 @Autowired
 RoomService roomService;

 // 変更後
 private final RoomService roomService;

 public RoomsController(RoomService roomService) {
	this.roomService = roomService;
 }
```

``` ReservationsController.java
 // 変更前
 @Autowired
 RoomService roomService;

 @Autowired
 ReservationService reservationService;

 // 変更後
 private final RoomService roomService;
 private final ReservationService reservationService;

 public ReservationsController(RoomService roomService, 
				ReservationService reservationService) {
	this.roomService = roomService;
	this.reservationService = reservationService;
 }
```

### リクエストマッピングの変更
@RequestMappingをそれぞれのHTTPメソッドに対応するアノテーションに変更
"path ="は省略可能

#### コントローラークラス

``` RoomsController.java
 // 変更前
 @RequestMapping(method = RequestMethod.GET)
 String listRooms(Model model) {
     ・・・
 }

 @RequestMapping(path = "{date}", method = RequestMethod.GET)
 String listRooms(
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 
	@PathVariable("date") LocalDate date, Model model) {
     ・・・
 }

 // 変更後
 @GetMapping
 String listRooms(Model model) {
     ・・・
 }

 @GetMapping("{date}")
 String listRooms(
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 
	@PathVariable("date") LocalDate date, Model model) {
     ・・・
 }
```

``` ReservationsController.java
 // 変更前
 @RequestMapping(method = RequestMethod.GET)
 String reserveForm(
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
	@PathVariable("date") LocalDate date,
	@PathVariable("roomId") Integer roomId, Model model) {
     ・・・
 }

 @RequestMapping(method = RequestMethod.POST)
 String reserve(@Validated ReservationForm form, BindingResult bindingResult,
	@AuthenticationPrincipal ReservationUserDetails userDetails,
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
	@PathVariable("date") LocalDate date,
	@PathVariable("roomId") Integer roomId, Model model) {
     ・・・
 }

 @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) {
     ・・・
 }

 // 変更後
 @GetMapping
 String reserveForm(
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
	@PathVariable("date") LocalDate date,
	@PathVariable("roomId") Integer roomId, Model model) {
     ・・・
 }

 @PostMapping
 String reserve(@Validated ReservationForm form, BindingResult bindingResult,
	@AuthenticationPrincipal ReservationUserDetails userDetails,
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
	@PathVariable("date") LocalDate date,
	@PathVariable("roomId") Integer roomId, Model model) {
     ・・・
 }

 @PostMapping(params = "cancel")
 String cancel(
	@RequestParam("reservationId") Integer reservationId,
	@PathVariable("roomId") Integer roomId,
	@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
	@PathVariable("date") LocalDate date, Model model) {
     ・・・
 }
```
### Thymeleaf2 から Thymeleaf3に変更
Thymeleaf3に合わせて、HTML形式に変更
(Thymeleaf2はXHTML形式)
閉じタグ"/"がなくてもエラーにならない。

``` loginForm.html
 // 変更前
<!DOCTYPE html>
<html xmlns:th = "http://www.thymelaef.org">
<head>
<meta charset = "UTF-8" />
<title></title>
</head>

     ・・・

<td>
    <input type = "text" id = "username" name = "username" value = "aaaa" />
</td>

     ・・・

<td>
<input type = "password" id = "password" name = "password" value = "demo" />
</td>

     ・・・



 // 変更後
<!DOCTYPE html>
<html xmlns:th = "http://www.thymelaef.org">
<head>
<meta charset = "UTF-8">
<title></title>
</head>

     ・・・

<td>
    <input type = "text" id = "username" name = "username" value = "aaaa">
</td>

     ・・・

<td>
<input type = "password" id = "password" name = "password" value = "demo">
</td>

     ・・・


```

``` reserveForm.html
 // 変更前
<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org"
       xmlns:sec = "http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset = "UTF-8" />

     ・・・

	<tr th:each = "reservation : ${reservations}">
		<td>
			<span th:text = "${reservation.startTime}" />
			-
			<span th:text = "${reservation.endTime}" />
		</td>
		<td>
			<span th:text = "${reservation.user.lastName}" />
			<span th:text = "${reservation.user.firstName}" />
		</td>
		<td>
			<form th:action = "@{'/reservations/' + ${date} + '/' + ${roomId}}" method = "post"
					sec:authorize = "${hasRole('ADMIN') or #vars.user.userId == #vars.reservation.user.userId}">
				<input type = "hidden" name = "reservationId" th:value = "${reservation.reservationId}" />
				<input type = "submit" name = "cancel" value = "取消" />
			</form>
		</td>
	</tr>

     ・・・

 // 変更後
<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org"
       xmlns:sec = "http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset = "UTF-8">

     ・・・

	<tr th:each = "reservation : ${reservations}">
		<td>
			<span th:text = "${reservation.startTime}">
			-
			<span th:text = "${reservation.endTime}">
		</td>
		<td>
			<span th:text = "${reservation.user.lastName}">
			<span th:text = "${reservation.user.firstName}">
		</td>
		<td>
			<form th:action = "@{'/reservations/' + ${date} + '/' + ${roomId}}" method = "post"
					sec:authorize = "${hasRole('ADMIN') or #vars.user.userId == #vars.reservation.user.userId}">
				<input type = "hidden" name = "reservationId" th:value = "${reservation.reservationId}">
				<input type = "submit" name = "cancel" value = "取消">
			</form>
		</td>
	</tr>

     ・・・


```

``` listRooms.html
 // 変更前
<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org"
       xmlns:sec = "http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset = "UTF-8" />

     ・・・

 // 変更後
<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org"
       xmlns:sec = "http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset = "UTF-8">

     ・・・

```

### Spring Data JPA 2.2に合わせた変更

#### 不要なソース
p.650,651の３つのConverterは作成不要。
(Spring Data JPA2.2では、java.time.Timeに対応しているため)
・LocalDateConverter.java
・LocalTimeConverter.java
・LocalDateTimeConverter.java

#### RepositoryのfindOne()メソッドの変更
CrudRepositoryのfindOne()メソッドがfindById()メソッドに名称変更。
戻り値がEntity型からOptional型になった。
そのため、下記のように変更
fineOne() => findById().get()
※get() : Optionalに値が存在する場合は値を返し、それ以外の場合はNoSuchElementExceptionをスローしてくれる

#### サービスクラス
```Java:ReservatoinService.java
 // 変更前

・・・
 public Reservation findById(Integer reservationId) {
    return reservationRepository.findOne(reservationId);
 }

 // 変更後

・・・
 public Reservation findById(Integer reservationId) {
    return reservationRepository.findById(reservationId).get();
 }
```

```Java:RoomService.java
 // 変更前

・・・

 public MeetingRoom findMeetingRoom(Integer roomId) {
	return meetingRoomRepository.findOne(roomId);
 }

 // 変更後

・・・

 public MeetingRoom findMeetingRoom(Integer roomId) {
	return meetingRoomRepository.findById(roomId).get();
 }
```

```Java:ReservationUserDetailsService.java
 // 変更前

・・・

 User user = userRepository.findOne(username);

 // 変更後

・・・

User user = userRepository.findById(username).get();
```