์ฒ์ ๊ณผ์ ๋ฅผ forkํ๋๋ฐ yml ํ์ผ์ด ๋ฐ๋ก ์์ด์ yml ํ์ผ๋ถํฐ ์์ฑ์ ํ๋ฉฐ ๊ณผ์ ๋ฅผ ์์ํ์๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์๋น์ค๋จ์ @transactional(readOnly = true)๋ก ์ค์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ํธ๋์ญ์ ๋ด์์ DB์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ, ๋ณ๊ฒฝ, ์ญ์ ํ๋ ๊ฒฝ์ฐ์๋ง ํด๋น ๋ฉ์๋์ @transactional ์ค์ ์ ํด์ฃผ์ด์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ saveTodo()์๋ ํด๋น ์ค์ ์ด ๋์ด ์์ง ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ๋ ๊ฒ์ด๋ค. @transactional ์ค์ ์ ํด ์ฃผ๋ฉด ํด๋น ์๋ฌ๊ฐ ๋ ์ด์ ๋ฐ์ํ์ง ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
[์๊ตฌ ์ฌํญ]
- User์ ์ ๋ณด์ nickname์ด ํ์ํด์ก์ด์.
- User ํ ์ด๋ธ์ nickname ์ปฌ๋ผ์ ์ถ๊ฐํด์ฃผ์ธ์.
- nickname์ ์ค๋ณต ๊ฐ๋ฅํฉ๋๋ค.
- ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ JWT์์ ์ ์ ์ ๋๋ค์์ ๊บผ๋ด ํ๋ฉด์ ๋ณด์ฌ์ฃผ๊ธธ ์ํ๊ณ ์์ด์.
[๋ณ๊ฒฝ ์ฌํญ]
- SignupRequest.java
- String nickname ํ๋ ์ถ๊ฐ(@notblank ์ค์ ์ ์ฉ)
- ์์ฑ์ ๋งค๊ฐ๋ณ์ ๋ฐ ํ๋ ์ด๊ธฐํ ์ฝ๋์ nickname ์ถ๊ฐ
- User ์ํฐํฐ
- String nickname ํ๋ ์ถ๊ฐ
- ์์ฑ์ ๋งค๊ฐ๋ณ์ ๋ฐ ์ด๊ธฐํ ์ฝ๋์ nickname ์ถ๊ฐ
- fromAuthUser() ๋ฉ์๋์์ User ๊ฐ์ฒด์ ์์ฑ์๋ฅผ ํธ์ถํ์ฌ ๋ฐํํ๋ ๋ถ๋ถ์์ ๋งค๊ฐ๋ณ์ nickname ์ถ๊ฐ
- AuthService.java
- signup() ๋ฉ์๋ ์์
- newUser์ ์์ฑ์์ nickname ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
- jwtUtil.createToken() ๋งค๊ฐ๋ณ์์ nickname ์ถ๊ฐ
- signin() ๋ฉ์๋ ์์
- jwtUtil.createToken() ๋งค๊ฐ๋ณ์์ nickname ์ถ๊ฐ
- signup() ๋ฉ์๋ ์์
- JwtUtil.java
- createToken() ๋ฉ์๋ ์์
- JWTํ ํฐ ๋น๋์ nickname claim ์ถ๊ฐ
- createToken() ๋ฉ์๋ ์์
- JwtFilter.java
- httpRequest์ nickname ์์ฑ ์ถ๊ฐ
- AuthUser.java
- String nickname ํ๋ ์ถ๊ฐ
- ์์ฑ์ ๋งค๊ฐ๋ณ์์ nickname ์ถ๊ฐ
- ์์ฑ์์์ nickname ์ด๊ธฐํ ์ฝ๋ ์ถ๊ฐ
- AuthUserArgumentResolver.java
- JwtFilter์์ setํ nickname ๊ฐ ๊ฐ์ ธ์ค๋ ์ฝ๋ ์ถ๊ฐ
- AuthUser ๊ฐ์ฒด์ ์์ฑ์ ํธ์ถํ์ฌ ๋ฐํํ๋ ์ฝ๋์ ๋งค๊ฐ๋ณ์ nickname ์ถ๊ฐ
- UserResponse.java
- String nickname ํ๋ ์ถ๊ฐ
- ์์ฑ์ ๋งค๊ฐ๋ณ์์ nickname ์ถ๊ฐ
- ์์ฑ์์์ nickname ํ๋ ์ด๊ธฐํ ํ๋๋ก ์ฝ๋ ์ถ๊ฐ
- UserResponse๊ฐ์ฒด ์์ฑ์ ํธ์ถํ๋ ๋ถ๋ถ์์ nickname ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
- CommentService.java
- ManagerService.java
- TodoService.java
- UserService.java
[์๊ตฌ ์ฌํญ]
UserAdminControllerํด๋์ค์changeUserRole()๋ฉ์๋๊ฐ ์คํ ์ ๋์ํด์ผํด์.AdminAccessLoggingAspectํด๋์ค์ ์๋ AOP๊ฐ ๊ฐ๋ฐ ์๋์ ๋ง๋๋ก ์ฝ๋๋ฅผ ์์ ํด์ฃผ์ธ์.
[๋ณ๊ฒฝ ์ฌํญ]
- AOP ์คํ ์์ ๋ณ๊ฒฝ
- @after -> @before
- AOP Target ๋ณ๊ฒฝ
- UserController.getUser() -> UserAdminController.changeUserRole()
[์๊ตฌ ์ฌํญ]
- ํ
์คํธ ํจํค์ง
org.example.expert.domain.todo.controller์
todo_๋จ๊ฑด_์กฐํ_์_todo๊ฐ_์กด์ฌํ์ง_์์_์์ธ๊ฐ_๋ฐ์ํ๋ค()ํ ์คํธ๊ฐ ์คํจํ๊ณ ์์ด์. - ํ ์คํธ๊ฐ ์ ์์ ์ผ๋ก ์ํ๋์ด ํต๊ณผํ ์ ์๋๋ก ์ฝ๋๋ฅผ ์์ ํด์ฃผ์ธ์.
[๋ณ๊ฒฝ ์ฌํญ]
MockHttpServletResponse
Status = 400
Body = {"code":400,"message":"Todo not found","status":"BAD_REQUEST"}
๋ฐ๋ผ์ then ๋ถ๋ถ์์ ๊ฒฐ๊ณผ๋ฅผ ์์ธกํ๋ ๋ถ๋ถ(.andExpect()) ์ฝ๋ ์์
- status().isOk() -> status().isBadRequest()
- jsonPath("$.status").value(HttpStatus.OK.name()) -> jsonPath("$.status").value(HttpStatus.BAD_REQUEST.name())
- jsonPath("$.code").value(HttpStatus.OK.value()) -> HttpStatus.BAD_REQUEST.value())
[์๊ตฌ ์ฌํญ]
- ํ ์ผ ๊ฒ์ ์
weather์กฐ๊ฑด์ผ๋ก๋ ๊ฒ์ํ ์ ์์ด์ผํด์.weather์กฐ๊ฑด์ ์์ ์๋ ์๊ณ , ์์ ์๋ ์์ด์!
- ํ ์ผ ๊ฒ์ ์ ์์ ์ผ ๊ธฐ์ค์ผ๋ก ๊ธฐ๊ฐ ๊ฒ์์ด ๊ฐ๋ฅํด์ผํด์.
- ๊ธฐ๊ฐ์ ์์๊ณผ ๋ ์กฐ๊ฑด์ ์์ ์๋ ์๊ณ , ์์ ์๋ ์์ด์!
- JPQL์ ์ฌ์ฉํ๊ณ , ์ฟผ๋ฆฌ ๋ฉ์๋๋ช
์ ์์ ๋กญ๊ฒ ์ง์ ํ๋ ๋๋ฌด ๊ธธ์ง ์๊ฒ ํด์ฃผ์ธ์.
(ํ์ํ ์, ์๋น์ค ๋จ์์ if๋ฌธ์ ์ฌ์ฉํด ์ฌ๋ฌ ๊ฐ์ ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ ๋ ์ข์ต๋๋ค.)
[๋ณ๊ฒฝ ์ฌํญ]
- TodoController.java
- getTodos() ๋ฉ์๋์ ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
- @RequestParam์ผ๋ก ์กฐ๊ฑด์ ๋ฐ์
- String weather : ๋ ์จ ์กฐ๊ฑด
- LocalDateTime startDate : ๊ธฐ๊ฐ ์์ ์กฐ๊ฑด
- LocalDateTime endDate : ๊ธฐ๊ฐ ๋ ์กฐ๊ฑด
(weather, ๊ฐ๊ฐ ์์๊ณผ ๋ ์กฐ๊ฑด ๋ชจ๋ ์์ ์๋ ์๊ณ ์์ ์๋ ์๋ค๊ณ ํ์ผ๋ฏ๋ก)
- weather์๋ required = false ์ต์ ์ ์ฉ
- ๊ธฐ๊ฐ ์์ ์กฐ๊ฑด์๋ MySQL DateTime ์ต์๊ฐ์ defaultValue๋ก ์ค์
- defaultValue = "1000-01-01T00:00:00"
- ๊ธฐ๊ฐ ๋ ์กฐ๊ฑด์๋ MySQL DateTime ์ต๋๊ฐ์ defaultValue๋ก ์ค์
- defaultValue = "9999-12-31T23:59:59"
- @RequestParam์ผ๋ก ์กฐ๊ฑด์ ๋ฐ์
- ๋ฐํํ๋ ResponseEntity์ body์์ ํธ์ถํ๋ todoService์ getTodos()๋ฉ์๋์ weather, ๊ธฐ๊ฐ ์์๊ณผ ๋ ์กฐ๊ฑด ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
- getTodos() ๋ฉ์๋์ ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
- TodoService.java
- ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
- String weather : ๋ ์จ ์กฐ๊ฑด
- LocalDateTime startDate : ๊ธฐ๊ฐ ์์ ์กฐ๊ฑด
- LocalDateTime endDate : ๊ธฐ๊ฐ ๋ ์กฐ๊ฑด
- weather ์กฐ๊ฑด ์ ๋ฌด์ ๋ฐ๋ผ Page todos๋ฅผ ์ด๊ธฐํ
- weather์ด null์ผ ๋, findAllByDateRange() ๋ฉ์๋ ์ฌ์ฉ
- weather ์กฐ๊ฑด์ด ์์ ๋, findAllByWeatherAndDateRage() ๋ฉ์๋ ์ฌ์ฉ
- TodoRepository.java
- findAllByDateRage() : ๊ธฐ๊ฐ ์กฐ๊ฑด๋ง์ผ๋ก ํ ์ผ ๋ค๊ฑด ์กฐํํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋ ์ถ๊ฐ
- findAllByWeatherAndDateRage() : weather ์กฐ๊ฑด๊ณผ ๊ธฐ๊ฐ ์กฐ๊ฑด์ผ๋ก ํ ์ผ ๋ค๊ฑด ์กฐํํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋ ์ถ๊ฐ
- ๋งค๊ฐ๋ณ์ ์ถ๊ฐ
[์๊ตฌ์ฌํญ]
- ํ ์ผ์ ์๋ก ์ ์ฅํ ์, ํ ์ผ์ ์์ฑํ ์ ์ ๋ ๋ด๋น์๋ก ์๋ ๋ฑ๋ก๋์ด์ผ ํฉ๋๋ค.
- JPA์
cascade๊ธฐ๋ฅ์ ํ์ฉํด ํ ์ผ์ ์์ฑํ ์ ์ ๊ฐ ๋ด๋น์๋ก ๋ฑ๋ก๋ ์ ์๊ฒ ํด์ฃผ์ธ์.
[๋ณ๊ฒฝ ์ฌํญ]
- CaseCadeType.PERSIST ์ค์ ์ ํ๋ฉด ๋ถ๋ชจ ์ํฐํฐ๊ฐ ์์ํ๋ ๋, ์์ ์ํฐํฐ๋ ์๋์ผ๋ก ์์ํ๋ฉ๋๋ค.
- ๋ค๋๋ค ๊ด๊ณ์์ ๋ ์ํฐํฐ ๊ฐ์ ๊ด๊ณ๋ฅผ ์ ์ฅํ ๋, ์ค๊ฐ ํ ์ด๋ธ์ ์ ์ฅํด์ผ ํ๋ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์์ํํ๋ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- Todo.java์ managers ํ๋์ cascade ์ต์ ์ผ๋ก CadecadeType.PERSIST๋ฅผ ์ค์ ํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์์ต๋๋ค.
[์๊ตฌ ์ฌํญ]
CommentControllerํด๋์ค์getComments()API๋ฅผ ํธ์ถํ ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ณ ์์ด์. N+1 ๋ฌธ์ ๋, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ์ผํค๋ ๋ํ์ ์ธ ๋ฌธ์ ์ค ํ๋๋ก, ํนํ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ๋ฐ์ํด์.- ํด๋น ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ฝ๋๋ฅผ ์์ ํด์ฃผ์ธ์.
[N+1 ๋ฌธ์ ๋?]
N+1๋ฌธ์ ๋ Lazy Loading์ด ์ค์ ๋ ๋ค๋์ผ ๋๋ ์ผ๋๋ค ๊ด๊ณ์์ ๋ฐ์ํ ์ ์๋ ์ฑ๋ฅ ๋ฌธ์ ๋ก
1๊ฐ์ ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ์กฐํํ ๋, N๊ฐ์ ์์ ์ํฐํฐ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์กฐํํ๋ ์ถ๊ฐ์ ์ธ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ฌ ์ด N+1๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ํ์์
๋๋ค.
[์คํจํ ๋ฐฉ๋ฒ]
์ฒ์์๋ FetchType.LAZY๋ก ๋ณ๊ฒฝํ์ฌ N+1๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ๋ณด๋ ค๊ณ ํ๋๋ฐ
Comment์ user ํ๋์ FetchType์ ๋ณ๊ฒฝํ์์ง๋ง ์ฌ์ ํ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์์ต๋๋ค.
์ด ๊ฒฐ๊ณผ์ ์ถ๊ฐ์ ์ธ ๊ฒ์์ ํตํด FetchType์ด ์ถ๊ฐ์ ์ธ ์ฟผ๋ฆฌ ๋ฐ์์ ๋ง์ ์ ์์ง๋ง ์ฒ์ ํ๋ฒ ๋ ๋ฆฌ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ๋์ ์ต์ ํ๋ ์ฟผ๋ฆฌ๋ ์๋๋ผ๋ ๊ฒ์ ์๊ฒ๋์์ต๋๋ค.
[ํด๊ฒฐ ๋ฐฉ๋ฒ]
๋ค์ ํด๊ฒฐ๋ฐฉ๋ฒ์ผ๋ก JOIN FETCH๋ฅผ ์ ์ฉํด ๋ณด์์ต๋๋ค.
CommentRepository์ findByTodoIdWithUser() ๋ฉ์๋์์ ๋ ๋ฆฌ๋ ์ฟผ๋ฆฌ์ JOIN FETCH๋ฅผ ์ ์ฉํ ๊ฒฐ๊ณผ N+1๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์์ต๋๋ค.
[์๊ตฌ ์ฌํญ]
CommentControllerํด๋์ค์getComments()API๋ฅผ ํธ์ถํ ๋ N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ณ ์์ด์. N+1 ๋ฌธ์ ๋, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ์ผํค๋ ๋ํ์ ์ธ ๋ฌธ์ ์ค ํ๋๋ก, ํนํ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ๋ฐ์ํด์.- ํด๋น ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ฝ๋๋ฅผ ์์ ํด์ฃผ์ธ์.
[N+1 ๋ฌธ์ ๋?]
N+1๋ฌธ์ ๋ Lazy Loading์ด ์ค์ ๋ ๋ค๋์ผ ๋๋ ์ผ๋๋ค ๊ด๊ณ์์ ๋ฐ์ํ ์ ์๋ ์ฑ๋ฅ ๋ฌธ์ ๋ก
1๊ฐ์ ๋ถ๋ชจ ์ํฐํฐ๋ฅผ ์กฐํํ ๋, N๊ฐ์ ์์ ์ํฐํฐ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์กฐํํ๋ ์ถ๊ฐ์ ์ธ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ฌ ์ด N+1๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ํ์์
๋๋ค.
[๋๋ฒ์งธ ํด๊ฒฐ ๋ฐฉ๋ฒ]
๋ค์ ํด๊ฒฐ๋ฐฉ๋ฒ์ผ๋ก @entitygraph๋ฅผ ์ ์ฉํด ๋ณด์์ต๋๋ค.
@entitygraph๋ Spring Data JPA์์ ํน์ ์ํฐํฐ ํ๋(๋ณดํต ์ฐ๊ด ์ํฐํฐ๋ค)๋ฅผ ์ฆ์๋ก๋ฉ ํ๋๋ก ์ง์ํ๋ ์ด๋
ธํ
์ด์
์
๋๋ค.
์ด๋ฅผ ์ฌ์ฉํ๋ฉด ์ฟผ๋ฆฌ ์คํ ์ ์ฐ๊ด๋ ์ํฐํฐ๋ค์ ํจ๊ป ๋ก๋ฉํ์ฌ, ๋ฐ๋ณต์ ์ธ ์ง์ฐ ๋ก๋ฉ์ผ๋ก ๋ฐ์ํ๋ N+1๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
attributePaths๋ฅผ ์ฌ์ฉํ์ฌ ์ฆ์ ๋ก๋ฉํ ์ฐ๊ด ์ํฐํฐ๋ฅผ ์ง์ ํฉ๋๋ค.
CommentRepository์ findByTodoIdWithUser() ๋ฉ์๋์ @entitygraph(attributePaths = {"user"}) ์ค์ ์ ํด ์ฃผ์ด
Comment์ ์ฐ๊ด ์ํฐํฐ ์ค User ์ํฐํฐ๋ฅผ ์ฆ์ ๋ก๋ฉํด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์์ต๋๋ค.
[์๊ตฌ ์ฌํญ]
- JPQL๋ก ์์ฑ๋
findByIdWithUser๋ฅผ QueryDSL๋ก ๋ณ๊ฒฝํฉ๋๋ค. - 7๋ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ ์ํด ์ฃผ์ธ์!
QueryDSL์ ์ฒ์ ์ฌ์ฉํด ๋ณด์์
ํน๊ฐ๋ ํํฐ๋์ด Q๊ฐ์ฒด ์์ฑํ์ค ๋์ฒ๋ผ entity ํด๋ ์์ Q๊ฐ์ฒด๊ฐ ์๊ธฐ๋ ๊ฒ์ธ ์ค ์๊ณ ์ค์ ์ ์๋ชปํ ์ค ์๊ณ ํผ์ ํ์ฐธ์ ํค๋งค๋ฉฐ gradle ์ค์ ๋ณ๊ฒฝํ๊ณ ์ด๊ฒ์ ๊ฒ ํด๋ณด๋ค
๋์ค์์ผ Q๊ฐ์ฒด๊ฐ ํ๋ก์ ํธ root ๊ฒฝ๋ก build ํ์ผ ์์ ์๊ธด ๊ฒ์ ์์์ต๋๋ค.
ํ์ง๋ง ์ฌ์ ํ QueryDSL ์ฝ๋๊ฐ ์คํ๋์ง ์๋ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์ง ์์
๊ฒฐ๊ตญ ํํฐ๋์ ๋์์ผ๋ก querydsl ๊ด๋ จ ์์กด์ฑ์ ๋ฒ์ ์
ํ๊ณ ์์ผ queryDSL ์ฝ๋๋ฅผ ์คํํ ์ ์์์ต๋๋ค.
'jakarta.persistence:jakarta.persistence-api:3.0.0' -> 'jakarta.persistence:jakarta.persistence-api:3.1.0'
ํํฐ๋๊ป์ QueryDSL์ ์ ๊ทธ๋ ์ด๋๊ฐ ๋์ง ์์ ๋ฒ์ ์ถฉ๋ ๋ฌธ์ ๊ฐ ์ฝ๊ฒ ๋ฐ์ํ ์ ์์ผ๋ ์กฐ์ฌํด์ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ํ์ จ์ต๋๋ค.
(๊ทธ๋ฐ๋ฐ ๊ทธ๋ฅ ์ฒ์๋ถํฐ ๊น๋ํ๊ฒ ํ๊ณ ์ถ์ด์ ์ด์ ์ปค๋ฐ ๋ด์ฉ์ผ๋ก resetํ๊ณ ๋ค์ ์์ฑํ์์ต๋๋ค.)
[ ๋ณ๊ฒฝ ์ฌํญ ]
- build.gradle ์์กด์ฑ ์ถ๊ฐ
- querydsl ์์กด์ฑ ์ถ๊ฐ
- QueryDslConfig.java ์ถ๊ฐ
- JPAQueryFactory Bean ์ถ๊ฐ
- TodoRepository.java
- extends TodoQueryRepository ์ถ๊ฐ
- findByIdWithUser() JPQL ๋ฉ์๋ ์ญ์
- TodoQueryRepository ์ธํฐํ์ด์ค ์ถ๊ฐ
- findByIdWithUser() ๋ฉ์๋ ์ถ๊ฐ
- TodoQueryRepositoryImpl.java
- TodoQueryRepository๋ฅผ implements
- todoIdEq() ๋ฉ์๋ ์ถ๊ฐ
- ๋ชจ์ ๋ฉด์ ๋, QueryDSL์์ ๋์ ์ฟผ๋ฆฌ ์์ฑ ๋ฐฉ๋ฒ์์ ์ฒ์ ์๊ฒ๋ BooleanExpression์ ์ฌ์ฉํด ๋ณด๊ณ ์ถ์ด์ ๋ง๋ค์ด ๋ณด์์ต๋๋ค.
- findByIdWithUser() ๋ฉ์๋ ์ค๋ฒ๋ผ์ด๋
- Todo Entity์ ์ด์ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ฐ์ง User Entity๋ฅผ ์กฐํํด ์ค๊ธฐ ์ํด fetchJoin() ์ฌ์ฉ
- where()์ todoIdEq() ๋ฉ์๋ ํธ์ถ
- fetchOne()์ ํ์ ๋, 2๊ฐ์ Entity๊ฐ์กฐํ๋๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ค๊ณ ํด์ fetchFirst() ์ฌ์ฉ.
- TodoService.java
- findByIdWithUser() ๋ฉ์๋๊ฐ Todo ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ฏ๋ก orElseThrow() ์ฝ๋ ์ญ์
- Todo๊ฐ null์ธ ๊ฒฝ์ฐ EntityNotFoundException()์ throw
[๋ ์๊ฐ ์ฟผ๋ฆฌ ํ์ธ]
select
todo
from
Todo todo
inner join
fetch
todo.user as user
where
todo.id = ?1 */ select
t1_0.id,
t1_0.contents,
t1_0.created_at,
t1_0.modified_at,
t1_0.title,
t1_0.user_id,
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.modified_at,
u1_0.nickname,
u1_0.password,
u1_0.user_role,
t1_0.weather
from
todos t1_0
join
users u1_0
on u1_0.id=t1_0.user_id
where
t1_0.id=?
limit
?
(์ฟผ๋ฆฌ๊ฐ 1๊ฐ๋ง ๋ ์๊ฐ ๊ฒ์ ์ข์ผ๋ User Entity์์ ๋ถํ์ํ ํ๋๋ ๋ก๋ํด ์ค๋ ๋ถ๋ถ์์ ์ฟผ๋ฆฌ ์ต์ ํ๊ฐ ์กฐ๊ธ ๋ ํ์ํด ๋ณด์ด๋ ๊ฒ ๊ฐ์ต๋๋ค.)
[์๊ตฌ ์ฌํญ]
- JPQL๋ก ์์ฑ๋
findByIdWithUser๋ฅผ QueryDSL๋ก ๋ณ๊ฒฝํฉ๋๋ค. - 7๋ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ ์ํด ์ฃผ์ธ์!
** POINT! **
- Level2. 8.QueryDSL - solution(1)์์ ๋ถํ์ํ ํ๋๊น์ง ๋ก๋ํด ์ค๋ ๋ถ๋ถ์ ์์
- Projections.constructor()์ ์ฌ์ฉํด ๋ณด๊ณ ์ถ์์ต๋๋ค.
[ ๋ณ๊ฒฝ ์ฌํญ ]
-
TodoQueryRepositoryImpl.java
- ๋ฐํ ๊ฐ์ฒด๋ฅผ TodoResponse๋ก ๋ณ๊ฒฝ
- select()์์ Projections.constructor() ์ฌ์ฉ
- fetch join()์ ์ฌ์ฉํ๋ ค ํ์ผ๋
"org.hibernate.query.SemanticException: Query specified join fetching, but the owner of the fetched association was not present in the select list [SqmSingularJoin(org.example.expert.domain.todo.entity.Todo(todo).user(user) : user)]"์๋ฌ ๋ฐ์
fetchJoin()์ ์ฌ์ฉํ์ง ์๊ณ join()์ผ๋ก user Entity์ ํ๋๋ฅผ ๊ฐ์ ธ์ด. - ๋ฐํ ๊ฐ์ฒด๋ฅผ TodoResponse๋ก ๋ณ๊ฒฝ
-
TodoQueryRepository.java
- ๋ฐํ๊ฐ์ฒด๋ฅผ TodoResponse๋ก ๋ณ๊ฒฝ
-
TodoService.java
- repository์์ TodoResponse ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ฏ๋ก null ์ฌ๋ถ๋ง ์ฒดํฌํ๊ณ null์ด ์๋ ๊ฒฝ์ฐ ๊ทธ๋๋ก ๋ฐํ[ ๋ ์๊ฐ ์ฟผ๋ฆฌ ]
-
Hibernate: /* select todo.id, todo.title, todo.contents, todo.weather, todo.user.id, todo.user.email, todo.user.nickname, todo.createdAt, todo.modifiedAt from Todo todo inner join todo.user as user where todo.id = ?1 */ select t1_0.id, t1_0.title, t1_0.contents, t1_0.weather, t1_0.user_id, u1_0.email, u1_0.nickname, t1_0.created_at, t1_0.modified_at from todos t1_0 join users u1_0 on u1_0.id=t1_0.user_id where t1_0.id=? limit ?
[์๊ตฌ ์ฌํญ]
- JPQL๋ก ์์ฑ๋
findByIdWithUser๋ฅผ QueryDSL๋ก ๋ณ๊ฒฝํฉ๋๋ค. - 7๋ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ ์ํด ์ฃผ์ธ์!
** POINT! **
- Level2. 8.QueryDSL - solution(1)์์ ๋ถํ์ํ ํ๋๊น์ง ๋ก๋ํด ์ค๋ ๋ถ๋ถ์ ์์
- ๋ชจ์๋ฉด์ ์ค๋นํ๋ฅผ ํ๋ฉฐ QueryDSL๋ก ๋์ ์ฟผ๋ฆฌ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์๋ Tuple ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด ๋ณด๊ณ ์ถ์๋ค.
[ ๋ณ๊ฒฝ ์ฌํญ ]
-
TodoQueryRepositoryImpl.java
- select()์์ TodoResponse ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐ ํ์ํ ํ๋๋ง ์ง์
- todo
- user.id
- user.email
- user.nickname
- fetchjoin()์ User Entity ์ ์ฒด๋ฅผ ๋ก๋ํด ์ค๊ธฐ ๋๋ฌธ์ ํ์ํ ํ๋๋ง ๊ฐ์ ธ์ค๊ณ ์ถ์ด์ ๊ทธ๋ฅ join() ์ฌ์ฉ
- TodoService์์ Q๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋์๊น? Repository์์ TodoResponse๋ก ๋ฐํํ๋๊ฒ ๋์์ง ๊ณ ๋ฏผํ๋ค ๊ทธ๋ฅ Repository์์ TodoResponse๋ก ๋ฐํํ์์ต๋๋ค.
[ ๋ ์๊ฐ ์ฟผ๋ฆฌ ][์ฐธ๊ณ : join๊ณผ fetchjoin ์ฐจ์ด์ ]
- Join
- ์กฐ์ธ๋ ์ํฐํฐ๋ ํ์ํ ๋, ์ถ๊ฐ ์ฟผ๋ฆฌ๋ฆฌ ๋ก๋(์ง์ฐ๋ก๋ฉ)
- ๊ธฐ๋ณธ์ ์ผ๋ก N+1๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
- Lazy Loading ์ค์ ์ ์ฉ
- Fetch Join
- ์ฆ์ ๋ก๋ฉ : ์ฐ๊ด๋ ์ํฐํฐ๊ฐ ์ฆ์ ํจ๊ป ๋ก๋ฉ๋ฉ๋๋ค.
- ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
- Lazy Loading ์ค์ ์ด ๋ฌด์๋ฉ๋๋ค.
- ์ฑ๋ฅ ์ ์ด์ ์ด ์์ ์ ์์ง๋ง ๋ถํ์ํ๊ฒ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ฉด ์ฑ๋ฅ ์ ํ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
(todoId๋ก todo ๋จ๊ฑด ์กฐํ์ ๊ฒฝ์ฐ,
Todo Entity์ User Entity๊ฐ @manytoone ๊ด๊ณ์ด๊ณ
select ์ ์์ ์ฐ๊ด์ํฐํฐ์ ํ๋๊ฐ ์ง์ ๋์ด ์์ผ๋ฏ๋ก ์ฆ์ ๋ก๋์ฒ๋ผ ๋์ํ์ฌ N+1๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ ๊ฒ์ด๋ผ ํ๋จํ๊ณ ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ๊ฐ์ธ์ ์ผ๋ก๋ Tuple ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ๋ฐฉ๋ฒ๋ณด๋ค๋ Projections.constructor()๋ฅผ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ด TodoResponse ๊ฐ์ฒด๋ก ๋ฐํํ๊ธฐ ๋ ์ข์๋ ๊ฒ ๊ฐ์ต๋๋ค.) - select()์์ TodoResponse ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐ ํ์ํ ํ๋๋ง ์ง์
-
Hibernate: /* select todo, user.id, user.email, user.nickname from Todo todo inner join todo.user as user where todo.id = ?1 */ select t1_0.id, t1_0.contents, t1_0.created_at, t1_0.modified_at, t1_0.title, t1_0.user_id, t1_0.weather, u1_0.email, u1_0.nickname from todos t1_0 join users u1_0 on u1_0.id=t1_0.user_id where t1_0.id=? limit ?
[์๊ตฌ ์ฌํญ]
- ๊ธฐ์กด
Filter์Argument Resolver๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋๋ค์ Spring Security๋ก ๋ณ๊ฒฝํด์ฃผ์ธ์.- ์ ๊ทผ ๊ถํ ๋ฐ ์ ์ ๊ถํ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ์ ์งํด์ฃผ์ธ์.
- ๊ถํ์ Spring Security์ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ฃผ์ธ์.
- ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ ์ ์งํ ๊ฑฐ์์. JWT๋ ๊ทธ๋๋ก ์ฌ์ฉํด์ฃผ์ธ์.
[๋ณ๊ฒฝ ์ฌํญ]
- @Auth Annotation์ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก ๊ด๋ จ ํด๋์ค ์ญ์
- Auth.java ์ญ์
- AuthUserArgumentResolver.java ์ญ์
- Spring Security ์ฌ์ฉ์ผ๋ก ์ด์ Filter ๋ฐ Config ํด๋์ค ์ญ์
- JwtFilter.java ์ญ์
- WebConfig.java ์ญ์
- build.gradle์ Spring Security ๊ด๋ จ ์์กด์ฑ ์ถ๊ฐ
- JwtUtil.java ์ฝ๋ ์ถ๊ฐ
- JwtUtil.AUTHORIZATION_HEADER ํ๋ ์ถ๊ฐ
- validateToken() ๋ฉ์๋ ์ถ๊ฐ
: token ์ ํจ์ฑ ๊ฒ์ฌ ๋ฉ์๋ - getTokenFromRequest() ๋ฉ์๋ ์ถ๊ฐ
: HttpServletRequest๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ JWT ํ ํฐ๊ฐ์ ๋ฐํํ๋ ๋ฉ์๋
- JwtSecurityFilter.java ์ถ๊ฐ
: ๊ธฐ์กด์ Filter๋ฅผ ๋์ ํ ํด๋์ค - JwtAuthenticationToken.java ์ถ๊ฐ
: ๊ธฐ์กด์ AuthUserArgumentResolver.java๋ฅผ ๋์ ํ ํด๋์ค - AuthUser.java ์ฝ๋ ์ถ๊ฐ
- getAuthorities() ๋ฉ์๋ ์ถ๊ฐ
: UserDetailsImpl ๊ฐ์ฒด๋ก ๋ถํฐ Authorities๋ฅผ ๋ฐํํ ๋ฉ์๋
- getAuthorities() ๋ฉ์๋ ์ถ๊ฐ
- UserDetailsServiceImpl.java ์ถ๊ฐ
- UserDetailsService๋ฅผ ๊ตฌํ
- loadUserByUsername()๋ฉ์๋ Override
: User email๋ก ๋ถํฐ ์ ํจํ User์ธ์ง ํ์ธ ํ, UserDetailsImpl ๊ฐ์ฒด ๋ฐํ
- UserDetailsImpl.java ์ถ๊ฐ
- UserDetails๋ฅผ ๊ตฌํ
- getAuthoritiest() ๋ฉ์๋ Override
: ์ธ๊ฐ User์ ์ธ๊ฐ ์ ๋ณด๋ฅผ ๋ฐํ - getPassword() ๋ฉ์๋๋ ํ์์ ์ผ๋ก ๊ตฌํํด์ผํด์ Override ๋ฉ์๋๋ฅผ ๋ง๋ค์ด ๋๊ธฐ๋ ํ๋๋ฐ JWTํ ํฐ์ ๋น๋ฐ๋ฒํธ ์ ๋ณด๋ ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ๊ตฌํํ์ง๋ ์์๋๋ฐ ์ด๋ด ๊ฒฝ์ฐ, ๊ทธ๋ฅ null์ ๋ฐํํด๋ ๋๋์ง ์๋๋ฉด ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์๋์ง ์์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
- getUsername() ๋ฉ์๋ Override
: AuthUser์ email์ ๋ฐํ
- UserRole.java ์ฝ๋ ์ถ๊ฐ
- authority ํ๋ ์ถ๊ฐ
- static ํด๋์ค Authority ์ถ๊ฐ
- SecurityConfig.java ์ถ๊ฐ
- Spring Security ์ค์
- csrf : disable
- JWT ํ ํฐ ๋ฐฉ์์ด๋ฏ๋ก sessionCreationPolicy๋ฅผ STATELESS(๋ฌด์ํ)๋ก ์ค์
- Filter ์์ ์ค์
- ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํผ : disable
- ํ์๊ฐ์ , ๋ก๊ทธ์ธ ์์ฒญ์ ์ธ์ฆ, ์ธ๊ฐ ์์ด ํต๊ณผํ๋๋ก ์ค์
- test ์์ฒญ์ ADMIN ๊ถํ ํ์ํ๋๋ก ์ค์
- ๊ทธ์ธ์ ์์ฒญ URL์ ๋ชจ๋ ์ธ์ฆ, ์ธ๊ฐ๋ฅผ ํ๋๋ก ์ค์
- Spring Security ์ค์
- UserRepository.java ์ฝ๋ ์ถ๊ฐ
- findAuthUserByEmail() ์ฟผ๋ฆฌ ๋ฉ์๋ ์ถ๊ฐ
: ์ ์ ์ Email๋ก Optional ๊ฐ์ฒด๋ฅผ ๋ฐํ
- findAuthUserByEmail() ์ฟผ๋ฆฌ ๋ฉ์๋ ์ถ๊ฐ
- ๊ธฐ์กด @Auth Annotation์ผ๋ก ์ธ์ฆ ์ ์ ์ ๋ณด๋ฅผ ๋ฐ๋ ๊ฒ์ @AuthenticationPrincipal๋ก ๋ณ๊ฒฝ
- CommentController.java
- saveComment()
- ManagerController.java
- saveManager()
- deleteManager()
- TodoController.java
- saveTodo()
- UserController.java
- changePassword()
- CommentController.java
- Spring Security ํน๊ฐ์ ์ฐธ๊ณ ํ์ฌ 9๋ฒ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์๋๋ฐ
UserDetailsImpl.java ์ถ๊ฐํ๋ ๋ถ๋ถ์์ UserDetails๋ฅผ implementsํด์ getPassword() ๋ฉ์๋๋ ํ์์ ์ผ๋ก ๊ตฌํํด์ผํด์ Override ๋ฉ์๋๋ฅผ ๋ง๋ค์ด ๋๊ธฐ๋ ํ๋๋ฐ JWTํ ํฐ์ ๋น๋ฐ๋ฒํธ ์ ๋ณด๋ ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ๊ตฌํํ์ง๋ ์์๋๋ฐ ์ด๋ด ๊ฒฝ์ฐ, ๊ทธ๋ฅ null์ ๋ฐํํด๋ ๋๋์ง ์๋๋ฉด ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์๋์ง ์์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. - JwtAuthenticationTokenํด๋์ค์์ AuthUser ๊ฐ์ฒด๋ฅผ ๋ฐ์ JwtAuthenticationToken๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๊ณผ์ ์์
super(authUser.getAuthorities()); ๋ถ๋ถ์ด ์๋๋ฐ
getAuthorities() ๋ฉ์๋๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ฐ๋ก ์์ด์
UserDetailsImpl๊ฐ์ฒด๋ฅผ authUser ๊ฐ์ฒด๋ก ์์ฑํ์ฌ UserDetailsImpl ๊ฐ์ฒด์ getAuthorities() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ฐํํ์๋๋ฐ ์ด ๋ฐฉ๋ฒ์ด ๋ง๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.