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用に変更
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)できる。
コンストラクタがクラス内に1つの場合、@Autowiredは省略可能(下記では省略している)
サービスクラス
// 変更前
@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;
}
// 変更前
@Autowired
UserRepository userRepository;
// 変更後
private final UserRepository userRepository;
public ReservationUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 変更前
@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;
}
コントローラークラス
// 変更前
@Autowired
RoomService roomService;
// 変更後
private final RoomService roomService;
public RoomsController(RoomService roomService) {
this.roomService = roomService;
}
// 変更前
@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 ="は省略可能
コントローラークラス
// 変更前
@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) {
・・・
}
// 変更前
@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形式)
閉じタグ"/"がなくてもエラーにならない。
// 変更前
<!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>
・・・
// 変更前
<!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>
・・・
// 変更前
<!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の3つの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をスローしてくれる
サービスクラス
// 変更前
・・・
public Reservation findById(Integer reservationId) {
return reservationRepository.findOne(reservationId);
}
// 変更後
・・・
public Reservation findById(Integer reservationId) {
return reservationRepository.findById(reservationId).get();
}
// 変更前
・・・
public MeetingRoom findMeetingRoom(Integer roomId) {
return meetingRoomRepository.findOne(roomId);
}
// 変更後
・・・
public MeetingRoom findMeetingRoom(Integer roomId) {
return meetingRoomRepository.findById(roomId).get();
}
// 変更前
・・・
User user = userRepository.findOne(username);
// 変更後
・・・
User user = userRepository.findById(username).get();