Skip to content
ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

15 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

SPRING PLUS

๐Ÿ“– ์ฃผํŠน๊ธฐ ํ”Œ๋Ÿฌ์Šค ์ฃผ์ฐจ ๊ณผ์ œ ํšŒ๊ณ 

์ฒ˜์Œ ๊ณผ์ œ๋ฅผ forkํ–ˆ๋Š”๋ฐ yml ํŒŒ์ผ์ด ๋”ฐ๋กœ ์—†์–ด์„œ yml ํŒŒ์ผ๋ถ€ํ„ฐ ์ƒ์„ฑ์„ ํ•˜๋ฉฐ ๊ณผ์ œ๋ฅผ ์‹œ์ž‘ํ•˜์˜€๋‹ค.

[Level 1]

1. ์ฝ”๋“œ ๊ฐœ์„  ํ€ด์ฆˆ @transactional์˜ ์ดํ•ด

๊ธฐ๋ณธ์ ์œผ๋กœ ์„œ๋น„์Šค๋‹จ์€ @transactional(readOnly = true)๋กœ ์„ค์ •ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ DB์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€, ๋ณ€๊ฒฝ, ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น ๋ฉ”์„œ๋“œ์— @transactional ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ saveTodo()์—๋Š” ํ•ด๋‹น ์„ค์ •์ด ๋˜์–ด ์žˆ์ง€ ์•Š์•„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ๋œ ๊ฒƒ์ด๋‹ค. @transactional ์„ค์ •์„ ํ•ด ์ฃผ๋ฉด ํ•ด๋‹น ์—๋Ÿฌ๊ฐ€ ๋” ์ด์ƒ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


2.์ฝ”๋“œ ์ถ”๊ฐ€ ํ€ด์ฆˆ - JWT์˜ ์ดํ•ด

[์š”๊ตฌ ์‚ฌํ•ญ]

  • 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 ์ถ”๊ฐ€
  • JwtUtil.java
    • createToken() ๋ฉ”์„œ๋“œ ์ˆ˜์ •
      • JWTํ† ํฐ ๋นŒ๋”์— nickname claim ์ถ”๊ฐ€
  • 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

3. ์ฝ”๋“œ ๊ฐœ์„  ํ€ด์ฆˆ - AOP์˜ ์ดํ•ด

[์š”๊ตฌ ์‚ฌํ•ญ]

  • UserAdminController ํด๋ž˜์Šค์˜ changeUserRole() ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰ ์ „ ๋™์ž‘ํ•ด์•ผํ•ด์š”.
  • AdminAccessLoggingAspect ํด๋ž˜์Šค์— ์žˆ๋Š” AOP๊ฐ€ ๊ฐœ๋ฐœ ์˜๋„์— ๋งž๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”.

[๋ณ€๊ฒฝ ์‚ฌํ•ญ]

  • AOP ์‹คํ–‰ ์‹œ์  ๋ณ€๊ฒฝ
    • @after -> @before
  • AOP Target ๋ณ€๊ฒฝ
    • UserController.getUser() -> UserAdminController.changeUserRole()

4.ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ํ€ด์ฆˆ - ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ์˜ ์ดํ•ด

[์š”๊ตฌ ์‚ฌํ•ญ]

  • ํ…Œ์ŠคํŠธ ํŒจํ‚ค์ง€ 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())

5.์ฝ”๋“œ ๊ฐœ์„  ํ€ด์ฆˆ - JPA์˜ ์ดํ•ด

[์š”๊ตฌ ์‚ฌํ•ญ]

  • ํ•  ์ผ ๊ฒ€์ƒ‰ ์‹œ 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"
    • ๋ฐ˜ํ™˜ํ•˜๋Š” ResponseEntity์˜ body์—์„œ ํ˜ธ์ถœํ•˜๋Š” todoService์˜ getTodos()๋ฉ”์„œ๋“œ์— weather, ๊ธฐ๊ฐ„ ์‹œ์ž‘๊ณผ ๋ ์กฐ๊ฑด ๋งค๊ฐœ๋ณ€์ˆ˜ ์ถ”๊ฐ€
  • TodoService.java
    • ๋งค๊ฐœ๋ณ€์ˆ˜ ์ถ”๊ฐ€
      • String weather : ๋‚ ์”จ ์กฐ๊ฑด
      • LocalDateTime startDate : ๊ธฐ๊ฐ„ ์‹œ์ž‘ ์กฐ๊ฑด
      • LocalDateTime endDate : ๊ธฐ๊ฐ„ ๋ ์กฐ๊ฑด
    • weather ์กฐ๊ฑด ์œ ๋ฌด์— ๋”ฐ๋ผ Page todos๋ฅผ ์ดˆ๊ธฐํ™”
      • weather์ด null์ผ ๋•Œ, findAllByDateRange() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
      • weather ์กฐ๊ฑด์ด ์žˆ์„ ๋•Œ, findAllByWeatherAndDateRage() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    • TodoRepository.java
      • findAllByDateRage() : ๊ธฐ๊ฐ„ ์กฐ๊ฑด๋งŒ์œผ๋กœ ํ• ์ผ ๋‹ค๊ฑด ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
      • findAllByWeatherAndDateRage() : weather ์กฐ๊ฑด๊ณผ ๊ธฐ๊ฐ„ ์กฐ๊ฑด์œผ๋กœ ํ• ์ผ ๋‹ค๊ฑด ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€

6.JPA Cascade

[์š”๊ตฌ์‚ฌํ•ญ]

  • ํ•  ์ผ์„ ์ƒˆ๋กœ ์ €์žฅํ•  ์‹œ, ํ•  ์ผ์„ ์ƒ์„ฑํ•œ ์œ ์ €๋Š” ๋‹ด๋‹น์ž๋กœ ์ž๋™ ๋“ฑ๋ก๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • JPA์˜ cascade ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด ํ•  ์ผ์„ ์ƒ์„ฑํ•œ ์œ ์ €๊ฐ€ ๋‹ด๋‹น์ž๋กœ ๋“ฑ๋ก๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์„ธ์š”.

[๋ณ€๊ฒฝ ์‚ฌํ•ญ]

  • CaseCadeType.PERSIST ์„ค์ •์„ ํ•˜๋ฉด ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์˜์†ํ™”๋  ๋•Œ, ์ž์‹ ์—”ํ‹ฐํ‹ฐ๋„ ์ž๋™์œผ๋กœ ์˜์†ํ™”๋ฉ๋‹ˆ๋‹ค.
  • ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„์—์„œ ๋‘ ์—”ํ‹ฐํ‹ฐ ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์ €์žฅํ•  ๋•Œ, ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ์˜์†ํ™”ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Todo.java์˜ managers ํ•„๋“œ์— cascade ์˜ต์…˜์œผ๋กœ CadecadeType.PERSIST๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.

7. N+1 - 1) JOIN FETCH ์‚ฌ์šฉ

[์š”๊ตฌ ์‚ฌํ•ญ]

  • 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๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.


7. N+1 - 2) @entitygraph ์‚ฌ์šฉ

[์š”๊ตฌ ์‚ฌํ•ญ]

  • 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 ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฆ‰์‹œ ๋กœ๋”ฉํ•ด์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.


8.QueryDSL - solution(1)

[์š”๊ตฌ ์‚ฌํ•ญ]

  • 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์—์„œ ๋ถˆํ•„์š”ํ•œ ํ•„๋“œ๋„ ๋กœ๋“œํ•ด ์˜ค๋Š” ๋ถ€๋ถ„์—์„œ ์ฟผ๋ฆฌ ์ตœ์ ํ™”๊ฐ€ ์กฐ๊ธˆ ๋” ํ•„์š”ํ•ด ๋ณด์ด๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.)


8.QueryDSL - solution(2) Projection.constructor() ์‚ฌ์šฉ

[์š”๊ตฌ ์‚ฌํ•ญ]

  • 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์˜ ํ•„๋“œ๋ฅผ ๊ฐ€์ ธ์˜ด.

  • 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 ?


8.QueryDSL - solution(3) Tuple ๊ฐ์ฒด ์‚ฌ์šฉ

[์š”๊ตฌ ์‚ฌํ•ญ]

  • 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 ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋” ์ข‹์•˜๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.)

  • 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 ?


9.Spring Security

[์š”๊ตฌ ์‚ฌํ•ญ]

  • ๊ธฐ์กด 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๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋ฉ”์„œ๋“œ
  • 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์€ ๋ชจ๋‘ ์ธ์ฆ, ์ธ๊ฐ€๋ฅผ ํ•˜๋„๋ก ์„ค์ •
  • UserRepository.java ์ฝ”๋“œ ์ถ”๊ฐ€
    • findAuthUserByEmail() ์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
      : ์œ ์ €์˜ Email๋กœ Optional ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
  • ๊ธฐ์กด @Auth Annotation์œผ๋กœ ์ธ์ฆ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›๋˜ ๊ฒƒ์„ @AuthenticationPrincipal๋กœ ๋ณ€๊ฒฝ
    • CommentController.java
      • saveComment()
    • ManagerController.java
      • saveManager()
      • deleteManager()
    • TodoController.java
      • saveTodo()
    • UserController.java
      • changePassword()

๐Ÿ’ญ ์ด๋ ‡๊ฒŒ ํ•œ ๊ฒƒ์ด ๋งž์„๊นŒ?

  • Spring Security ํŠน๊ฐ•์„ ์ฐธ๊ณ ํ•˜์—ฌ 9๋ฒˆ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€๋Š”๋ฐ
    UserDetailsImpl.java ์ถ”๊ฐ€ํ•˜๋Š” ๋ถ€๋ถ„์—์„œ UserDetails๋ฅผ implementsํ•ด์„œ getPassword() ๋ฉ”์„œ๋“œ๋Š” ํ•„์ˆ˜์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด์•ผํ•ด์„œ Override ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด ๋†“๊ธฐ๋Š” ํ–ˆ๋Š”๋ฐ JWTํ† ํฐ์— ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •๋ณด๋Š” ํ•„์š”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ๊ตฌํ˜„ํ•˜์ง€๋Š” ์•Š์•˜๋Š”๋ฐ ์ด๋Ÿด ๊ฒฝ์šฐ, ๊ทธ๋ƒฅ null์„ ๋ฐ˜ํ™˜ํ•ด๋„ ๋˜๋Š”์ง€ ์•„๋‹ˆ๋ฉด ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”์ง€ ์•„์ง ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.
  • JwtAuthenticationTokenํด๋ž˜์Šค์—์„œ AuthUser ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ JwtAuthenticationToken๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ •์—์„œ
    super(authUser.getAuthorities()); ๋ถ€๋ถ„์ด ์žˆ๋Š”๋ฐ
    getAuthorities() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋”ฐ๋กœ ์—†์–ด์„œ
    UserDetailsImpl๊ฐ์ฒด๋ฅผ authUser ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜์—ฌ UserDetailsImpl ๊ฐ์ฒด์˜ getAuthorities() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜์˜€๋Š”๋ฐ ์ด ๋ฐฉ๋ฒ•์ด ๋งž๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค.

About

spring-plus

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages