Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

๐ŸŽจ ProblemService ๋ฌธ์ œ ์ƒ์„ฑ ๋ฐ ๋ฌธ์ œ ์—…๋ฐ์ดํŠธ ์—”ํ‹ฐํ‹ฐ๋ช… ์ˆ˜์ • #11

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from

Conversation

saebomnewspring
Copy link
Collaborator

@saebomnewspring saebomnewspring commented Feb 12, 2025

feat_1: Problem ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ ๋ฐ ๊ธฐ๋ณธ ๊ตฌ์กฐ ์ถ”๊ฐ€

  • Problem ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค ๊ตฌํ˜„
  • ProblemCategory ์—ด๊ฑฐํ˜• ์ถ”๊ฐ€
  • BaseTimeEntity ์ƒ์† ๊ตฌํ˜„
  • ๊ธฐ๋ณธ DTO ํด๋ž˜์Šค ๊ตฌํ˜„

feat_2: Problem ๋„๋ฉ”์ธ ์„œ๋น„์Šค ๊ณ„์ธต ๊ตฌํ˜„

  • ProblemService ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ตฌํ˜„
  • ProblemConverter ์ถ”๊ฐ€๋กœ Entity-DTO ๋ณ€ํ™˜ ๋กœ์ง ๋ถ„๋ฆฌ
  • CRUD ๊ธฐ๋Šฅ ๊ตฌํ˜„

feat_3: Problem API ์—”๋“œํฌ์ธํŠธ ๋ฐ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€

  • RESTful API ์—”๋“œํฌ์ธํŠธ ๊ตฌํ˜„
  • ์„œ๋น„์Šค ๊ณ„์ธต ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ์‘๋‹ต ๊ตฌ์กฐ ์ •์˜

feat_4: Problem ๋„๋ฉ”์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ตฌํ˜„

  • ProblemNotFoundException ์˜ˆ์™ธ ํด๋ž˜์Šค ์ถ”๊ฐ€
  • GlobalExceptionHandler๋ฅผ ํ†ตํ•œ ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ตฌํ˜„
  • HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๋”ฐ๋ฅธ ์‘๋‹ต ์ฒ˜๋ฆฌ ๋กœ์ง ๊ตฌํ˜„

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/dto/ProblemDto.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ProblemDto.java ์ฝ”๋“œ๋ฅผ ๊ฒ€ํ† ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ง์”€๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ๊ธ์ •์  ์ธก๋ฉด:

    • Lombok ์–ด๋…ธํ…Œ์ด์…˜(@Getter, @NoArgsConstructor, @AllArgsConstructor)์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
    • DTO์˜ ๋ชฉ์ ์— ๋งž๊ฒŒ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํ•„๋“œ๋ช…๋„ ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.
    • ํŒจํ‚ค์ง€ ๊ตฌ์กฐ๋„ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ํ•  ์ :

    • ์ฃผ์„์ด ์ „ํ˜€ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ ํ•„๋“œ์˜ ์—ญํ• ์ด๋‚˜ ์˜๋ฏธ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜์— ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, ProblemCategory์˜ enum ๊ฐ’์— ๋Œ€ํ•œ ์„ค๋ช…์ด ์žˆ์œผ๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • level ํ•„๋“œ๋Š” Integer ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ๋ฌธ์ œ ๋‚œ์ด๋„๋Š” ์Œ์ˆ˜๊ฐ€ ๋  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, Integer ๋Œ€์‹  int๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์—ฌ 0 ์ด์ƒ์˜ ๊ฐ’๋งŒ ํ—ˆ์šฉํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (๋ฌผ๋ก  ํ•„์š”์— ๋”ฐ๋ผ ์Œ์ˆ˜ ๋ ˆ๋ฒจ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด Integer๋ฅผ ์œ ์ง€ํ•˜๊ณ  null ์ฒดํฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ๋งž์Šต๋‹ˆ๋‹ค.)
  • problemCategory ํ•„๋“œ๋Š” ProblemCategory enum ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์™ธ๋ถ€์—์„œ ์œ ํšจํ•˜์ง€ ์•Š์€ category ๊ฐ’์ด ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • id ํ•„๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ž๋™ ์ƒ์„ฑ๋˜๋Š” ๊ฐ’์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. DTO๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๊ฐ์ฒด์ด๋ฏ€๋กœ, ์ƒ์„ฑ ์‹œ์— id๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š๊ณ , ์‘๋‹ต ์‹œ์—๋งŒ ํฌํ•จ์‹œํ‚ค๋Š” ๊ฒƒ์ด ๋” ์ ํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” DTO์˜ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํŠน๋ณ„ํ•œ ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋งŒ์•ฝ ์ด DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜์ด๋‚˜ ๋ณต์‚ฌ ์ž‘์—…์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, Immutable ๊ฐ์ฒด๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Immutable ๊ฐ์ฒด๋Š” ํ•œ ๋ฒˆ ์ƒ์„ฑ๋œ ํ›„์—๋Š” ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•˜๊ณ  ๋ถˆํ•„์š”ํ•œ ๋ณต์‚ฌ ์ž‘์—…์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Lombok์˜ @Value ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ Immutable ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋งŒ์•ฝ description ํ•„๋“œ์— ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. HTML escape ์ฒ˜๋ฆฌ ๋˜๋Š” sanitize ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด (์˜ˆ: ๊ด€๋ฆฌ์ž๋งŒ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ ๊ด€๋ จ ์ •๋ณด)๋Š” DTO์— ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. DTO๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ:
    • level ํ•„๋“œ์— @Min(0) ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ 0 ์ด์ƒ์˜ ๊ฐ’๋งŒ ํ—ˆ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    • problemCategory ํ•„๋“œ์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์œ ํšจํ•œ enum ๊ฐ’๋งŒ ํ—ˆ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์„ ์ถ”๊ฐ€:
    • ๊ฐ ํ•„๋“œ์˜ ์—ญํ• ๊ณผ ์˜๋ฏธ์— ๋Œ€ํ•œ ์ฃผ์„์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • ProblemCategory enum ๊ฐ’์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • id ํ•„๋“œ ๊ด€๋ฆฌ:
    • DTO ์ƒ์„ฑ ์‹œ์— id๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š๊ณ , ์‘๋‹ต ์‹œ์—๋งŒ ํฌํ•จ์‹œํ‚ค๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: ProblemResponseDto ์™€ ๊ฐ™์€ ๋ณ„๋„์˜ ์‘๋‹ต DTO๋ฅผ ์ƒ์„ฑ)
  • XSS ๋ฐฉ์ง€:
    • description ํ•„๋“œ์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, HTML escape ์ฒ˜๋ฆฌ ๋˜๋Š” sanitize ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
  • Builder ํŒจํ„ด ์ ์šฉ:
    • ํ•„๋“œ๊ฐ€ ๋งŽ์•„์งˆ ๊ฒฝ์šฐ, Builder ํŒจํ„ด์„ ์ ์šฉํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ณ  ๊ฐ์ฒด ์ƒ์„ฑ์„ ๋” ์œ ์—ฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Lombok์˜ @Builder ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„ํŽธํ•˜๊ฒŒ Builder ํŒจํ„ด์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ˆ˜์ • ์˜ˆ์‹œ:

package site.haruhana.www.dto;

import lombok.*;
import site.haruhana.www.entity.ProblemCategory;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder // Builder ํŒจํ„ด ์ ์šฉ
public class ProblemDto {

    private Long id;

    @NotNull(message = "์ œ๋ชฉ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
    private String title;

    private String description;

    @Min(value = 0, message = "๋ ˆ๋ฒจ์€ 0 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.") // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€
    private int level; // int ๋กœ ๋ณ€๊ฒฝ

    private ProblemCategory problemCategory; // Problem ์ข…๋ฅ˜ (Enum)
}

์œ„ ๋ฆฌ๋ทฐ๋Š” ํ˜„์žฌ ์ฝ”๋“œ๋งŒ ๋ณด๊ณ  ํŒ๋‹จํ•œ ๋‚ด์šฉ์ด๋ฉฐ, ์‹ค์ œ ์‚ฌ์šฉ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋” ์ ์ ˆํ•œ ๊ฐœ์„  ๋ฐฉ์•ˆ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ๊ฐ€ ํ”„๋กœ์ ํŠธ์— ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/repository/ProblemRepository.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. JpaRepository๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ถ”๊ฐ€์ ์œผ๋กœ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์— ์†ํ•˜๋Š” ๋ฌธ์ œ๋“ค์„ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์งง๊ณ  ๋‹จ์ˆœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํฐ ๋ฌธ์ œ๋Š” ์—†์–ด ๋ณด์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๋งค์šฐ ์ฝ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฆ„, ๋ฉ”์„œ๋“œ ์ด๋ฆ„, ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„ ๋“ฑ์ด ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.
  • ํ’ˆ์งˆ: JpaRepository๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜์—ฌ ์ฝ”๋“œ์˜ ์ค‘๋ณต์„ ์ค„์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Spring Data JPA์˜ ์ปจ๋ฒค์…˜์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: findByProblemCategory ๋ฉ”์„œ๋“œ์—์„œ category๊ฐ€ null์ผ ๊ฒฝ์šฐ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋™์ž‘์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ProblemCategory๊ฐ€ ํ•„์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ผ๋ฉด null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ํ•˜์ง€๋งŒ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ validateํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.)
  • ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ: Problem ์—”ํ‹ฐํ‹ฐ์™€ ProblemCategory ์—”ํ‹ฐํ‹ฐ ๊ฐ„์˜ ๊ด€๊ณ„ ์„ค์ • (OneToMany, ManyToOne ๋“ฑ)์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ด€๊ณ„ ์„ค์ •์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ํ™•์ธํ•˜๊ณ , ํ•„์š”ํ•˜๋‹ค๋ฉด ์—ฐ๊ด€๊ด€๊ณ„ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ๋กœ์ง์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ถ€์žฌ: JpaRepository๋Š” ๋‹ค์–‘ํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด DB ์—ฐ๊ฒฐ ์‹คํŒจ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜ ๋“ฑ. ์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๋ฅผ ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Repository ๋ ˆ์ด์–ด์—์„œ๋Š” ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๊ณ , ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ์ธ๋ฑ์Šค: problem_category_id ์ปฌ๋Ÿผ์— ์ ์ ˆํ•œ ์ธ๋ฑ์Šค๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ธ๋ฑ์Šค๊ฐ€ ์—†์œผ๋ฉด findByProblemCategory ์ฟผ๋ฆฌ์˜ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPA์˜ @Index ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ๋ฑ์Šค๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฟผ๋ฆฌ ์ตœ์ ํ™”: ํ˜„์žฌ ์ฝ”๋“œ๋Š” Spring Data JPA๊ฐ€ ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPQL์ด๋‚˜ Native Query๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ๋ณต์žกํ•œ ์กฐ๊ฑด ๊ฒ€์ƒ‰์ด๋‚˜ ์กฐ์ธ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ฟผ๋ฆฌ ์ตœ์ ํ™”๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ: Pageable์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋Š” ํšจ์œจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ ์‚ฌ์šฉ๋Ÿ‰์— ๋”ฐ๋ผ ์ถ”๊ฐ€์ ์ธ ์„ฑ๋Šฅ ๊ฐœ์„ ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” Problem ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring์˜ @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜, Redis์™€ ๊ฐ™์€ ์™ธ๋ถ€ ์บ์‹œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • SQL Injection: Spring Data JPA๋Š” PreparedStatement๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ SQL Injection ๊ณต๊ฒฉ์— ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Native Query๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ SQL Injection์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ฒ ์ €ํžˆ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ Problem ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ œํ•œํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: Problem ์—”ํ‹ฐํ‹ฐ์— ๋ฏผ๊ฐํ•œ ์ •๋ณด (์˜ˆ: ์‚ฌ์šฉ์ž ๊ฐœ์ธ ์ •๋ณด)๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ์ •๋ณด๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ฑฐ๋‚˜, ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋”์šฑ ์—„๊ฒฉํ•˜๊ฒŒ ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • Null Check: findByProblemCategory ๋ฉ”์„œ๋“œ์— category ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค.
  • ์ธ๋ฑ์Šค ์ถ”๊ฐ€: problem_category_id ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค. @Index ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์— ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฟผ๋ฆฌ ์ตœ์ ํ™” (ํ•„์š”ํ•œ ๊ฒฝ์šฐ): ๋งŒ์•ฝ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPQL์ด๋‚˜ Native Query๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ตœ์ ํ™”ํ•˜์‹ญ์‹œ์˜ค.
  • ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ ๊ด€๋ฆฌ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ): Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•˜์‹ญ์‹œ์˜ค.
  • ์บ์‹ฑ ์ „๋žต (ํ•„์š”ํ•œ ๊ฒฝ์šฐ): ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” Problem ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค์‹ญ์‹œ์˜ค. Spring์˜ @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์ด๋‚˜ Redis์™€ ๊ฐ™์€ ์™ธ๋ถ€ ์บ์‹œ ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: Repository ๋ ˆ์ด์–ด์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜์‹ญ์‹œ์˜ค.

๊ฒฐ๋ก :

ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋น„๊ต์  ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ๊ณผ ๊ฐœ์„  ํฌ์ธํŠธ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋”์šฑ ์•ˆ์ •์ ์ด๊ณ  ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์‹ค์ œ ์‚ฌ์šฉ๋Ÿ‰๊ณผ ๋ฐ์ดํ„ฐ์˜ ํŠน์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์„ฑ๋Šฅ ๋ฐ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜๋Š” ๊ฒƒ๋„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค.

Copy link

src/main/java/site/haruhana/www/Service/ProblemService.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/Service/ProblemService.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ CRUD ๋กœ์ง์„ ์ž˜ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ฐ€๋…์„ฑ๋„ ๋‚˜์˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. lombok์„ ํ™œ์šฉํ•˜์—ฌ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ธ ์ ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๊ฐ€๋…์„ฑ: ์ „๋ฐ˜์ ์œผ๋กœ ๋ฉ”์†Œ๋“œ ์ด๋ฆ„์ด ๋ช…ํ™•ํ•˜๊ณ  ์ฃผ์„๋„ ์ž˜ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ updateProblem ๋ฉ”์†Œ๋“œ์˜ ์ฃผ์„์— "๋ฌธ์ œ ์‚ญ์„ธ"๋ผ๊ณ  ์˜คํƒ€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: DTO์™€ Entity๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ์ผ๊ด€์„ฑ์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. getProblemById์—์„œ๋Š” DTO๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ๋ฉ”์†Œ๋“œ์—์„œ๋Š” Entity๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. DTO ์‚ฌ์šฉ ์ „๋žต์„ ๋ช…ํ™•ํžˆ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์—ญํ•  ๋ถ„๋ฆฌ: Problem Entity ๋‚ด๋ถ€์— updateProblem ๋ฉ”์†Œ๋“œ๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ์„ค๊ณ„์ž…๋‹ˆ๋‹ค. Entity๊ฐ€ ์ž์‹ ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์„ ์ง์ ‘ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์€ ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ์ธก๋ฉด์—์„œ ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • updateProblem ํŠธ๋žœ์žญ์…˜: updateProblem ๋ฉ”์„œ๋“œ์— @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ์ง€๋งŒ, problemRepository.save(problem)์„ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. JPA์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ ์ž๋™์œผ๋กœ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ฐ์ง€ํ•˜์—ฌ DB์— ๋ฐ˜์˜ํ•˜๋ฏ€๋กœ save ๋ฉ”์„œ๋“œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ๋™์ผ Entity๋ฅผ ๋‘ ๋ฒˆ ์ €์žฅํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ฒซ๋ฒˆ์งธ๋Š” ๋ณ€๊ฒฝ ๊ฐ์ง€์— ์˜ํ•œ ์ €์žฅ, ๋‘๋ฒˆ์งธ๋Š” save๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์— ์˜ํ•œ ์ €์žฅ)
  • updateProblem Entity ์ƒํƒœ ๋ณ€๊ฒฝ: updateProblem ๋ฉ”์„œ๋“œ์—์„œ problem.updateProblem(...)์„ ํ˜ธ์ถœํ•˜์—ฌ Entity์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€๋งŒ, ๋งŒ์•ฝ updatedProblem์˜ ํŠน์ • ํ•„๋“œ ๊ฐ’์ด null์ด๋ผ๋ฉด, updateProblem ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ null ์ฒดํฌ ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด Entity์˜ ํ•„๋“œ๊ฐ€ ์˜๋„์น˜ ์•Š๊ฒŒ null ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemById ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: NoSuchElementException์„ ๋˜์ง€๋Š” ๊ฒƒ์€ ์ ์ ˆํ•˜์ง€๋งŒ, ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ (์˜ˆ: ResourceNotFoundException)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ์ข€ ๋” ์‚ฌ์šฉ์ž ์นœํ™”์ ์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. (์˜ˆ: "ID์— ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • N+1 ๋ฌธ์ œ: getAllProblems ๋ฉ”์„œ๋“œ๋Š” ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฏ€๋กœ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์งˆ ๊ฒฝ์šฐ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋˜๋Š” ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: getProblemById ๋ฉ”์„œ๋“œ๋Š” ์ž์ฃผ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์บ์‹ฑ์„ ์ ์šฉํ•˜์—ฌ DB ์ ‘๊ทผ ํšŸ์ˆ˜๋ฅผ ์ค„์ด๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: Redis, Ehcache)
  • ์ธ๋ฑ์‹ฑ: getProblemsByCategory ๋ฉ”์„œ๋“œ๋Š” ProblemCategory ๊ธฐ์ค€์œผ๋กœ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, Problem Entity์˜ problemCategory ํ•„๋“œ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: createProblem ๋ฐ updateProblem ๋ฉ”์„œ๋“œ์—์„œ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์ „ํ˜€ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๋ชฉ, ์„ค๋ช…, ๋ ˆ๋ฒจ ๋“ฑ ์ค‘์š”ํ•œ ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ํ™•๋ณดํ•˜๊ณ , ์ž ์žฌ์ ์ธ ๊ณต๊ฒฉ (์˜ˆ: SQL Injection)์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด (RBAC)๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • DTO ์‚ฌ์šฉ ์ „๋žต ํ†ต์ผ: Entity๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ๋ชจ๋“  API์—์„œ DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ตํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค. ์ด๋ฅผ ํ†ตํ•ด Entity์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ ๋ณ€ํ™”๋กœ๋ถ€ํ„ฐ API๋ฅผ ๋ณดํ˜ธํ•˜๊ณ , ๋ถˆํ•„์š”ํ•œ ํ•„๋“œ๊ฐ€ ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Validation ์ถ”๊ฐ€: javax.validation.constraints ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ Entity ๋˜๋Š” DTO์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์ •์˜ํ•˜๊ณ , Spring์˜ @Valid ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์‹ญ์‹œ์˜ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ผ๊ด€์„ฑ: NoSuchElementException ๋Œ€์‹ , ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ (์˜ˆ: ProblemNotFoundException)๋ฅผ ๋งŒ๋“ค๊ณ , Spring์˜ @ControllerAdvice๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ์ค‘์•™ ์ง‘์ค‘์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์‹ญ์‹œ์˜ค.
  • updateProblem ๋ฉ”์„œ๋“œ ๊ฐœ์„ :
    • updatedProblem์˜ ํ•„๋“œ๊ฐ€ null์ผ ๊ฒฝ์šฐ, ๊ธฐ์กด ๊ฐ’์„ ์œ ์ง€ํ•˜๋„๋ก ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.
    • problemRepository.save(problem) ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•˜์‹ญ์‹œ์˜ค. JPA์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜์—ฌ DB์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  • getAllProblems ๋ฉ”์„œ๋“œ ๊ฐœ์„ : ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์ ์šฉํ•˜๊ฑฐ๋‚˜, ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜์‹ญ์‹œ์˜ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๊ตฌํ˜„ํ•˜์‹ญ์‹œ์˜ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜์‹ญ์‹œ์˜ค.
  • ๋กœ๊น…: ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ๋””๋ฒ„๊น…์„ ์šฉ์ดํ•˜๊ฒŒ ํ•˜์‹ญ์‹œ์˜ค.

์ˆ˜์ • ์˜ˆ์‹œ (updateProblem ๋ฉ”์„œ๋“œ):

@Transactional
public Problem updateProblem(Long id, Problem updatedProblem) {
    Problem problem = problemRepository.findById(id)
            .orElseThrow(() -> new NoSuchElementException("Problem not found"));

    // ํ•„๋“œ๊ฐ€ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์—…๋ฐ์ดํŠธ
    if (updatedProblem.getTitle() != null) {
        problem.setTitle(updatedProblem.getTitle());
    }
    if (updatedProblem.getDescription() != null) {
        problem.setDescription(updatedProblem.getDescription());
    }
    if (updatedProblem.getLevel() != null) {
        problem.setLevel(updatedProblem.getLevel());
    }
    if (updatedProblem.getProblemCategory() != null) {
        problem.setProblemCategory(updatedProblem.getProblemCategory());
    }

    // problemRepository.save(problem); // JPA๊ฐ€ ์ž๋™ ์ €์žฅํ•˜๋ฏ€๋กœ ๋ถˆํ•„์š”
    return problem;
}

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ์œ„์— ์–ธ๊ธ‰๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ๊ถŒํ•œ ๊ฒ€์‚ฌ, DTO ์‚ฌ์šฉ ์ „๋žต, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ์„ฑ๋Šฅ ๊ฐœ์„ ์— ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

ProblemController.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ ProblemController.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ƒ์„ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์ธ ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ์ž˜ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋Š” ์—ญํ• ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ:
    • @RequiredArgsConstructor ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ์˜์กด์„ฑ ๊ด€๋ฆฌ๊ฐ€ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.
    • HTTP ๋ฉ”์„œ๋“œ์™€ ์—”๋“œํฌ์ธํŠธ ๋งคํ•‘์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋˜์–ด ์žˆ์–ด ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
    • try-catch ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ถ€๋ถ„๋„ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.
    • ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ResponseEntity๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HTTP ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ ์€ ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :
    • ์ฃผ์„์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ๊ณผ ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฐ˜ํ™˜ ๊ฐ’์— ๋Œ€ํ•œ ์„ค๋ช…์„ Javadoc ํ˜•ํƒœ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • getProblemsByCategory ์—”๋“œํฌ์ธํŠธ์˜ /problems ๋งคํ•‘์€ ์ƒ์œ„ @RequestMapping("/problems")๊ณผ ์ถฉ๋Œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ข€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ 

  • updateProblem์—์„œ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ถ€์žฌ: updateProblem ๋ฉ”์„œ๋“œ์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ Problem ๊ฐ์ฒด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ธฐ ์ „์— ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์ฑ„์›Œ์ ธ ์žˆ๋Š”์ง€, ๋ฐ์ดํ„ฐ ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ๋“ฑ์„ ๊ฒ€์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์ด ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ๋‚ด์šฉ์ด ๋„ˆ๋ฌด ๊ธธ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ฑฐ๋‚˜, ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๋“ฑ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • createProblem์—์„œ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ถ€์žฌ: createProblem ๋ฉ”์„œ๋“œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory ์—”๋“œํฌ์ธํŠธ ๋งคํ•‘ ์ค‘๋ณต: ์ƒ์œ„ ๋ ˆ๋ฒจ์˜ @RequestMapping("/problems")๊ณผ ํ•˜์œ„ ๋ ˆ๋ฒจ์˜ @GetMapping("/problems")์ด ์ค‘๋ณต๋˜์–ด ํ˜ผ๋ž€์„ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์œ„ ๋ ˆ๋ฒจ์˜ ๋งคํ•‘์„ /category ๋˜๋Š” /by-category ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • getAllProblems ์—”๋“œํฌ์ธํŠธ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ๋ถ€์žฌ: ๋ฌธ์ œ ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ ๊ฒฝ์šฐ, ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ์„ฑ๋Šฅ์— ๋ถ€๋‹ด์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • DTO ๋ฏธ์‚ฌ์šฉ: getAllProblems์€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด API ์‘๋‹ต๋„ ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • updateProblem์—์„œ ID ๋ถˆ์ผ์น˜ ์ฒ˜๋ฆฌ: updateProblem์—์„œ @PathVariable Long id ์™€ @RequestBody Problem problem ์˜ ID ๊ฐ€ ๋‹ค๋ฅผ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ: ProblemService์—์„œ getAllProblems๋‚˜ getProblemsByCategory ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ๋กœ๋”ฉํ•œ๋‹ค๋ฉด N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPA์˜ Fetch Join์ด๋‚˜ Entity Graph ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๋„๋ก ๊ฐœ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring์˜ @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ Redis์™€ ๊ฐ™์€ ์™ธ๋ถ€ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ: ๋ฌธ์ œ ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ ๊ฒฝ์šฐ, getAllProblems ์—”๋“œํฌ์ธํŠธ์— ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ์ ์šฉํ•˜์—ฌ ์‘๋‹ต ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฟผ๋ฆฌ ์ตœ์ ํ™”: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ๋ถ„์„ํ•˜๊ณ , ์ธ๋ฑ์Šค๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์‹คํ–‰ ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ถ€์žฌ: ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋ถ€์กฑํ•˜์—ฌ SQL Injection, XSS ๊ณต๊ฒฉ ๋“ฑ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ธ์ฝ”๋”ฉํ•˜์—ฌ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Validation์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด(RBAC)๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ํŠน์ • ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์ œํ•œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด, API ํ‚ค ๋“ฑ์˜ ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ์ฝ”๋“œ์— ์ง์ ‘ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๋ณ„๋„์˜ ์„ค์ • ํŒŒ์ผ์— ์ €์žฅํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • CSRF ๋ฐฉ์–ด: ์›น ํŽ˜์ด์ง€์—์„œ Cross-Site Request Forgery (CSRF) ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•ด Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” CSRF protection์„ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€:
    • createProblem, updateProblem ๋ฉ”์„œ๋“œ์— @Valid ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ Bean Validation์„ ์ ์šฉํ•˜๊ณ , Problem DTO์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
    • ProblemDto์— @NotBlank, @Size, @NotNull ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
  2. DTO ์‚ฌ์šฉ:
    • getAllProblems ์—”๋“œํฌ์ธํŠธ์—์„œ Problem ์—”ํ‹ฐํ‹ฐ ๋Œ€์‹  ProblemDto ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
    • ProblemDto๋Š” ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•  ํ•„๋“œ๋งŒ ํฌํ•จํ•˜๋„๋ก ์„ค๊ณ„ํ•˜์—ฌ ๋ณด์•ˆ ๋ฐ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  3. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐœ์„ :
    • NoSuchElementException ์™ธ์— ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ์˜ˆ์™ธ (์˜ˆ: DataIntegrityViolationException)์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • @ControllerAdvice๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์ผ๊ด€๋œ ํ˜•์‹์˜ ์—๋Ÿฌ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  4. getProblemsByCategory ์—”๋“œํฌ์ธํŠธ ์ˆ˜์ •:
    • ์—”๋“œํฌ์ธํŠธ ๋งคํ•‘์„ /categories ๋˜๋Š” /by-category ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ƒ์œ„ ๋ ˆ๋ฒจ ๋งคํ•‘๊ณผ์˜ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  5. ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ์ถ”๊ฐ€:
    • getAllProblems ์—”๋“œํฌ์ธํŠธ์— @PageableDefault ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  6. ๋ณด์•ˆ ๊ฐ•ํ™”:
    • Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
    • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๊ฐ•ํ™”ํ•˜๊ณ , CSRF protection์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  7. ๋กœ๊ทธ ์ถ”๊ฐ€:
    • Controller, Service ๋ ˆ์ด์–ด์— ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ๋””๋ฒ„๊น…์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
    • org.slf4j.Logger๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  8. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ:
    • Controller, Service ๋ ˆ์ด์–ด์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฆฌ๋ทฐ๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์„ ๊ฐœ์„ ํ•˜๊ณ  ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

ProblemRepository.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ src/main/java/site/haruhana/www/repository/ProblemRepository.java ํŒŒ์ผ์„ ๋ฆฌ๋ทฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ฝ”๋“œ ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๋‹จํ•˜๊ณ  ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค. Spring Data JPA์˜ JpaRepository๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ๋ฅผ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌํ•˜์—ฌ ์กฐํšŒํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฆ„๊ณผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ์ง๊ด€์ ์ด์–ด์„œ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. findByProblemCategory ๋ฉ”์„œ๋“œ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ์กฐํšŒ๋ผ๋Š” ๋ชฉ์ ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ํŠน๋ณ„ํ•œ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ ์ด ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Spring Data JPA์˜ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋„ ์ ์ ˆํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • null ์ฒ˜๋ฆฌ: ProblemCategory๊ฐ€ null์ธ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ null ๊ฐ’์ด ๋“ค์–ด์˜ฌ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค๋ฉด ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ProblemCategory๊ฐ€ null์ธ ๊ฒฝ์šฐ, ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์ด ์˜ˆ์ƒ๋˜๋Š” ๋™์ž‘์ธ์ง€ ์•„๋‹ˆ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•˜๋Š”์ง€ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ: findByProblemCategory ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, Problem ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด @EntityGraph ๋˜๋Š” JPQL์˜ JOIN FETCH๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ๋กœ๋”ฉํ•˜๋„๋ก ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, Problem ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ์žˆ๋‹ค๋ฉด ์ ๊ทน์ ์œผ๋กœ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง• ์„ฑ๋Šฅ: ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์•„์งˆ ๊ฒฝ์šฐ, ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๊ฐ€ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์ธ๋ฑ์Šค๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ProblemCategory์— ๋Œ€ํ•œ ์ธ๋ฑ์Šค๋ฅผ ํ™•์ธํ•˜๊ณ  ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ๋ชฉ๋ก์€ ์บ์‹ฑ์„ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜, Ehcache, Redis ๋“ฑ์˜ ์บ์‹œ ์†”๋ฃจ์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ์— ๋Œ€ํ•œ ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ์ด์Šˆ๋Š” ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ์‚ฌ์šฉ์ž ๊ถŒํ•œ์— ๋”ฐ๋ผ ๋ฌธ์ œ ์กฐํšŒ ๊ถŒํ•œ์„ ์ œํ•œํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection: Spring Data JPA๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์‚ฌ์šฉํ•˜์—ฌ SQL Injection ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•  ๊ฒฝ์šฐ SQL Injection์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • null ์ฒ˜๋ฆฌ ์ถ”๊ฐ€: ProblemCategory๊ฐ€ null์ธ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, null์ธ ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋“ฑ์˜ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
    
    // ๊ฐœ์„ ๋œ ์ฝ”๋“œ ์˜ˆ์‹œ
    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
    Page<Problem> findAll(Pageable pageable); // category๊ฐ€ null์ธ ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์กฐํšŒ
  • N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: Problem ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด @EntityGraph ๋˜๋Š” JOIN FETCH๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

    // ์˜ˆ์‹œ: ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ 'author'๋ฅผ ํ•จ๊ป˜ ๋กœ๋”ฉ
    @EntityGraph(attributePaths = {"author"})
    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
  • ํŽ˜์ด์ง• ์„ฑ๋Šฅ ์ตœ์ ํ™”: ProblemCategory ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํŽ˜์ด์ง• ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

    -- MySQL ์˜ˆ์‹œ
    CREATE INDEX idx_problem_category ON problem (problem_category_id);
  • ์บ์‹ฑ ๋„์ž…: ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ๋ชฉ๋ก์€ ์บ์‹ฑ์„ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

    // ์˜ˆ์‹œ: Spring Cache Abstraction ์‚ฌ์šฉ
    @Cacheable("problemByCategory")
    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
  • ์ ‘๊ทผ ์ œ์–ด ๊ตฌํ˜„: ์‚ฌ์šฉ์ž ๊ถŒํ•œ์— ๋”ฐ๋ผ ๋ฌธ์ œ ์กฐํšŒ ๊ถŒํ•œ์„ ์ œํ•œํ•ด์•ผ ํ•  ๊ฒฝ์šฐ, Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ, null ์ฒ˜๋ฆฌ, N+1 ๋ฌธ์ œ, ํŽ˜์ด์ง• ์„ฑ๋Šฅ, ๋ณด์•ˆ ๋“ฑ์˜ ์ธก๋ฉด์—์„œ ๊ฐœ์„ ์˜ ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์•ˆ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜์—ฌ ์ฝ”๋“œ ํ’ˆ์งˆ๊ณผ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ๋ณด์•ˆ ๋ฐ ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ฐ•ํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

ProblemDto.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ProblemDto.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•ญ๋ชฉ์„ ์ค‘์‹ฌ์œผ๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

  1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ
  2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ 
  3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ
  4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ
  5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

์ฝ”๋“œ:

package site.haruhana.www.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import site.haruhana.www.entity.ProblemCategory;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ProblemDto {
    private Long id;
    private String title;
    private String description;
    private Integer level;
    private ProblemCategory problemCategory;
}

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์žฅ์ :
    • Lombok ์–ด๋…ธํ…Œ์ด์…˜(@Getter, @NoArgsConstructor, @AllArgsConstructor)์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
    • ํ•„๋“œ ์ด๋ฆ„์€ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚ด๊ณ  ์žˆ์–ด ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
    • ํด๋ž˜์Šค ์ด๋ฆ„(ProblemDto)์€ ํด๋ž˜์Šค์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :
    • javadoc ๋ถ€์žฌ: ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ javadoc์ด ์—†์–ด ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช… (์˜ˆ: @param id ๋ฌธ์ œ ID)์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
    • ProblemCategory ์ฃผ์„: ProblemCategory์— ๋Œ€ํ•œ ์ฃผ์„์ด ์—†์–ด ํ•ด๋‹น Enum ๋˜๋Š” Entity๊ฐ€ ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋Š”์ง€ ๋ฐ”๋กœ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. (์˜ˆ: @param problemCategory ๋ฌธ์ œ์˜ ์นดํ…Œ๊ณ ๋ฆฌ (์˜ˆ: ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ž๋ฃŒ๊ตฌ์กฐ))

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ 

  • Null ๊ฐ€๋Šฅ์„ฑ: String title, String description, ProblemCategory problemCategory ํ•„๋“œ๋Š” null ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ null ๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์ƒ์„ฑ์ž ๋˜๋Š” setter์—์„œ null ์ฒดํฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜, @NotNull ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ถ€์žฌ: level ํ•„๋“œ์˜ ๋ฒ”์œ„์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ ˆ๋ฒจ์ด ํŠน์ • ๋ฒ”์œ„ ๋‚ด์— ์žˆ์–ด์•ผ ํ•œ๋‹ค๋ฉด, ์ƒ์„ฑ์ž ๋˜๋Š” setter์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • DTO ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ์„ฑ: DTO๋Š” ์ฃผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ์ฒด๋กœ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. final ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ๋ฅผ ๋ถˆ๋ณ€์œผ๋กœ ๋งŒ๋“ค๊ณ , ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ฐ’์„ ํ• ๋‹นํ•˜๋„๋ก ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ์บ์‹ฑ ์ „๋žต: ProblemDto๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค๋ฉด ์บ์‹ฑ ์ „๋žต์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ์—ญํ• ๋งŒ ํ•˜๋ฏ€๋กœ, ์„ฑ๋Šฅ ๊ฐœ์„ ์˜ ์šฐ์„ ์ˆœ์œ„๋Š” ๋‚ฎ์Šต๋‹ˆ๋‹ค.
  • ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์ „๋‹ฌ: API ํ˜ธ์ถœ ์‹œ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด, ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๋‹ด๋Š” DTO๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋„คํŠธ์›Œํฌ ๋น„์šฉ์„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection ๋ฐฉ์ง€: ProblemDto๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ง์ ‘์ ์œผ๋กœ ์ƒํ˜ธ์ž‘์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ SQL Injection ๋ฌธ์ œ์™€๋Š” ์ง์ ‘์ ์ธ ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ProblemDto์— ๋‹ด๊ธด ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ SQL Injection์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • XSS ๋ฐฉ์ง€: description ํ•„๋“œ์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ํฌํ•จ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค๋ฉด, XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ธก ๋˜๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ์ ์ ˆํ•œ XSS ๋ฐฉ์–ด ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • ํ•„๋“œ ์ฃผ์„ ์ถ”๊ฐ€: ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ javadoc์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • Nullability ๋ช…์‹œ: @NotNull ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ null ๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š” ํ•„๋“œ๋ฅผ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: level ํ•„๋“œ์˜ ๋ฒ”์œ„์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ถˆ๋ณ€ ๊ฐ์ฒด ๊ณ ๋ ค: final ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ๋ฅผ ๋ถˆ๋ณ€์œผ๋กœ ๋งŒ๋“ค๊ณ , ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ฐ’์„ ํ• ๋‹นํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜์—ฌ DTO๋ฅผ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. (์„ ํƒ ์‚ฌํ•ญ, ํ•˜์ง€๋งŒ ๊ถŒ์žฅ)
  • XSS ๋ฐฉ์–ด: description ํ•„๋“œ์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ํฌํ•จ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค๋ฉด, XSS ๋ฐฉ์–ด ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • Builder ํŒจํ„ด ๊ณ ๋ ค: ๊ฐ์ฒด ์ƒ์„ฑ์„ ๋”์šฑ ์œ ์—ฐํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด Builder ํŒจํ„ด์„ Lombok์˜ @Builder ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Equals & Hashcode ๊ตฌํ˜„: ProblemDto ๊ฐ์ฒด๋ฅผ Set ๋˜๋Š” Map์˜ key๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด equals์™€ hashCode ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Lombok์˜ @EqualsAndHashCode ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ˆ˜์ • ์˜ˆ์‹œ:

package site.haruhana.www.dto;

import lombok.*;
import site.haruhana.www.entity.ProblemCategory;

import javax.validation.constraints.NotNull;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class ProblemDto {
    /**
     * ๋ฌธ์ œ ID
     */
    private Long id;

    /**
     * ๋ฌธ์ œ ์ œ๋ชฉ
     */
    @NotNull
    private String title;

    /**
     * ๋ฌธ์ œ ์„ค๋ช…
     */
    private String description;

    /**
     * ๋ฌธ์ œ ๋ ˆ๋ฒจ (1-5)
     */
    private Integer level;

    /**
     * ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ (์˜ˆ: ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ž๋ฃŒ๊ตฌ์กฐ)
     */
    private ProblemCategory problemCategory;
}

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ์ œ์•ˆ๋“ค์„ ๊ณ ๋ คํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/Service/ProblemService.java ๋ฆฌ๋ทฐ

ProblemService ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ๋กœ๊น…์„ ํ†ตํ•ด ๋™์ž‘์„ ์ถ”์ ํ•˜๊ธฐ ์šฉ์ดํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋ ค๊ณ  ๋…ธ๋ ฅํ•œ ์ ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„, ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ , ๊ทธ๋ฆฌ๊ณ  ์„ฑ๋Šฅ๊ณผ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๊ฐ€๋…์„ฑ: ์ „์ฒด์ ์œผ๋กœ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ๋ช…ํ™•ํ•˜๊ณ , ์ฃผ์„์ด ์ ์ ˆํ•˜๊ฒŒ ๋‹ฌ๋ ค ์žˆ์–ด ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ getProblemById ๋ฉ”์„œ๋“œ ๋‚ด log.error ๋Š” ์ ์ ˆํ•œ ์œ„์น˜์— ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ๋กœ๊น…์„ ํ†ตํ•ด ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ๋””๋ฒ„๊น…์ด ์šฉ์ดํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. DI (Dependency Injection) ๋ฅผ ํ†ตํ•ด ProblemRepository๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ˜ ์ ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • getProblemById ๋ฉ”์„œ๋“œ์˜ ๋กœ๊น…: NoSuchElementException ๋ฐœ์ƒ ํ›„์— log.error๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋กœ๊น…์ด ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฐœ์ƒ ์ „์— ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, Exception handling ์€ Service layer ์—์„œ ํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค Controller layer ์—์„œ ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค. Service layer ์—์„œ๋Š” Exception ์„ throw ํ•˜๊ณ , Controller layer ์—์„œ ExceptionHandler ๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

  • updateProblem ๋ฉ”์„œ๋“œ์˜ problemRepository.save(problem): @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ๊ณ , JPA์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค๋ฉด, problemRepository.save(problem)๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„๋„ ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์ ์— ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์ž๋™์œผ๋กœ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ save๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ํ•˜์ง€๋งŒ updateProblem ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ problem ๊ฐ์ฒด๊ฐ€ ๋ถ„๋ฆฌ(detached)๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค๋ฉด save๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

  • updateProblem ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ: updateProblem ๋ฉ”์„œ๋“œ๋Š” Problem ์—”ํ‹ฐํ‹ฐ ์ „์ฒด๋ฅผ ๋ฐ›์•„์„œ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ถˆํ•„์š”ํ•œ ํ•„๋“œ๊นŒ์ง€ ๋ชจ๋‘ ๋ณด๋‚ด์•ผ ํ•˜๋Š” ๋ถ€๋‹ด์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜๋„์น˜ ์•Š๊ฒŒ null ๊ฐ’์„ ๋ณด๋‚ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฎ์–ด์”Œ์›Œ์งˆ ์œ„ํ—˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์—…๋ฐ์ดํŠธํ•  ํ•„๋“œ๋งŒ ๋‹ด์€ DTO๋ฅผ ๋ณ„๋„๋กœ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: NoSuchElementException์€ ๋Ÿฐํƒ€์ž„ ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๋ฅผ catchํ•˜์—ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋งž๋Š” ๋‹ค๋ฅธ ์˜ˆ์™ธ๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜, ์•„๋‹ˆ๋ฉด Controller layer์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•„ 500 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • N+1 ๋ฌธ์ œ: getAllProblems ๋ฉ”์„œ๋“œ์—์„œ findAll()์„ ํ˜ธ์ถœํ•  ๋•Œ, ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Fetch Join์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒํ•˜๋„๋ก ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์บ์‹ฑ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ (์˜ˆ: ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก)๋Š” ์บ์‹ฑ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ํšŸ์ˆ˜๋ฅผ ์ค„์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Pageable ์‚ฌ์šฉ: getAllProblems ๋ฉ”์†Œ๋“œ๋Š” ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฏ€๋กœ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์งˆ ๊ฒฝ์šฐ ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Pageable์„ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • SQL Injection: ProblemRepository์—์„œ ์ง์ ‘ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPA์˜ ๋ฉ”์„œ๋“œ๋‚˜ Named Parameter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SQL Injection์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์ œ๊ณต๋œ ์ฝ”๋“œ์—๋Š” ์ง์ ‘์ ์ธ SQL ์ฟผ๋ฆฌ๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํ•ด๋‹น์‚ฌํ•ญ ์—†์Œ)
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ: ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•˜๋Š” API๋Š” ์ ์ ˆํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: ๋ฌธ์ œ ์ƒ์„ฑ/์ˆ˜์ • ์‹œ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์˜ ๊ธธ์ด, ์„ค๋ช…์˜ ๊ธธ์ด, ๋ ˆ๋ฒจ์˜ ๋ฒ”์œ„ ๋“ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Validation์„ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ๋ฆฌํ•˜๊ฒŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  1. getProblemById ๋ฉ”์„œ๋“œ์˜ ๋กœ๊น… ์œ„์น˜ ์ˆ˜์ •: log.error ํ˜ธ์ถœ์„ problemRepository.findById(id) ์ง์ „ ๋˜๋Š” .orElseThrow() ์•ˆ์œผ๋กœ ์˜ฎ๊น๋‹ˆ๋‹ค. ๋˜ํ•œ Controller Layer ์—์„œ ExceptionHandler ๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

    public ProblemDto getProblemById(Long id) {
        log.info("Fetching problem with ID: {}", id);
        Problem problem = problemRepository.findById(id)
                .orElseThrow(() -> {
                    log.error("Problem not found with ID: {}", id);
                    return new NoSuchElementException("Problem not found");
                });
    
        // ์ƒ์„ฑ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ProblemDto ๊ฐ์ฒด ์ƒ์„ฑ
        return new ProblemDto(problem.getId(), problem.getTitle(), problem.getDescription(), problem.getLevel(), problem.getProblemCategory());
    }
  2. updateProblem ๋ฉ”์„œ๋“œ์˜ ๋ถˆํ•„์š”ํ•œ save ์ œ๊ฑฐ ์—ฌ๋ถ€ ํ™•์ธ: JPA ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์ดํ•ดํ•˜๊ณ , save ํ˜ธ์ถœ์ด ์ •๋ง ํ•„์š”ํ•œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ œ๊ฑฐ ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ detached ์ƒํƒœ๊ฐ€ ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค๋ฉด save๋Š” ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  3. updateProblem ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ: Problem ์—”ํ‹ฐํ‹ฐ ๋Œ€์‹  ์—…๋ฐ์ดํŠธํ•  ํ•„๋“œ๋งŒ ๋‹ด์€ DTO๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์˜ ๋ถ€๋‹ด์„ ์ค„์ด๊ณ , ๋ฐ์ดํ„ฐ ๋ฎ์–ด์“ฐ๊ธฐ ์œ„ํ—˜์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// DTO ์˜ˆ์‹œ
@Data
public class UpdateProblemRequest {
    private String title;
    private String description;
    private Integer level;
    private ProblemCategory problemCategory;
}

// Service ๋ฉ”์„œ๋“œ ์ˆ˜์ •
@Transactional
public Problem updateProblem(Long id, UpdateProblemRequest request) {
    log.info("Updating problem with ID: {}", id);
    Problem problem = problemRepository.findById(id)
            .orElseThrow(() -> new NoSuchElementException("Problem not found"));

    problem.updateProblem(
            request.getTitle(),
            request.getDescription(),
            request.getLevel(),
            request.getProblemCategory()
    );

    return problem; // save ๋ถˆํ•„์š”
}
  1. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: getAllProblems์—์„œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•ด์•ผ ํ•œ๋‹ค๋ฉด, Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๋˜๋Š” DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

  2. ์บ์‹ฑ ์ ์šฉ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ (์˜ˆ: ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ)๋Š” ์บ์‹ฑ์„ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์บ์‹ฑ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  3. ๊ถŒํ•œ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•˜๋Š” API์— Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

  4. ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: ๋ฌธ์ œ ์ƒ์„ฑ/์ˆ˜์ • ์‹œ Spring Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  5. Pageable ์‚ฌ์šฉ: getAllProblems ๋ฉ”์†Œ๋“œ๋ฅผ Page<Problem> getAllProblems(Pageable pageable) ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ์œ„์— ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ฑ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, N+1 ๋ฌธ์ œ, ๊ถŒํ•œ ๊ฒ€์‚ฌ, ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋“ฑ์€ ์‹ค์ œ ์„œ๋น„์Šค ์šด์˜์—์„œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ˆ ๋ฐ˜๋“œ์‹œ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

ProblemController.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/controller/ProblemController.java ํŒŒ์ผ์˜ ์ฝ”๋“œ์— ๋Œ€ํ•œ ์ƒ์„ธ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๊ฒฐํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. RESTful API์˜ ๊ธฐ๋ณธ ๊ทœ์น™์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • Lombok ํ™œ์šฉ: @RequiredArgsConstructor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•œ ์ ์€ ์ข‹์Šต๋‹ˆ๋‹ค.
  • RESTful API ์ค€์ˆ˜: HTTP ๋ฉ”์„œ๋“œ์™€ URL ๋งคํ•‘์„ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•˜์—ฌ RESTful ์›์น™์„ ์ค€์ˆ˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ช…๋ช… ๊ทœ์น™: ๋ณ€์ˆ˜๋ช…, ๋ฉ”์„œ๋“œ๋ช…, ํด๋ž˜์Šค๋ช…์ด ๋น„๊ต์  ์ผ๊ด€์„ฑ ์žˆ๊ณ  ์˜๋ฏธ๋ฅผ ์ž˜ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ์ฃผ์„ ๋ถ€์กฑ: ์ฝ”๋“œ ์ž์ฒด๋Š” ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šฐ๋‚˜, ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ๊ณผ ์ฑ…์ž„์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ฃผ์„์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ๋”์šฑ ๊ฐ€๋…์„ฑ์ด ๋†’์•„์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ๋ณต์žกํ•œ ๋กœ์ง์ด๋‚˜ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์ด ์ ์šฉ๋œ ๋ถ€๋ถ„์— ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค.
  • ๋ถˆํ•„์š”ํ•œ ๊ณต๋ฐฑ: ์ฝ”๋“œ ๋ผ์ธ ๋์— ๋ถˆํ•„์š”ํ•œ ๊ณต๋ฐฑ๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. IDE ์„ค์ •์„ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์ œ๊ฑฐํ•˜๋„๋ก ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ 

  • updateProblem ๋ฉ”์„œ๋“œ: updateProblem ๋ฉ”์„œ๋“œ์—์„œ ์—…๋ฐ์ดํŠธํ•  problem ๊ฐ์ฒด๋ฅผ ๋ฐ›๋Š”๋ฐ, ID๋ฅผ URL ๊ฒฝ๋กœ ๋ณ€์ˆ˜ (@PathVariable)๋กœ๋„ ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Service ๋ ˆ์ด์–ด์—์„œ ID๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์ด ์—†๋‹ค๋ฉด, ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: URL์˜ ID๋Š” 1์ธ๋ฐ, problem ๊ฐ์ฒด์˜ ID๋Š” 2์ธ ๊ฒฝ์šฐ)
  • createProblem ๋ฐ updateProblem์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋ถ€์กฑ: @RequestBody๋กœ ๋ฐ›๋Š” Problem ๊ฐ์ฒด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ธฐ ์ „์— ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์ฑ„์›Œ์ ธ ์žˆ๋Š”์ง€, ๋ฐ์ดํ„ฐ ํ˜•์‹(์˜ˆ: ๊ธธ์ด, ํŒจํ„ด)์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ๊ฒ€์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. @Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ Bean Validation์„ ํ™œ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: getProblemById์—์„œ NoSuchElementException๋งŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์˜ˆ์™ธ(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๋ฌธ์ œ, ๊ถŒํ•œ ๋ฌธ์ œ ๋“ฑ)๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ๋” ํฌ๊ด„์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต์„ ์ˆ˜๋ฆฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. try-catch ๋ธ”๋ก์„ ๋Š˜๋ฆฌ๊ธฐ๋ณด๋‹ค๋Š” @ControllerAdvice๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory ๋ฉ”์„œ๋“œ: ์ด ๋ฉ”์„œ๋“œ์˜ URL ๋งคํ•‘์ด /problems/problems๋กœ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ค‘๋ณต๋œ ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด ํ˜ผ๋ž€์„ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /problems/category ๋˜๋Š” /problems?category={category}์™€ ๊ฐ™์ด ๋” ์ง๊ด€์ ์ธ URL ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ ๊ฐ€๋Šฅ์„ฑ: getAllProblems, getProblemsByCategory ๋“ฑ ์—ฌ๋Ÿฌ ๋ฌธ์ œ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. Problem ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€๋œ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ, JPA์˜ Fetch Join ๋˜๋Š” EntityGraph๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๋„๋ก ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ „์†ก: getAllProblems๋Š” ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์•„์ง€๋ฉด ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ๊ณผ ํด๋ผ์ด์–ธํŠธ ์ธก ์ฒ˜๋ฆฌ ๋ถ€๋‹ด์ด ์ปค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์ ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜๋ˆ„์–ด ์ „์†กํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ(์˜ˆ: ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ฌธ์ œ ๋ชฉ๋ก)์— ๋Œ€ํ•ด์„œ๋Š” ์บ์‹ฑ ์ „๋žต์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์บ์‹ฑ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection: ProblemCategory๋ฅผ Enum์œผ๋กœ ์ง์ ‘ ๋ฐ›์•„ ์•ˆ์ „ํ•ด ๋ณด์ด์ง€๋งŒ, ๋‹ค๋ฅธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPA Named Parameter ๋˜๋Š” Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SQL Injection์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Cross-Site Scripting (XSS): ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋ง ์—†์ด ๊ทธ๋Œ€๋กœ ์›น ํŽ˜์ด์ง€์— ์ถœ๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋“  ๋ฌธ์ œ์— ์ ‘๊ทผ, ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด (RBAC) ๋˜๋Š” ์†์„ฑ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด (ABAC)๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • CSRF (Cross-Site Request Forgery): POST, PUT, DELETE ์š”์ฒญ์— ๋Œ€ํ•ด CSRF ๋ฐฉ์–ด ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด CSRF ํ† ํฐ์„ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: Problem ๊ฐ์ฒด์— ๋ฏผ๊ฐํ•œ ์ •๋ณด(์˜ˆ: ๊ฐœ์ธ ์ •๋ณด, ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด, API ์‘๋‹ต์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ œ์™ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • DTO ํ™œ์šฉ: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ API ์š”์ฒญ/์‘๋‹ต์— ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , DTO (Data Transfer Object)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. DTO๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—”ํ‹ฐํ‹ฐ์˜ ๋ณ€๊ฒฝ์— API๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์„ ํƒ์ ์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ProblemDto๊ฐ€ ์ด๋ฏธ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ์—์„œ๋„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค.
  • ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ถ”๊ฐ€: Bean Validation์„ ์‚ฌ์šฉํ•˜์—ฌ @RequestBody๋กœ ๋ฐ›๋Š” ๊ฐ์ฒด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.
    @PostMapping
    public ResponseEntity<Problem> createProblem(@Valid @RequestBody ProblemDto problemDto) {
        return new ResponseEntity<>(problemService.createProblem(problemDto), HttpStatus.CREATED);
    }
  • ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ ์šฉ: getAllProblems ๋ฉ”์„œ๋“œ์— ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์ ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์•„์ ธ๋„ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜์‹ญ์‹œ์˜ค.
    @GetMapping
    public ResponseEntity<Page<Problem>> getAllProblems(@PageableDefault Pageable pageable) {
        return ResponseEntity.ok(problemService.getAllProblems(pageable));
    }
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐ•ํ™”: @ControllerAdvice๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ๋ฐœ์ƒ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์‹ญ์‹œ์˜ค.
  • API ๋ฌธ์„œํ™”: Swagger ๋˜๋Š” OpenAPI Specification์„ ์‚ฌ์šฉํ•˜์—ฌ API ๋ฌธ์„œ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•˜๊ณ , API ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ API๋ฅผ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜์‹ญ์‹œ์˜ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: Controller, Service, Repository ๋ ˆ์ด์–ด์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜์‹ญ์‹œ์˜ค.

์ˆ˜์ •๋œ ์ฝ”๋“œ ์˜ˆ์‹œ (์ผ๋ถ€):

@RestController
@RequiredArgsConstructor
@RequestMapping("/problems")
public class ProblemController {

    private final ProblemService problemService;

    @GetMapping
    public ResponseEntity<Page<ProblemDto>> getAllProblems(@PageableDefault Pageable pageable) {
        Page<ProblemDto> problems = problemService.getAllProblems(pageable);
        return ResponseEntity.ok(problems);
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProblemDto> getProblemById(@PathVariable Long id) {
        try {
            ProblemDto problemDto = problemService.getProblemById(id);
            return ResponseEntity.ok(problemDto);
        } catch (NoSuchElementException e) {
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping
    public ResponseEntity<ProblemDto> createProblem(@Valid @RequestBody ProblemDto problemDto) {
        ProblemDto createdProblem = problemService.createProblem(problemDto);
        return new ResponseEntity<>(createdProblem, HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<ProblemDto> updateProblem(@PathVariable Long id, @Valid @RequestBody ProblemDto problemDto) {
        try {
            ProblemDto updatedProblem = problemService.updateProblem(id, problemDto);
            return ResponseEntity.ok(updatedProblem);
        } catch (NoSuchElementException e) {
            return ResponseEntity.notFound().build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProblem(@PathVariable Long id) {
        problemService.deleteProblem(id);
        return ResponseEntity.noContent().build();
    }

    @GetMapping("/category")
    public ResponseEntity<Page<ProblemDto>> getProblemsByCategory(
            @RequestParam("category") ProblemCategory category,
            @PageableDefault Pageable pageable) {
        Page<ProblemDto> problems = problemService.getProblemsByCategory(category, pageable);
        if (problems.isEmpty()) {
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(problems);
    }
}

์ด ๋ฆฌ๋ทฐ๋Š” ์ฝ”๋“œ์˜ ํ˜„์žฌ ์ƒํƒœ์— ๋Œ€ํ•œ ๋ถ„์„์ด๋ฉฐ, ์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ์˜ ๋™์ž‘์€ ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถฉ๋ถ„ํ•œ ํ…Œ์ŠคํŠธ์™€ ๊ฒ€์ฆ์„ ๊ฑฐ์ณ ์•ˆ์ •์ ์ธ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ์€ ๋งค์šฐ ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์กฐ๊ธˆ ๋” ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๋งค์šฐ ์ข‹์Œ: ์ฝ”๋“œ๋Š” ์งง๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šฐ๋ฉฐ, ๋ช…ํ™•ํ•œ ๋ชฉ์ (ํŠน์ • ๋ฌธ์ œ(Problem)๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์„ ๋•Œ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์˜ˆ์™ธ)์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋„ค์ด๋ฐ: ํด๋ž˜์Šค ์ด๋ฆ„ ProblemNotFoundException์€ ์˜ˆ์™ธ์˜ ์˜๋ฏธ๋ฅผ ์ž˜ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • ๋ฏธ๋ฏธํ•จ: ํ˜„์žฌ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ๋ช…๋ฐฑํ•œ ๋ฒ„๊ทธ๋Š” ์—†์Šต๋‹ˆ๋‹ค.
  • ๋ฌธ์ œ์ : ํ˜„์žฌ ์˜ˆ์™ธ ํด๋ž˜์Šค๋Š” ๋ฉ”์‹œ์ง€๋งŒ ์ „๋‹ฌ๋ฐ›์•„ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ํ•„์š”ํ•œ ์ถ”๊ฐ€์ ์ธ ์ •๋ณด(์˜ˆ: ๋ฌธ์ œ ID, ๊ด€๋ จ ์‚ฌ์šฉ์ž ์ •๋ณด ๋“ฑ)๋ฅผ ๋‹ด์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋””๋ฒ„๊น… ๋ฐ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Checked Exception vs. Unchecked Exception: RuntimeException์„ ์ƒ์†๋ฐ›์œผ๋ฏ€๋กœ unchecked exception์ž…๋‹ˆ๋‹ค. API ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ˜๋“œ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ•์ œํ•˜์ง€ ์•Š์•„๋„ ๋˜๋ฏ€๋กœ ์‚ฌ์šฉ ํŽธ์˜์„ฑ์€ ๋†’์ง€๋งŒ, ์ƒํ™ฉ์— ๋”ฐ๋ผ checked exception (IOException ๋“ฑ๊ณผ ๊ฐ™์ด Exception์„ ์ƒ์†)์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฐ˜๋“œ์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ค‘์š”ํ•œ ๊ฒฝ์šฐ๋ผ๋ฉด checked exception์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ์—†์Œ: ํ•ด๋‹น ์ฝ”๋“œ๋Š” ์˜ˆ์™ธ ํด๋ž˜์Šค ์ •์˜์ด๋ฏ€๋กœ, ์ž์ฒด์ ์ธ ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฐœ์ƒ ๋นˆ๋„๊ฐ€ ๋†’๋‹ค๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ ์ž์ฒด๋ฅผ ์ค„์ด๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•ด์•ผ๊ฒ ์ง€๋งŒ, ์ด๋Š” ์ด ์˜ˆ์™ธ ํด๋ž˜์Šค์™€๋Š” ์ง์ ‘์ ์ธ ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์—†์Œ: ์ด ์˜ˆ์™ธ ํด๋ž˜์Šค ์ž์ฒด๋Š” ๋ณด์•ˆ๊ณผ ๊ด€๋ จ๋œ ์ด์Šˆ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด(์˜ˆ: ๋น„๋ฐ€๋ฒˆํ˜ธ, ๊ฐœ์ธ ์ •๋ณด ๋“ฑ)๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • ์˜ˆ์™ธ ์ปจํ…์ŠคํŠธ ์ถ”๊ฐ€: ๋ฌธ์ œ์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์ •๋ณด(์˜ˆ: ๋ฌธ์ œ ID)๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ƒ์„ฑ์ž๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    package site.haruhana.www.exception;
    
    public class ProblemNotFoundException extends RuntimeException {
        private Long problemId;
    
        public ProblemNotFoundException(Long problemId) {
            this(problemId, "Problem not found with id: " + problemId); // ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€
        }
    
        public ProblemNotFoundException(Long problemId, String message) {
            super(message);
            this.problemId = problemId;
        }
    
        public Long getProblemId() {
            return problemId;
        }
    }

    ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋ฌธ์ œ ID๋ฅผ ์‰ฝ๊ฒŒ ์–ป์„ ์ˆ˜ ์žˆ์–ด ๋””๋ฒ„๊น…์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์— ๋ฌธ์ œ ID๋ฅผ ๊ธฐ๋กํ•˜๊ฑฐ๋‚˜, ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋กœ๊น…: ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค. ๋ฌธ์ œ ํ•ด๊ฒฐ์— ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ์ , ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€, ๋ฌธ์ œ ID ๋“ฑ์„ ๊ธฐ๋กํ•˜๋ฉด ๋ฌธ์ œ์˜ ์›์ธ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • Checked Exception ๊ณ ๋ ค: ProblemNotFoundException์ด ๋ฐœ์ƒํ•˜๋ฉด ๋ฐ˜๋“œ์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ค‘์š”ํ•œ ์˜ˆ์™ธ์ธ์ง€ ํŒ๋‹จํ•˜๊ณ , ๊ทธ๋ ‡๋‹ค๋ฉด Exception์„ ์ƒ์†๋ฐ›์•„ checked exception์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค.

  • ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ๊ตฌ์ฒดํ™”: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ์ข€ ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, "Problem not found with id: {problemId}"์™€ ๊ฐ™์ด ๋ฌธ์ œ ID๋ฅผ ํฌํ•จํ•˜๋ฉด ๋””๋ฒ„๊น…์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค๋กœ์„œ ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐ ๋””๋ฒ„๊น…์„ ๋•๊ธฐ ์œ„ํ•ด ๋ฌธ์ œ ID๋ฅผ ํฌํ•จํ•˜๊ณ  ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ์ฒดํ™”ํ•˜๋Š” ๋“ฑ์˜ ๊ฐœ์„ ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์˜ˆ์™ธ์˜ ์ค‘์š”๋„๋ฅผ ๊ณ ๋ คํ•˜์—ฌ checked exception์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/repository/ProblemRepository.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ์งง๊ณ  ๊ฐ„๊ฒฐํ•˜์—ฌ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
  • ๋ช…๋ช… ๊ทœ์น™ ์ค€์ˆ˜: ProblemRepository๋ผ๋Š” ์ด๋ฆ„์€ ์ธํ„ฐํŽ˜์ด์Šค์˜ ์—ญํ• ๊ณผ ์ฑ…์ž„์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. findByProblemCategory ๋ฉ”์†Œ๋“œ๋ช… ์—ญ์‹œ ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.
  • import ๋ฌธ: ํ•„์š”ํ•œ ํด๋ž˜์Šค๋งŒ importํ•˜์—ฌ ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: JPA Repository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์•„ Spring Data JPA์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์€ ํ‘œ์ค€์ ์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: findByProblemCategory ๋ฉ”์†Œ๋“œ์—์„œ category ์ธ์ž๊ฐ€ null์ผ ๊ฒฝ์šฐ, JPA Provider์— ๋”ฐ๋ผ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, @NonNull ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…์‹œ์ ์œผ๋กœ null ๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Pageable ์ฒ˜๋ฆฌ ์ฃผ์˜: Pageable ๊ฐ์ฒด๋ฅผ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋„˜์–ด์˜ค๋Š” Pageable ๊ฐ์ฒด๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•˜์—ฌ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ: ๋งŒ์•ฝ ProblemCategory ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ, ์บ์‹ฑ ์ „๋žต์„ ์‹ ์ค‘ํ•˜๊ฒŒ ๊ณ ๋ คํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” ํŒ๋‹จํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.)

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ์ฟผ๋ฆฌ ์ตœ์ ํ™”: findByProblemCategory ๋ฉ”์†Œ๋“œ๋Š” JPA๊ฐ€ ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, JPQL์ด๋‚˜ native query๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ๋ณต์žกํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์„ ๊ฒฝ์šฐ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…์‹œ์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค.
  • ์ธ๋ฑ์Šค ํ™œ์šฉ: ProblemCategory ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ธ๋ฑ์Šค๊ฐ€ ์—†๋‹ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • SQL Injection ์ทจ์•ฝ์ : ํ˜„์žฌ ์ฝ”๋“œ๋Š” Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ SQL Injection ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์–ด๋Š” ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, JPQL์ด๋‚˜ native query๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ฒ ์ €ํžˆ ํ•˜์—ฌ SQL Injection ์ทจ์•ฝ์ ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ฌธ์ œ ์ ‘๊ทผ ๊ถŒํ•œ์— ๋Œ€ํ•œ ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํŠน์ • ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋ฉด, ๋ฉ”์†Œ๋“œ ์‹คํ–‰ ์ „์— ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • Null ์ฒดํฌ: findByProblemCategory ๋ฉ”์†Œ๋“œ์— category ์ธ์ž๊ฐ€ null์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
    
    // ๊ฐœ์„ ๋œ ์ฝ”๋“œ (Optional ์‚ฌ์šฉ)
    Optional<Page<Problem>> findByProblemCategory(Optional<ProblemCategory> category, Pageable pageable);
    
    // ๋งŒ์•ฝ category๊ฐ€ null์ด๋ฉด ๋นˆ Page๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ตฌํ˜„
    default Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable) {
        if (category == null) {
            return Page.empty(); // ๋นˆ ํŽ˜์ด์ง€ ๋ฐ˜ํ™˜
        }
        return findByProblemCategory(category, pageable);
    }
  • Pageable ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋„˜์–ด์˜ค๋Š” Pageable ๊ฐ์ฒด์˜ page size๊ฐ€ ์ ์ ˆํ•œ ๋ฒ”์œ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ํฐ page size๋Š” ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @Override
    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable) {
        int pageSize = pageable.getPageSize();
        if (pageSize > MAX_PAGE_SIZE) {
            pageable = PageRequest.of(pageable.getPageNumber(), MAX_PAGE_SIZE, pageable.getSort()); // ์ตœ๋Œ€ ํŽ˜์ด์ง€ ์‚ฌ์ด์ฆˆ๋กœ ์กฐ์ •
        }
        return super.findByProblemCategory(category, pageable);
    }
  • ์ฟผ๋ฆฌ ํŠœ๋‹: ํ•„์š”ํ•˜๋‹ค๋ฉด @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

    @Query("SELECT p FROM Problem p JOIN FETCH p.problemCategory WHERE p.problemCategory = :category")
    Page<Problem> findByProblemCategoryWithFetchJoin(@Param("category") ProblemCategory category, Pageable pageable);
  • (์„ ํƒ ์‚ฌํ•ญ) Auditing ์ถ”๊ฐ€: Problem ์—”ํ‹ฐํ‹ฐ์— ์ƒ์„ฑ์ผ์‹œ, ์ˆ˜์ •์ผ์‹œ, ์ƒ์„ฑ์ž, ์ˆ˜์ •์ž๋ฅผ ๊ธฐ๋กํ•˜๋Š” Auditing ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ถ”์  ๋ฐ ๊ด€๋ฆฌ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. Spring Data JPA Auditing์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

ProblemRepository.java ์ฝ”๋“œ๋Š” ์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ๊ณผ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ์ œ์•ˆ๋“ค์„ ๊ณ ๋ คํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋ฉด ๋”์šฑ ์•ˆ์ •์ ์ด๊ณ  ํšจ์œจ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, NullPointerException, Pageable ์œ ํšจ์„ฑ, ์ฟผ๋ฆฌ ์ตœ์ ํ™”์— ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์ด์‹ญ์‹œ์˜ค.

Copy link

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ๋ฆฌ๋ทฐ

GlobalExceptionHandler.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ:

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. @ControllerAdvice์™€ @ExceptionHandler ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ์ค‘์•™ ์ง‘์ค‘์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ์ž˜ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  • ๋„ค์ด๋ฐ: ํด๋ž˜์Šค ์ด๋ฆ„(GlobalExceptionHandler)๊ณผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„(handleProblemNotFoundException, handleGenericException)์€ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋ฉฐ, ํ‘œ์ค€์ ์ธ Java ๋„ค์ด๋ฐ ๊ทœ์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.
  • ๊ตฌ์กฐ: ์˜ˆ์™ธ ์ข…๋ฅ˜๋ณ„๋กœ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์šฉ์ดํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • handleGenericException์˜ ๊ณผ๋„ํ•œ ์ผ๋ฐ˜ํ™”: Exception.class๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์žก์•„๋ƒ…๋‹ˆ๋‹ค. ์ด๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊นŒ์ง€ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋””๋ฒ„๊น…์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์กด์žฌํ•˜๋”๋ผ๋„ ์ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋จผ์ € ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ์˜ˆ์™ธ๋ฅผ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‘๋‹ต ๋ฉ”์‹œ์ง€์˜ ๋…ธ์ถœ: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ƒ ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ๋ฉ”์‹œ์ง€์— ํฌํ•จ๋  ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž์—๊ฒŒ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ์„ฑ๋Šฅ ๊ด€๋ จ ์ด์Šˆ๋Š” ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•˜๋Š” ์ž‘์—…์ด ์•„๋‹ˆ๋ฏ€๋กœ, ํ˜„์žฌ ์ฝ”๋“œ์—์„œ ์„ฑ๋Šฅ ๊ฐœ์„ ์„ ํฌ๊ฒŒ ๊ธฐ๋Œ€ํ•˜๊ธฐ๋Š” ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๊ฐ€ ์ง€๋‚˜์น˜๊ฒŒ ๊ธธ๊ฑฐ๋‚˜ ๋ณต์žกํ•˜๋‹ค๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ฌธ์ž์—ด ์—ฐ์‚ฐ์„ ์ค„์ด๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ๋…ธ์ถœ: ์œ„์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ ์ทจ์•ฝ์ ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ž ์žฌ์ ์ธ ์ •๋ณด ์œ ์ถœ: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ์‹œ์Šคํ…œ ๋‚ด๋ถ€ ์ •๋ณด (DB ์—ฐ๊ฒฐ ์ •๋ณด, ํŒŒ์ผ ๊ฒฝ๋กœ ๋“ฑ)๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด ๊ณต๊ฒฉ์ž์—๊ฒŒ ์•…์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • handleGenericException ์ˆ˜์ •:
    • ๊ฐ€๋Šฅํ•˜๋ฉด ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, IllegalArgumentException, NullPointerException ๋“ฑ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • handleGenericException์€ ์ •๋ง ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๋“ฑ์˜ ์ตœ์†Œํ•œ์˜ ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ์‘๋‹ต ๋ฉ”์‹œ์ง€ ์ˆ˜์ •:
    • ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ง์ ‘ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ๋Œ€์‹ , ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋˜๋Š” ์˜ค๋ฅ˜ ์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ProblemNotFoundException์˜ ๊ฒฝ์šฐ, "์š”์ฒญํ•˜์‹  ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋กœ๊น… ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ์„œ๋ฒ„ ์ธก์— ๊ธฐ๋กํ•˜๊ณ , ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ๋ฌธ์ œ ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์‘๋‹ต ํฌ๋งท ํ†ต์ผ:
    • ๋ชจ๋“  ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ผ๊ด€๋œ ํ˜•์‹์˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, JSON ํ˜•์‹์œผ๋กœ ์˜ค๋ฅ˜ ์ฝ”๋“œ, ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€, ํƒ€์ž„์Šคํƒฌํ”„ ๋“ฑ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ๋”์šฑ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
  • ๋กœ๊น… ์ถ”๊ฐ€:
    • ๊ฐ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ์ ์ ˆํ•˜๊ฒŒ ์„ค์ •ํ•˜์—ฌ (์˜ˆ: ERROR ๋ ˆ๋ฒจ) ์ค‘์š”ํ•œ ์˜ค๋ฅ˜๋งŒ ๊ธฐ๋กํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋กœ๊น… ์‹œ ์˜ˆ์™ธ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ํฌํ•จํ•˜์—ฌ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ๋„์›€์ด ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ •๋œ ์ฝ”๋“œ ์˜ˆ์‹œ (์ผ๋ถ€):

package site.haruhana.www.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import site.haruhana.www.exception.ProblemNotFoundException;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(ProblemNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleProblemNotFoundException(ProblemNotFoundException ex) {
        logger.warn("Problem not found: {}", ex.getMessage());
        ErrorResponse response = new ErrorResponse("RESOURCE_NOT_FOUND", "์š”์ฒญํ•˜์‹  ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
        logger.error("Unexpected error: {}", ex.getMessage(), ex); // ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๋กœ๊น…
        ErrorResponse response = new ErrorResponse("INTERNAL_SERVER_ERROR", "์„œ๋ฒ„์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// ErrorResponse ํด๋ž˜์Šค (์˜ˆ์‹œ)
class ErrorResponse {
    private String code;
    private String message;

    public ErrorResponse(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ข‹์€ ์ฝ”๋“œ์ด์ง€๋งŒ, ๋ณด์•ˆ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ์œ„ํ•ด ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„  ์‚ฌํ•ญ์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ๋…ธ์ถœ ๋ฌธ์ œ๋Š” ๋ฐ˜๋“œ์‹œ ํ•ด๊ฒฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์œ„์— ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ๋”์šฑ ์•ˆ์ •์ ์ด๊ณ  ์•ˆ์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

ProblemDto.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ProblemDto.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ฝ”๋“œ ํ’ˆ์งˆ, ์ž ์žฌ์  ๋ฌธ์ œ์ , ์„ฑ๋Šฅ, ๋ณด์•ˆ, ๊ทธ๋ฆฌ๊ณ  ๊ฐœ์„  ์ œ์•ˆ์— ๋Œ€ํ•œ ์ƒ์„ธ ๋ถ„์„์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์ธ ์ ์€ ์ข‹์Šต๋‹ˆ๋‹ค. @Getter, @Builder, @NoArgsConstructor, @AllArgsConstructor ์–ด๋…ธํ…Œ์ด์…˜์˜ ์‚ฌ์šฉ์€ ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ ์œ ์ง€: ํด๋ž˜์Šค๋ช… ProblemDto๋Š” Data Transfer Object ํŒจํ„ด์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, ์—ญํ• ์— ๋งž๋Š” ์ด๋ฆ„์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ช…ํ™•์„ฑ: ํ•„๋“œ๋ช…(id, title, description, answer, level, problemCategory)๋“ค์€ ๊ฐ ์†์„ฑ์˜ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • Problem ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ์ž ๋ฌธ์ œ: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ›์•„ ProblemDto๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ƒ์„ฑ์ž์—์„œ this.problemCategory = getProblemCategory(); ๋ถ€๋ถ„์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. getProblemCategory()๋Š” ํ˜„์žฌ DTO ๋‚ด์— ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์•„๋งˆ๋„ problem.getProblemCategory()๋ฅผ ์˜๋„ํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: Problem ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ๋“ค์ด null์ผ ๊ฒฝ์šฐ, ProblemDto ์ƒ์„ฑ ์‹œ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ: DTO๋Š” ์ฃผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก์— ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ์žฆ์€ ๊ฐ์ฒด ์ƒ์„ฑ์ด ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด ๊ฐ์ฒด ํ’€๋ง(Object Pooling) ๋“ฑ์˜ ๊ธฐ๋ฒ•์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ฝ”๋“œ๋Š” ํฌ๋ฆฌํ‹ฐ์ปฌํ•œ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ „์†ก: API ์‘๋‹ต์ด๋‚˜ ์š”์ฒญ์— ํ•„์š”ํ•˜์ง€ ์•Š์€ ํ•„๋“œ๋Š” DTO์— ํฌํ•จ์‹œํ‚ค์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ „์†กํ•˜๋„๋ก DTO๋ฅผ ์„ค๊ณ„ํ•ด์•ผ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: answer ํ•„๋“œ๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. API๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ ์ง์ ‘ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ๋ณด์•ˆ์„ ๊ณ ๋ คํ•œ ์•”ํ˜ธํ™” ๋ฐ ๊ถŒํ•œ ๊ด€๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋กœ๊ทธ์— answer๊ฐ€ ๊ธฐ๋ก๋˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection ๋ฐ XSS ๊ณต๊ฒฉ ๋ฐฉ์ง€: title, description ๋“ฑ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›๋Š” ํ•„๋“œ์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. SQL Injection์ด๋‚˜ XSS ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ž…๋ ฅ ๊ฐ’์„ ์ ์ ˆํžˆ ํ•„ํ„ฐ๋งํ•˜๊ฑฐ๋‚˜ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด ๋ถ€๋ถ„์€ DTO ์ž์ฒด์˜ ๋ฌธ์ œ๋Š” ์•„๋‹ˆ์ง€๋งŒ, DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ ˆ์ด์–ด์—์„œ ๋ฐ˜๋“œ์‹œ ๊ณ ๋ คํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • ์ƒ์„ฑ์ž ์ˆ˜์ •: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ›๋Š” ์ƒ์„ฑ์ž๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = problem.getDescription();
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory();
    }
  • Null ์ฒ˜๋ฆฌ: Problem ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ๋•Œ null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ NullPointerException์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle() != null ? problem.getTitle() : ""; // ์˜ˆ์‹œ: ๋นˆ ๋ฌธ์ž์—ด๋กœ ์ฒ˜๋ฆฌ
        this.description = problem.getDescription() != null ? problem.getDescription() : "";
        this.answer = problem.getAnswer() != null ? problem.getAnswer() : "";
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory();
    }

    ๋˜๋Š”, Lombok์˜ @NonNull ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํŒŒ์ผ ์‹œ์ ์— null ์ฒดํฌ๋ฅผ ๊ฐ•์ œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค (๋‹จ, Problem ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ์— ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค).

  • Validation ์ถ”๊ฐ€: javax.validation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DTO์˜ ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, @NotBlank ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ง€์ •ํ•˜๊ฑฐ๋‚˜, @Size ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ž์—ด ๊ธธ์ด ์ œํ•œ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.Size;
    
    @Getter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ProblemDto {
        private Long id;
    
        @NotBlank(message = "์ œ๋ชฉ์€ ํ•„์ˆ˜ ์ž…๋ ฅ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.")
        @Size(min = 1, max = 255, message = "์ œ๋ชฉ์€ 1์ž ์ด์ƒ 255์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
        private String title;
    
        private String description;
        private String answer;
        private int level;
        private ProblemCategory problemCategory;
    
        // ... ์ƒ์„ฑ์ž ...
    }

    ์ด๋ ‡๊ฒŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ปจํŠธ๋กค๋Ÿฌ์—์„œ DTO๋ฅผ ๋ฐ›๊ธฐ ์ „์— ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋น„์Šค ๋ ˆ์ด์–ด๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ํ•จ๊ป˜ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฏผ๊ฐ ์ •๋ณด ์ฒ˜๋ฆฌ: answer ํ•„๋“œ๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ์ง์ ‘ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋ฐฑ์—”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ •๋‹ต ํ™•์ธ API๋ฅผ ๋ณ„๋„๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜, answer ํ•„๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์—ฌ ์ €์žฅํ•˜๊ณ  ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ๋ณตํ˜ธํ™”ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • DTO ๋ณ€ํ™˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ: Problem ์—”ํ‹ฐํ‹ฐ์™€ ProblemDto ๊ฐ„์˜ ๋ณ€ํ™˜ ๋กœ์ง์ด ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค๋ฉด, ๋ณ„๋„์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ModelMapper ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ฐ์ฒด ๊ฐ„์˜ ๋ณ€ํ™˜์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๋ฌธ์ œ์ ๋“ค์„ ์ˆ˜์ •ํ•˜๊ณ  ๊ฐœ์„  ์ œ์•ˆ์„ ์ ์šฉํ•˜๋ฉด ๋”์šฑ ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ƒ์„ฑ์ž ์˜ค๋ฅ˜ ์ˆ˜์ •๊ณผ ๋ฏผ๊ฐ ์ •๋ณด ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์— ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ฐ null ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ๋”์šฑ robustํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฆฌ๋ทฐ๊ฐ€ ์ฝ”๋“œ ๊ฐœ์„ ์— ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ํ˜น์‹œ ๋” ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/entity/Problem.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/entity/Problem.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ์ œ๊ณตํ•ด์ฃผ์‹  src/main/java/site/haruhana/www/entity/Problem.java ํŒŒ์ผ์˜ ์ฝ”๋“œ ์กฐ๊ฐ์„ ๋ฆฌ๋ทฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ํŒŒ์ผ์ด ์•„๋‹Œ ์ผ๋ถ€ ์ฝ”๋“œ ์กฐ๊ฐ๋งŒ ์ œ๊ณต๋˜์—ˆ์œผ๋ฏ€๋กœ, ๋ฆฌ๋ทฐ๋Š” ์ œ๊ณต๋œ ๋ถ€๋ถ„์— ํ•œ์ •๋จ์„ ์•Œ๋ ค๋“œ๋ฆฝ๋‹ˆ๋‹ค.

๋ฆฌ๋ทฐ ๋Œ€์ƒ ์ฝ”๋“œ:

+import lombok.Builder;
+import site.haruhana.www.dto.ProblemDto;
+@Builder
+     * @param updatedProblemDto ๊ฐฑ์‹ ํ•  ๋ฌธ์ œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” DTO
+    public void update(ProblemDto updatedProblemDto) {
+        this.title = updatedProblemDto.getTitle();
+        this.description = updatedProblemDto.getDescription();
+        this.level = updatedProblemDto.getLevel();
+        this.problemCategory = updatedProblemDto.getProblemCategory();
+    }

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋ณ€์ˆ˜๋ช…๋„ ์ง๊ด€์ ์ด๋ฉฐ, update ๋ฉ”์†Œ๋“œ์˜ ๊ธฐ๋Šฅ๋„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.
  • Lombok Builder ์–ด๋…ธํ…Œ์ด์…˜: @Builder ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋นŒ๋” ํŒจํ„ด์„ ์ ์šฉํ•œ ๊ฒƒ์€ ๊ฐ์ฒด ์ƒ์„ฑ์˜ ์œ ์—ฐ์„ฑ์„ ๋†’์ด๋Š” ์ข‹์€ ์„ ํƒ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋นŒ๋” ํŒจํ„ด์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š”์ง€๋Š” ์ „์ฒด ์ฝ”๋“œ๋ฅผ ๋ด์•ผ ๋” ์ •ํ™•ํ•˜๊ฒŒ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„: update ๋ฉ”์†Œ๋“œ์˜ @param ์ฃผ์„์€ ์„ค๋ช…์„ ์ž˜ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ฝ”๋“œ ์ปจ๋ฒค์…˜์— ๋งž๊ฒŒ ์ฃผ์„์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (์˜ˆ: /** @param updatedProblemDto ๊ฐฑ์‹ ํ•  ๋ฌธ์ œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” DTO */)

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: updatedProblemDto๊ฐ€ null์ผ ๊ฒฝ์šฐ, NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•„๋“œ ๊ฐ’ ๊ฒ€์ฆ ๋ถ€์žฌ: updatedProblemDto์˜ ํ•„๋“œ ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, title์ด ๋นˆ ๋ฌธ์ž์—ด์ด๊ฑฐ๋‚˜ level์ด ์œ ํšจํ•œ ๋ฒ”์œ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ์„ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • DTO ํ•„๋“œ ๋ณ€๊ฒฝ ์‹œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ: ProblemDto์˜ ํ•„๋“œ ์ด๋ฆ„์ด ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ, update ๋ฉ”์†Œ๋“œ๋„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌํŒฉํ† ๋ง ์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ๋ถˆ๊ฐ€๋Šฅ: update ๋ฉ”์†Œ๋“œ๋Š” ProblemDto์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ: ๋งŒ์•ฝ ProblemDto์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์—์„œ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DTO ๋‚ด๋ถ€ ๊ตฌํ˜„์„ ํ™•์ธํ•˜๊ณ , ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ์„ ์ตœ์†Œํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Reflection ์‚ฌ์šฉ ์ตœ์†Œํ™”: Lombok์˜ Builder ํŒจํ„ด ๊ตฌํ˜„ ๋ฐฉ์‹์— ๋”ฐ๋ผ Reflection์ด ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Reflection์€ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๋‹ค๋ฅธ ๋Œ€์•ˆ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection ๊ฐ€๋Šฅ์„ฑ: ์ œ๊ณต๋œ ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” SQL Injection ๊ฐ€๋Šฅ์„ฑ์„ ํŒ๋‹จํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, title์ด๋‚˜ description๊ณผ ๊ฐ™์€ ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•  ๋•Œ, ์ ์ ˆํ•œ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฉด SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PreparedStatement๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ORM ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ SQL Injection์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ ์‚ฝ์ž… (XSS): title์ด๋‚˜ description ํ•„๋“œ์— ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‚ฝ์ž…๋  ๊ฒฝ์šฐ, XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. HTML ์—”ํ‹ฐํ‹ฐ ์ธ์ฝ”๋”ฉ ๋“ฑ์˜ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ XSS ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๋ฌธ์ œ: update ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ถŒํ•œ ๊ฒ€์‚ฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ธ๊ฐ€๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋„๋ก ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. Null ์ฒดํฌ: update ๋ฉ”์†Œ๋“œ ์‹œ์ž‘ ๋ถ€๋ถ„์— updatedProblemDto๊ฐ€ null์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    public void update(ProblemDto updatedProblemDto) {
        if (updatedProblemDto == null) {
            throw new IllegalArgumentException("updatedProblemDto cannot be null");
        }
  2. ์œ ํšจ์„ฑ ๊ฒ€์ฆ: ๊ฐ ํ•„๋“œ ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Bean Validation (JSR 303) ๋˜๋Š” custom validator๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.Size;
    
    // ProblemDto.java
    public class ProblemDto {
        @NotBlank(message = "Title cannot be blank")
        @Size(min = 1, max = 255, message = "Title must be between 1 and 255 characters")
        private String title;
    
        // other fields...
    }
    
    // Problem.java
    import javax.validation.Valid;
    
    public void update(@Valid ProblemDto updatedProblemDto) {
        // ...
    }
  3. ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€: ํ•„์š”์— ๋”ฐ๋ผ ํŠน์ • ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฉ”์†Œ๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.

    public void updateTitle(String title) {
        this.title = title;
    }
    
    public void updateDescription(String description) {
        this.description = description;
    }
  4. ๋ณด์•ˆ ๊ฐ•ํ™”: SQL Injection ๋ฐ XSS ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ ์ ˆํ•œ ์กฐ์น˜๋ฅผ ์ทจํ•ฉ๋‹ˆ๋‹ค.

  5. ๋ถˆ๋ณ€ ๊ฐ์ฒด ํ™œ์šฉ (Optional): Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค๊ณ , update ๋ฉ”์†Œ๋“œ์—์„œ ์ƒˆ๋กœ์šด Problem ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ์ฒด์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ด๊ณ , ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    public Problem update(ProblemDto updatedProblemDto) {
        return Problem.builder()
                .title(updatedProblemDto.getTitle())
                .description(updatedProblemDto.getDescription())
                .level(updatedProblemDto.getLevel())
                .problemCategory(updatedProblemDto.getProblemCategory())
                .build();
    }
  6. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: update ๋ฉ”์†Œ๋“œ์˜ ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜:

  • ์œ„ ๋ฆฌ๋ทฐ๋Š” ์ œ๊ณต๋œ ์ฝ”๋“œ ์กฐ๊ฐ์— ๊ธฐ๋ฐ˜ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ, ์ „์ฒด ์ฝ”๋“œ์˜ ๋งฅ๋ฝ์— ๋”ฐ๋ผ ์ˆ˜์ •์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์–ธ๊ธ‰๋œ ๋ณด์•ˆ ์ด์Šˆ๋Š” ์ผ๋ฐ˜์ ์ธ ๋‚ด์šฉ์ด๋ฉฐ, ์‹ค์ œ ๋ณด์•ˆ ์ทจ์•ฝ์ ์€ ์ฝ”๋“œ ์ „์ฒด๋ฅผ ๋ถ„์„ํ•ด์•ผ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ „์ฒด ์ฝ”๋“œ๋ฅผ ์ œ๊ณตํ•ด์ฃผ์‹œ๋ฉด ๋”์šฑ ์ƒ์„ธํ•˜๊ณ  ์ •ํ™•ํ•œ ๋ฆฌ๋ทฐ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜๊ณ ํ•˜์„ธ์š”!

Copy link

src/test/java/site/haruhana/www/Service/ProblemServiceTest.java ๋ฆฌ๋ทฐ

src/test/java/site/haruhana/www/Service/ProblemServiceTest.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ ์ฃผ์–ด์ง„ ProblemServiceTest.java ์ฝ”๋“œ์— ๋Œ€ํ•œ ์ƒ์„ธ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๋ฉฐ, ๊ฐ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์˜ ๋ชฉ์ ์„ ๋ถ„๋ช…ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ด๋Š” DisplayName ์• ๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค. given-when-then ํŒจํ„ด์„ ์ž˜ ์ ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๊ตฌ์กฐํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: Mockito๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ์„ ๊ฒฉ๋ฆฌํ•˜๊ณ , ์˜ˆ์ƒ๋˜๋Š” ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์ดˆ์ ์„ ๋งž์ถ”๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. given, when, then ์ฃผ์„์€ ํ…Œ์ŠคํŠธ ํ๋ฆ„์„ ์‰ฝ๊ฒŒ ๋”ฐ๋ผ๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
  • ์ƒ์„ฑ์ž ์ฃผ์ž…: ProblemService์— ๋Œ€ํ•œ ์˜์กด์„ฑ ์ฃผ์ž…์€ @InjectMocks ๋ฐ @Mock์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž˜ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • createTestProblem() ๋ฉ”์„œ๋“œ ํ™œ์šฉ: ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ Problem ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์˜€์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • ProblemDto์˜ ๋™๋“ฑ์„ฑ ๋น„๊ต: ProblemDto ๊ฐ์ฒด์˜ ๋‚ด์šฉ์„ ๊ฒ€์ฆํ•˜๋Š” ๋ถ€๋ถ„์ด ์—†์Šต๋‹ˆ๋‹ค. DTO์— equals()์™€ hashCode() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ฐ์ฒด ์ฃผ์†Œ๊ฐ’์œผ๋กœ ๋น„๊ตํ•˜๊ฒŒ ๋˜์–ด ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • updateProblem() ํ…Œ์ŠคํŠธ์˜ ๊ฒ€์ฆ ๋ถ€์กฑ: updateProblem() ํ…Œ์ŠคํŠธ์—์„œ, ์—…๋ฐ์ดํŠธ๋œ ๋‚ด์šฉ์ด ์‹ค์ œ๋กœ ๋ฐ˜์˜๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. updateDto์— ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , result๋กœ ๋ฐ˜ํ™˜๋œ ProblemDto์˜ ๋‚ด์šฉ์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ Mockito import: import static org.mockito.Mockito.*;๋Š” ๋ชจ๋“  Mockito static ๋ฉ”์„œ๋“œ๋ฅผ ์ž„ํฌํŠธํ•˜๋ฏ€๋กœ ๋ถˆํ•„์š”ํ•œ ์ž„ํฌํŠธ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ์ž„ํฌํŠธํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค (์˜ˆ: import static org.mockito.Mockito.when;).

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋น„์šฉ: createTestProblem()์€ ๊ฐ„๋‹จํ•œ ๊ฐ์ฒด ์ƒ์„ฑ ๋ฉ”์„œ๋“œ์ด์ง€๋งŒ, ๋งŽ์€ ์ˆ˜์˜ ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค๋ฉด, Problem ๊ฐ์ฒด ์ƒ์„ฑ์ด ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด, static final ๋ณ€์ˆ˜๋กœ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์•ˆ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ํ•˜์ง€๋งŒ ํ˜„์žฌ๋Š” ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ์„ ์ค„ ๊ฒƒ์œผ๋กœ ๋ณด์ด์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ์ง์ ‘์ ์ธ ๋ณด์•ˆ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์ฝ”๋“œ(ProblemService)๊ฐ€ SQL Injection, XSS ๋“ฑ์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์ด๋ฅผ ๊ฐ„๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณด์•ˆ ๊ด€๋ จ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค (์˜ˆ: ์•…์˜์ ์ธ ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ–ˆ์„ ๋•Œ์˜ ๋™์ž‘ ๊ฒ€์ฆ)๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. ProblemDto์˜ equals() ๋ฐ hashCode() ๊ตฌํ˜„:

    • ProblemDto ํด๋ž˜์Šค์— equals()์™€ hashCode() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ๊ฐ์ฒด์˜ ๋‚ด์šฉ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋น„๊ตํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. IDE์˜ ์ž๋™ ์ƒ์„ฑ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    // ProblemDto.java
    @Data // Lombok ์–ด๋…ธํ…Œ์ด์…˜ (equals, hashCode ์ž๋™ ์ƒ์„ฑ)
    public class ProblemDto {
        // ... ๊ธฐ์กด ํ•„๋“œ ...
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ProblemDto that = (ProblemDto) o;
            return level == that.level &&
                   Objects.equals(title, that.title) &&
                   Objects.equals(description, that.description) &&
                   problemCategory == that.problemCategory &&
                   Objects.equals(answer, that.answer);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(title, description, level, problemCategory, answer);
        }
    }
  2. updateProblem() ํ…Œ์ŠคํŠธ์˜ ๋‚ด์šฉ ๊ฒ€์ฆ ๊ฐ•ํ™”:

    • updateDto์— ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , problemService.updateProblem() ํ˜ธ์ถœ ํ›„ ๋ฐ˜ํ™˜๋œ result ๊ฐ์ฒด์˜ ํ•„๋“œ ๊ฐ’๋“ค์ด updateDto์˜ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
    @Test
    @DisplayName("๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์ œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค")
    void updateProblem() {
        // given: ์ˆ˜์ •ํ•  ๋ฌธ์ œ ID์™€ ์—…๋ฐ์ดํŠธํ•  ๋‚ด์šฉ์ด ์ฃผ์–ด์กŒ์„ ๋•Œ
        Long problemId = 1L;
        Problem existingProblem = createTestProblem();
        ProblemDto updateDto = new ProblemDto();
        updateDto.setTitle("์ˆ˜์ •๋œ ์ œ๋ชฉ");
        updateDto.setDescription("์ˆ˜์ •๋œ ์„ค๋ช…");
        updateDto.setLevel(5);
        updateDto.setProblemCategory(ProblemCategory.FRONTEND);
        updateDto.setAnswer("์ˆ˜์ •๋œ ๋‹ต์•ˆ");
    
        given(problemRepository.findById(problemId)).willReturn(Optional.of(existingProblem));
        given(problemRepository.save(any(Problem.class))).willReturn(existingProblem);
        given(problemConverter.toDto(any(Problem.class))).willReturn(updateDto);
    
        // when: ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด
        ProblemDto result = problemService.updateProblem(problemId, updateDto);
    
        // then: ์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์ œ๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค
        assertNotNull(result, "์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์ œ๋Š” null์ด ์•„๋‹ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals("์ˆ˜์ •๋œ ์ œ๋ชฉ", result.getTitle(), "์ œ๋ชฉ์ด ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals("์ˆ˜์ •๋œ ์„ค๋ช…", result.getDescription(), "์„ค๋ช…์ด ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals(5, result.getLevel(), "๋ ˆ๋ฒจ์ด ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals(ProblemCategory.FRONTEND, result.getProblemCategory(), "์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals("์ˆ˜์ •๋œ ๋‹ต์•ˆ", result.getAnswer(), "๋‹ต์•ˆ์ด ์—…๋ฐ์ดํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
    
        verify(problemRepository).findById(problemId);
        verify(problemRepository).save(any(Problem.class));
        verify(problemConverter).toDto(any(Problem.class));
    }
  3. ๋ช…์‹œ์ ์ธ Mockito ์ž„ํฌํŠธ:

    import static org.mockito.Mockito.when;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.any;
    import static org.mockito.Mockito.willDoNothing; // ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ถ”๊ฐ€
  4. ๋ณด์•ˆ ๊ด€๋ จ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ถ”๊ฐ€ (๊ณ ๋ ค):

    • ๋งŒ์•ฝ ProblemService๊ฐ€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ํฌํ•จํ•œ๋‹ค๋ฉด, ์•…์˜์ ์ธ ์ž…๋ ฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์ด๋‚˜ ์„ค๋ช…์— SQL Injection์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ˆ˜ ๋ฌธ์ž๋ฅผ ํฌํ•จ์‹œ์ผœ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

์š”์•ฝ

์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ œ์•ˆ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ์„ ๋”์šฑ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ProblemDto์˜ ๋™๋“ฑ์„ฑ ๋น„๊ต ๊ตฌํ˜„ ๋ฐ updateProblem() ํ…Œ์ŠคํŠธ์˜ ๋‚ด์šฉ ๊ฒ€์ฆ ๊ฐ•ํ™”๋Š” ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

์ฝ”๋“œ ๋ฆฌ๋ทฐ: src/main/java/site/haruhana/www/controller/ProblemController.java

์ดํ‰:

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , Spring MVC์˜ ํ‘œ์ค€์ ์ธ ๋ฐฉ์‹์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ์‘๋‹ต ํฌ๋งท, API ์—”๋“œํฌ์ธํŠธ ์„ค๊ณ„ ๋“ฑ ๊ธฐ๋ณธ์ ์ธ ์‚ฌํ•ญ๋“ค์ด ์ž˜ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ๊ณผ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ์ฝ๊ธฐ ์‰ฝ๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋ช…ํ™•ํ•œ ๋ณ€์ˆ˜ ์ด๋ฆ„๊ณผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ์ด ์ž˜ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: ์ „์ฒด์ ์œผ๋กœ ์ฝ”๋“œ ์Šคํƒ€์ผ์ด ์ผ๊ด€๋ฉ๋‹ˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ProblemService๋ฅผ ํ†ตํ•ด ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. BaseResponse DTO๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‘๋‹ต ํ˜•์‹์„ ํ†ต์ผํ•œ ๊ฒƒ๋„ ์ข‹์€ ์„ ํƒ์ž…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ :

  • createProblem ๋ฉ”์„œ๋“œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: createProblem ๋ฉ”์„œ๋“œ์—์„œ Exception์„ catchํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๊ด‘๋ฒ”์œ„ํ•ฉ๋‹ˆ๋‹ค. ์ข€ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ(์˜ˆ: IllegalArgumentException, DataIntegrityViolationException)๋ฅผ catchํ•˜์—ฌ, ์–ด๋–ค ์ข…๋ฅ˜์˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•˜๊ณ , ๊ทธ์— ๋งž๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฌธ์ œ ํ•ด๊ฒฐ์ด ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • updateProblem ๋ฉ”์„œ๋“œ์˜ ID ๊ฒ€์ฆ: updateProblem ๋ฉ”์„œ๋“œ์—์„œ @PathVariable Long id ์™€ problemDto ๋‚ด๋ถ€์˜ ID๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ์ž˜๋ชป๋œ ์š”์ฒญ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” problemService์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ง€๋งŒ, ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory ๋ฉ”์„œ๋“œ์˜ ํŽ˜์ด์ง€ ์ •๋ณด: getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ ํŽ˜์ด์ง€ ์ •๋ณด๋ฅผ ์‘๋‹ต์— ํฌํ•จํ•˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์ฒด ํŽ˜์ด์ง€ ์ˆ˜, ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ๋“ฑ ํŽ˜์ด์ง€ ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. BaseResponse์— ํŽ˜์ด์ง€ ์ •๋ณด๋ฅผ ๋‹ด์„ ์ˆ˜ ์žˆ๋„๋ก ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • deleteProblem ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜๊ฐ’: deleteProblem ๋ฉ”์„œ๋“œ๋Š” ์‚ญ์ œ ์„ฑ๊ณต ์‹œ null์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ญ์ œ API๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ 204 No Content ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. BaseResponse<Void> ๋ณด๋‹ค๋Š” ResponseEntity<Void>๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • N+1 ๋ฌธ์ œ: ProblemService ๋‚ด์—์„œ getAllProblems, getProblemById, getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” ํ•„๋“œ๋ฅผ ์กฐํšŒํ•  ๋•Œ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฌ์šฐ๋ฏ€๋กœ, JPA์˜ Fetch Join, Entity Graph ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๋„๋ก ์ตœ์ ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ๋ฌธ์ œ ์กฐํšŒ API(getProblemById, getAllProblems, getProblemsByCategory)๋Š” ์ž์ฃผ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์บ์‹ฑ์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹ฑ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • DTO ๋งคํ•‘: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ProblemDto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์•„์งˆ์ˆ˜๋ก ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฏ€๋กœ, MapStruct ๋“ฑ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DTO ๋งคํ•‘์„ ์ตœ์ ํ™”ํ•˜๊ฑฐ๋‚˜, ์ง์ ‘ ๋งคํ•‘ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋”๋ผ๋„ ์„ฑ๋Šฅ์— ์œ ์˜ํ•˜์—ฌ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์ธ๊ฐ€(Authorization) ๋ฏธ์ ์šฉ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์ธ์ฆ(Authentication)๋งŒ ๊ณ ๋ ค๋˜์–ด ์žˆ๊ณ , ์ธ๊ฐ€(Authorization)๋Š” ๊ณ ๋ ค๋˜์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. API ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ์„ค์ •ํ•˜์—ฌ, ํŠน์ • ์—ญํ• (์˜ˆ: ADMIN)๋งŒ ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ๊ฐ€๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • SQL Injection: getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ category ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPA Repository๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ๋‚˜ @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ๋•Œ SQL Injection์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด Spring Data JPA์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ: createProblem ๋ฐ updateProblem ๋ฉ”์„œ๋“œ์—์„œ ProblemDto์˜ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์˜ ๊ธธ์ด ์ œํ•œ, ๋‚ด์šฉ์˜ ํ•„์ˆ˜ ์—ฌ๋ถ€ ๋“ฑ์„ ๊ฒ€์ฆํ•˜์—ฌ ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: createProblem์—์„œ Exception ๋Œ€์‹  ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ๋ฅผ catchํ•˜๊ณ , ๊ฐ ์˜ˆ์™ธ์— ๋งž๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ID ๊ฒ€์ฆ ๋กœ์ง ์ถ”๊ฐ€: updateProblem ๋ฉ”์„œ๋“œ์—์„œ @PathVariable Long id ์™€ problemDto ๋‚ด๋ถ€์˜ ID๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง€ ์ •๋ณด ์‘๋‹ต์— ํฌํ•จ: getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ ํŽ˜์ด์ง€ ์ •๋ณด๋ฅผ ์‘๋‹ต์— ํฌํ•จํ•˜๋„๋ก BaseResponse ๋˜๋Š” ๋ณ„๋„์˜ DTO๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ญ์ œ API ์‘๋‹ต ์ฝ”๋“œ ๋ณ€๊ฒฝ: deleteProblem ๋ฉ”์„œ๋“œ์—์„œ ResponseEntity<Void>๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ 204 No Content ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
  • N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: JPA Fetch Join, Entity Graph ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
  • ์บ์‹ฑ ์ ์šฉ: ๋ฌธ์ œ ์กฐํšŒ API์— ์บ์‹ฑ์„ ์ ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ์ธ๊ฐ€(Authorization) ์ ์šฉ: Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ API ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection ๋ฐฉ์ง€: JPA Repository์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ SQL Injection ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ ์ถ”๊ฐ€: Spring Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ProblemDto์˜ ์ž…๋ ฅ๊ฐ’์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • Swagger/OpenAPI ๋„์ž…: API ๋ฌธ์„œ ์ž๋™ํ™”๋ฅผ ์œ„ํ•ด Swagger ๋˜๋Š” OpenAPI๋ฅผ ๋„์ž…ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜์—ฌ ๋”์šฑ ์•ˆ์ •์ ์ด๊ณ  ํšจ์œจ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/Service/ProblemService.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/Service/ProblemService.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์„ ํ™œ์šฉํ•˜์—ฌ boilerplate code๋ฅผ ์ค„์˜€๊ณ , Slf4j๋ฅผ ํ†ตํ•ด ๋กœ๊น…์„ ์ ์ ˆํ•˜๊ฒŒ ํ™œ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์ฃผ์„๋„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ผ๊ด€์„ฑ: updateProblem ๋ฉ”์„œ๋“œ์—์„œ Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•œ ํ›„, problem.update(updatedProblemDto)๋ฅผ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ๋‹ค์‹œ problemRepository.save(problem)์„ ํ˜ธ์ถœํ•˜์—ฌ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ, ์—”ํ‹ฐํ‹ฐ์˜ update ๋ฉ”์„œ๋“œ๋Š” ๊ตฌ์ฒด์ ์œผ๋กœ ์–ด๋–ค ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ฃผ์„์„ ๋‹ฌ์•„์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • DTO ๋ณ€ํ™˜ ์œ„์น˜: getProblemsByCategory ๋ฉ”์„œ๋“œ๋Š” Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. API ์ŠคํŽ™์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒ ์ง€๋งŒ, ์ผ๋ฐ˜์ ์œผ๋กœ Service Layer์—์„œ DTO๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ณ , presentation layer์™€ domain layer ๊ฐ„์˜ ์˜์กด์„ฑ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: deleteProblem์—์„œ ProblemNotFoundException์„ catchํ•˜์—ฌ ๋‹ค์‹œ throwํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์˜ˆ์™ธ๋Š” ์ด๋ฏธ ์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ์œผ๋ฉฐ, catch ๋ธ”๋ก์ด ์—†์–ด๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฉ”์„œ๋“œ ์ด๋ฆ„: updateProblem์—์„œ ์ธ์ž๋กœ updatedProblemDto๋ฅผ ๋ฐ›๊ณ  ์žˆ๋Š”๋ฐ, update ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ DTO์˜ ๋ฐ์ดํ„ฐ๋ฅผ Problem ์—”ํ‹ฐํ‹ฐ์— ๋ณต์‚ฌํ•˜๋Š” ๋กœ์ง์ด ์žˆ๋‹ค๋ฉด, updatedProblemDto ์ธ์ˆ˜๋ฅผ ๋ฐ›๋Š” ๊ฒƒ์€ ๋‹น์—ฐํ•˜์ง€๋งŒ ๋ฉ”์„œ๋“œ ์ด๋ฆ„๋งŒ ๋ด์„œ๋Š” ๋ณต์‚ฌ ๋กœ์ง์ด ์žˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • updateProblem ๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜: @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ์ง€๋งŒ, problem.update(updatedProblemDto) ๋‚ด๋ถ€์—์„œ ์–ด๋–ค ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, updatedProblemDto์— ํŠน์ • ํ•„๋“œ๊ฐ€ null๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ํ•„๋“œ๊ฐ€ null๋กœ ์—…๋ฐ์ดํŠธ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • updateProblem ์ €์žฅ ๋กœ์ง: update ๋ฉ”์„œ๋“œ์—์„œ ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธ ํ›„ problemRepository.save(problem)๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋œ ์ƒํƒœ์—์„œ ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด, JPA์˜ ๋ณ€๊ฒฝ ๊ฐ์ง€(Dirty Checking) ๊ธฐ๋Šฅ์— ์˜ํ•ด ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์ ์— ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.
  • ์นดํ…Œ๊ณ ๋ฆฌ ์กฐํšŒ ์‹œ DTO ๋ณ€ํ™˜ ์—†์Œ: getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ API ์ŠคํŽ™์— ๋”ฐ๋ผ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์— ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๊ณ , ๋ ˆ์ด์–ด ๊ฐ„์˜ ์˜์กด์„ฑ์ด ๋†’์•„์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ: getAllProblems ๋ฉ”์„œ๋“œ์—์„œ findAll(pageable).map(ProblemDto::new)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ProblemDto ์ƒ์„ฑ ๊ณผ์ •์—์„œ Lazy Loading๋˜๋Š” ํ•„๋“œ์— ์ ‘๊ทผํ•˜๋ฉด N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Problem ์—”ํ‹ฐํ‹ฐ์™€ ๊ด€๋ จ๋œ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์˜ค๋„๋ก ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: problemRepository.findAllWithFetchJoin(pageable))
  • existsById ๋ถˆํ•„์š”ํ•œ ํ˜ธ์ถœ: deleteProblem ๋ฉ”์„œ๋“œ์—์„œ problemRepository.existsById(id)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ , deleteById(id)๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. deleteById(id)๋Š” ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID๋กœ ํ˜ธ์ถœ๋  ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฏ€๋กœ, existsById(id) ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•˜๊ณ  deleteById(id) ํ˜ธ์ถœ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ catchํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ save ํ˜ธ์ถœ: updateProblem ๋ฉ”์„œ๋“œ์—์„œ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ณ€๊ฒฝ ๊ฐ์ง€์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋ฏ€๋กœ, problemRepository.save(problem) ํ˜ธ์ถœ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ๊ถŒํ•œ ๊ฒ€์‚ฌ ๋ถ€์žฌ: ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ ์‹œ ์š”์ฒญ์„ ๋ณด๋‚ธ ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๋Š” ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ ๋ถ€์žฌ: createProblem, updateProblem ๋ฉ”์„œ๋“œ์—์„œ ProblemDto์˜ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. XSS ๊ณต๊ฒฉ, SQL Injection ๊ณต๊ฒฉ ๋“ฑ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: @Valid ์–ด๋…ธํ…Œ์ด์…˜, Bean Validation)
  • ๊ฐœ์ธ ์ •๋ณด ๋…ธ์ถœ: Problem ์—”ํ‹ฐํ‹ฐ์— ๊ฐœ์ธ ์ •๋ณด์™€ ๊ด€๋ จ๋œ ํ•„๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด, DTO๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • DTO ๋ณ€ํ™˜: getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ProblemDto๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
  • updateProblem ๋ฉ”์„œ๋“œ ๊ฐœ์„ :
    • problem.update(updatedProblemDto) ๋‚ด์—์„œ ์–ด๋–ค ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š”์ง€ ์ฃผ์„์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • problemRepository.save(problem) ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
  • deleteProblem ๋ฉ”์„œ๋“œ ๊ฐœ์„ :
    • problemRepository.existsById(id) ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•˜๊ณ , deleteById(id) ํ˜ธ์ถœ ์‹œ ๋ฐœ์ƒํ•˜๋Š” EmptyResultDataAccessException (Spring Data JPA์—์„œ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID๋กœ ์‚ญ์ œ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ)๋ฅผ catchํ•˜์—ฌ ProblemNotFoundException์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋˜์ง‘๋‹ˆ๋‹ค.
  • N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: getAllProblems ๋ฉ”์„œ๋“œ์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์œผ๋ฏ€๋กœ, Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋„๋ก ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ ๋ฐ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ ์ถ”๊ฐ€: Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ณ , Bean Validation ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • Problem ์—”ํ‹ฐํ‹ฐ update ๋ฉ”์„œ๋“œ ๊ตฌํ˜„: updateProblemDto์˜ ํ•„๋“œ๋ฅผ Problem ์—”ํ‹ฐํ‹ฐ์— ์•ˆ์ „ํ•˜๊ฒŒ ๋ณต์‚ฌํ•˜๋„๋ก Problem ์—”ํ‹ฐํ‹ฐ์— update ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. (null ๊ฐ’ ์ฒ˜๋ฆฌ, ํ•„๋“œ ์ œํ•œ ๋“ฑ ๊ณ ๋ ค)

๊ฐœ์„ ๋œ deleteProblem ๋ฉ”์„œ๋“œ ์˜ˆ์‹œ:

public void deleteProblem(Long id) {
    log.info("๋ฌธ์ œ ์‚ญ์ œ ์‹œ์ž‘ - ๋ฌธ์ œ ID: {}", id);
    try {
        problemRepository.deleteById(id);
        log.info("๋ฌธ์ œ ์‚ญ์ œ ์™„๋ฃŒ - ๋ฌธ์ œ ID: {}", id);
    } catch (EmptyResultDataAccessException e) {
        log.error("๋ฌธ์ œ ์‚ญ์ œ ์‹คํŒจ - ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ (ID: {})", id);
        throw new ProblemNotFoundException("์‚ญ์ œํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
    } catch (Exception e) {
        log.error("๋ฌธ์ œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ - ๋ฌธ์ œ ID: {}, ์˜ค๋ฅ˜: {}", id, e.getMessage());
        throw new RuntimeException("๋ฌธ์ œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", e);
    }
}

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋œ ์ฝ”๋“œ์ด์ง€๋งŒ, ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ฑ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ, ๊ถŒํ•œ ๊ฒ€์‚ฌ, ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์€ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ˆ ๋ฐ˜๋“œ์‹œ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

ProblemDto.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค. src/main/java/site/haruhana/www/dto/ProblemDto.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , Builder ํŒจํ„ด์„ ์ ์šฉํ•˜์—ฌ ๊ฐ์ฒด ์ƒ์„ฑ์„ ์šฉ์ดํ•˜๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ์ด ๋‚˜์˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ์ƒ์„ฑ์ž, getter ๋“ฑ์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ๊ฐ„๊ฒฐ์„ฑ์„ ์œ ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ช…๋ช… ๊ทœ์น™: ๋ณ€์ˆ˜๋ช… (id, title, description ๋“ฑ)์€ ์ง๊ด€์ ์ด๊ณ  ์˜๋ฏธ๋ฅผ ์ž˜ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋˜๋Š” ๋ฌธ์ œ์ :

  • ์ƒ์„ฑ์ž ๋ถˆ์ผ์น˜: Problem problem ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ProblemDto๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ƒ์„ฑ์ž์—์„œ this.problemCategory = getProblemCategory(); ๋ผ๊ณ  ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” Problem ์—”ํ‹ฐํ‹ฐ์—์„œ problemCategory ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ProblemDto ํด๋ž˜์Šค ๋‚ด์— ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์€ ๋ฉ”์„œ๋“œ (getProblemCategory()) ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ์˜๋„ํ•œ ๋ฐ”๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์ˆ˜์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์•„๋งˆ problem.getProblemCategory()๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๊ณ  ํ–ˆ๋˜ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: Problem ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ ์ค‘ ํ•˜๋‚˜๋ผ๋„ null ๊ฐ’์„ ๊ฐ€์งˆ ๊ฒฝ์šฐ, DTO ์ƒ์„ฑ ์‹œ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ description, answer๋Š” null์ด ํ—ˆ์šฉ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ๊ฐ์ฒด ๋ณต์‚ฌ: Problem ์—”ํ‹ฐํ‹ฐ์—์„œ ProblemDto๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋ผ๋ฉด, MapStruct์™€ ๊ฐ™์€ ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ ์ฝ”๋“œ ๊ทœ๋ชจ์—์„œ๋Š” ํฌ๊ฒŒ ๋ฌธ์ œ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ ๊ฐ€๋Šฅ์„ฑ: answer ํ•„๋“œ๋Š” ๋ฌธ์ œ์˜ ์ •๋‹ต์„ ๋‹ด๊ณ  ์žˆ์œผ๋ฏ€๋กœ, API ์‘๋‹ต ๋“ฑ์œผ๋กœ ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ DTO๋ฅผ ๋ถ„๋ฆฌํ•˜๊ฑฐ๋‚˜, ์‘๋‹ต ์‹œ answer ํ•„๋“œ๋ฅผ ์ œ์™ธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection ๋ฐฉ์ง€: DTO๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ์ฃผ์ง€๋Š” ์•Š์ง€๋งŒ, DTO์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DTO์˜ ํ•„๋“œ ๊ฐ’์„ ์‚ฌ์šฉํ•  ๋•Œ ์ ์ ˆํ•œ escaping ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • ์ƒ์„ฑ์ž ์ˆ˜์ •: ProblemDto(Problem problem) ์ƒ์„ฑ์ž ๋‚ด์˜ this.problemCategory = getProblemCategory();๋ฅผ this.problemCategory = problem.getProblemCategory();๋กœ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = problem.getDescription();
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory();
    }
  • NullPointerException ๋ฐฉ์ง€: Problem ์—”ํ‹ฐํ‹ฐ ํ•„๋“œ ์ค‘ null ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ํ•„๋“œ์— ๋Œ€ํ•œ null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. Optional์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = Optional.ofNullable(problem.getDescription()).orElse(""); // or empty string
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory();
    }
  • ํ•„๋“œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: ํ•„์š”์— ๋”ฐ๋ผ DTO ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, @NotNull, @Size ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (javax.validation.constraints ์‚ฌ์šฉ)

  • ํ•„์š”์— ๋”ฐ๋ฅธ DTO ๋ถ„๋ฆฌ: API ์‘๋‹ต ์‹œ answer ํ•„๋“œ๋ฅผ ์ œ์™ธํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์‘๋‹ต ์ „์šฉ DTO๋ฅผ ๋ณ„๋„๋กœ ์ƒ์„ฑํ•˜์—ฌ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋œ ์ฝ”๋“œ์ด์ง€๋งŒ, ์œ„์— ์–ธ๊ธ‰๋œ ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ์ ์„ ์ˆ˜์ •ํ•˜๋ฉด ๋”์šฑ ์•ˆ์ •์ ์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ์ƒ์„ฑ์ž ์ˆ˜์ •์€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ณด์•ˆ์ ์ธ ์ธก๋ฉด๊ณผ ์ž ์žฌ์ ์ธ NullPointerException ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์— ์œ ์˜ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ์ œ์‹œ๋œ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฒ€ํ† ํ•˜๊ณ  ๋ฆฌ๋ทฐํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ฅผ ์ž˜ ๊ฐ–์ถ”๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ํŒจํ‚ค์ง€ ๋ช…์นญ, ํด๋ž˜์Šค ๋ช…์นญ, ๋ณ€์ˆ˜ ๋ช…์นญ ๋ชจ๋‘ ์˜๋ฏธ๊ฐ€ ๋ช…ํ™•ํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค. ๋“ค์—ฌ์“ฐ๊ธฐ ๋“ฑ ๊ธฐ๋ณธ์ ์ธ ์ฝ”๋“œ ์Šคํƒ€์ผ๋„ ์ค€์ˆ˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • Checked Exception vs Unchecked Exception: ProblemNotFoundException์€ RuntimeException์„ ์ƒ์†๋ฐ›๋Š” Unchecked Exception์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ try-catch ๋ธ”๋ก์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•„๋„ ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ƒ ๋ฐ˜๋“œ์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์˜ˆ์™ธ๋ผ๋ฉด Exception ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›๋Š” Checked Exception์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Checked Exception์€ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๊ฐ•์ œํ•˜์—ฌ ํ”„๋กœ๊ทธ๋žจ์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Unchecked Exception์€ ๋ถˆํ•„์š”ํ•œ try-catch ๋ธ”๋ก์„ ์ค„์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋Š” ๋ฐœ์ƒ ์›์ธ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ด์•ผ ๋””๋ฒ„๊น…์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ "Problem Not Found"๋ณด๋‹ค๋Š” ์–ด๋–ค ๋ฌธ์ œ(Problem)๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, "Problem with ID '123' not found." ์™€ ๊ฐ™์ด ํŠน์ • ID๋ฅผ ์–ธ๊ธ‰ํ•˜๋ฉด ๋ฌธ์ œ ํ•ด๊ฒฐ์— ๋” ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ํ•ด๋‹น ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜์—ฌ ํŠน๋ณ„ํ•œ ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์˜ˆ์™ธ๊ฐ€ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ์˜ˆ์™ธ ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ์ด ๋ถ€๋‹ด๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ProblemNotFoundException์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋งŒ ๋˜์ ธ์ง€๋Š” ์˜ˆ์™ธ์ด๋ฏ€๋กœ, ์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ ํฌ๊ฒŒ ๊ณ ๋ คํ•  ๋ถ€๋ถ„์€ ์•„๋‹™๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ํ•ด๋‹น ์ฝ”๋“œ๋Š” ์ง์ ‘์ ์ธ ๋ณด์•ˆ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด (์˜ˆ: ์‚ฌ์šฉ์ž ๊ฐœ์ธ ์ •๋ณด, ์‹œ์Šคํ…œ ๋‚ด๋ถ€ ์ •๋ณด)๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ •๋ณด๊ฐ€ ์˜ˆ์™ธ ๋กœ๊ทธ์— ๊ธฐ๋ก๋  ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž์—๊ฒŒ ์•…์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ๊ตฌ์ฒดํ™”: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ์–ด๋–ค ๋ฌธ์ œ(Problem)๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.

    public ProblemNotFoundException(Long problemId) {
        super("Problem with ID '" + problemId + "' not found.");
    }
    
    public ProblemNotFoundException(String problemName) {
        super("Problem with name '" + problemName + "' not found.");
    }
  • Checked Exception ์—ฌ๋ถ€ ๊ณ ๋ ค: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ƒ ๋ฐ˜๋“œ์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์˜ˆ์™ธ๋ผ๋ฉด Exception ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›๋Š” Checked Exception์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.

  • ์ปค์Šคํ…€ ์˜ˆ์™ธ ์ •๋ณด ์ถ”๊ฐ€ (์„ ํƒ์ ): ๋งŒ์•ฝ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์ถ”๊ฐ€์ ์ธ ์ •๋ณด(์˜ˆ: ์š”์ฒญ URI, ์‚ฌ์šฉ์ž ID ๋“ฑ)๋ฅผ ๋กœ๊น…ํ•˜๊ฑฐ๋‚˜ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ์ •๋ณด๋ฅผ ํ•„๋“œ๋กœ ์ถ”๊ฐ€ํ•˜๊ณ  ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    public class ProblemNotFoundException extends RuntimeException {
        private final Long problemId;
    
        public ProblemNotFoundException(Long problemId) {
            super("Problem with ID '" + problemId + "' not found.");
            this.problemId = problemId;
        }
    
        public Long getProblemId() {
            return problemId;
        }
    }
  • Exception Handler ์‚ฌ์šฉ: Spring Framework์—์„œ๋Š” @ControllerAdvice ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ProblemNotFoundException์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํŠน์ • ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก Exception Handler๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด, ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ง์ ‘ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ฝ”๋“œ๋Š” ์ „๋ฐ˜์ ์œผ๋กœ ํ›Œ๋ฅญํ•˜๋ฉฐ, ๊ฐ€๋…์„ฑ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์šฉ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ์ฒดํ™”ํ•˜๋Š” ๊ฒƒ์€ ๋””๋ฒ„๊น…์— ํฐ ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. Checked Exception์œผ๋กœ ๋ณ€๊ฒฝํ• ์ง€๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์—ฌ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/converter/ProblemConverter.java ๋ฆฌ๋ทฐ

ProblemConverter.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ์งง๊ณ  ๊ฐ„๊ฒฐํ•˜๋ฉฐ, toEntity์™€ toDto ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚˜ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋ช…๋ช… ๊ทœ์น™ ์ค€์ˆ˜: ๋ณ€์ˆ˜๋ช…, ํด๋ž˜์Šค๋ช…, ๋ฉ”์„œ๋“œ๋ช…์ด Java ํ‘œ์ค€ ๋ช…๋ช… ๊ทœ์น™์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„ ๋ถ€์กฑ: ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•˜์—ฌ ํ˜„์žฌ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ๋ณต์žก๋„๊ฐ€ ์ฆ๊ฐ€ํ•  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…์„ ์ฃผ์„์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: @param, @return ํƒœ๊ทธ๋ฅผ ํ™œ์šฉํ•œ JavaDoc)

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: ProblemDto ๋˜๋Š” Problem ๊ฐ์ฒด์˜ ํ•„๋“œ๊ฐ€ null์ผ ๊ฒฝ์šฐ, NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งคํ•‘ ๋ˆ„๋ฝ: ํ˜„์žฌ๋Š” Problem ์—”ํ‹ฐํ‹ฐ์™€ ProblemDto๊ฐ€ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๊ณต์œ ํ•˜์ง€๋งŒ, ์ถ”ํ›„ ํ•„๋“œ๊ฐ€ ์ถ”๊ฐ€/์‚ญ์ œ๋  ๊ฒฝ์šฐ ๋งคํ•‘์ด ๋ˆ„๋ฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ปดํŒŒ์ผ ์‹œ์ ์— ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋Š” ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (์˜ˆ: MapStruct) ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹จ๋ฐฉํ–ฅ ๋ณ€ํ™˜: toEntity๋Š” ProblemDto์—์„œ Problem์œผ๋กœ, toDto๋Š” Problem์—์„œ ProblemDto๋กœ์˜ ๋ณ€ํ™˜๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์˜ ๋ณ€ํ™˜๋„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ฌธ์ œ ์—†์Œ)

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์„ฑ๋Šฅ ์ด์Šˆ๋Š” ์—†์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ: ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•˜๊ณ  ๋ณต์žกํ•œ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ˜„์žฌ ์„ฑ๋Šฅ ์ด์Šˆ๋Š” ์—†์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค.
  • ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ: Builder ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ, ์žฆ์€ ๊ฐ์ฒด ์ƒ์„ฑ์€ ์•ฝ๊ฐ„์˜ ์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ฃผ ๋†’์€ ๋นˆ๋„๋กœ ๋ณ€ํ™˜์ด ์ด๋ฃจ์–ด์ง„๋‹ค๋ฉด ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์ด๋‚˜ ๊ฐ์ฒด ํ’€๋ง ๋“ฑ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์—๋Š” Builder ํŒจํ„ด์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ ๋ฐฉ์ง€: Problem ์—”ํ‹ฐํ‹ฐ์— ๋ฏผ๊ฐํ•œ ์ •๋ณด (์˜ˆ: ๋น„๋ฐ€๋ฒˆํ˜ธ, ๊ฐœ์ธ์ •๋ณด)๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ProblemDto๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€๋กœ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด DTO์— ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜์—ฌ ๋ณด์•ˆ ์ˆ˜์ค€์„ ์กฐ์ ˆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ: DTO๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅ๋ฐ›์„ ๋•Œ, ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ฒ ์ €ํžˆ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์˜ ๊ธธ์ด ์ œํ•œ, ํ—ˆ์šฉ๋˜๋Š” ๋ฌธ์ž ์ข…๋ฅ˜ ๋“ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ SQL Injection์ด๋‚˜ XSS ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๋ณ„๋„ Validation ๋กœ์ง ํ•„์š”)

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. NullPointerException ๋ฐฉ์ง€: ProblemDto์™€ Problem ํ•„๋“œ์˜ null ๊ฐ€๋Šฅ์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ if๋ฌธ์ด๋‚˜ Optional์„ ์‚ฌ์šฉํ•˜์—ฌ NullPointerException์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: dto.getTitle() != null ? dto.getTitle() : "")
  2. ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋„์ž… (์„ ํƒ ์‚ฌํ•ญ): MapStruct์™€ ๊ฐ™์€ ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ ๋งคํ•‘์„ ์ž๋™ํ™”ํ•˜๊ณ , ์ปดํŒŒ์ผ ์‹œ์ ์— ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. (ํ•„์ˆ˜ ์‚ฌํ•ญ์€ ์•„๋‹˜)
  3. ์ฃผ์„ ์ถ”๊ฐ€: ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ JavaDoc ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  4. Validation ๋กœ์ง ์ถ”๊ฐ€: DTO์— ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Spring Validation์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @NotNull, @Size ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ DTO ํ•„๋“œ์— ๋Œ€ํ•œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  5. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: ProblemConverter ํด๋ž˜์Šค์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์ •ํ™•์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, null ๊ฐ’์„ ํฌํ•จํ•œ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ (NullPointerException ๋ฐฉ์ง€):

public Problem toEntity(ProblemDto dto) {
    return Problem.builder()
            .title(dto.getTitle() != null ? dto.getTitle() : "") // null ์ฒ˜๋ฆฌ
            .description(dto.getDescription() != null ? dto.getDescription() : "") // null ์ฒ˜๋ฆฌ
            .level(dto.getLevel() != null ? dto.getLevel() : 0) // null ์ฒ˜๋ฆฌ
            .answer(dto.getAnswer() != null ? dto.getAnswer() : "") // null ์ฒ˜๋ฆฌ
            .problemCategory(dto.getProblemCategory() != null ? dto.getProblemCategory() : "") // null ์ฒ˜๋ฆฌ
            .build();
}

์˜ˆ์‹œ (์ฃผ์„ ์ถ”๊ฐ€):

/**
 * ProblemDto ๊ฐ์ฒด๋ฅผ Problem ์—”ํ‹ฐํ‹ฐ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 *
 * @param dto ProblemDto ๊ฐ์ฒด
 * @return Problem ์—”ํ‹ฐํ‹ฐ
 */
public Problem toEntity(ProblemDto dto) {
    // ...
}

/**
 * Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ProblemDto ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 *
 * @param entity Problem ์—”ํ‹ฐํ‹ฐ
 * @return ProblemDto ๊ฐ์ฒด
 */
public ProblemDto toDto(Problem entity) {
    // ...
}

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•ด๋ณด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํŠนํžˆ NullPointerException ๋ฐฉ์ง€์™€ ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์€ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

ProblemRepository.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Problem ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ์„ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ์ฝ”๋“œ ํ’ˆ์งˆ ์ž์ฒด๋Š” ๋‚˜์˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์‹ค์ œ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋”ฐ๋ผ ๊ฐœ์„ ๋  ๋ถ€๋ถ„์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ์žฅ์ :

    • ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ์งง๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.
    • Spring Data JPA์˜ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์–ด ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. findByProblemCategory๋ผ๋Š” ๋ฉ”์„œ๋“œ ์ด๋ฆ„์€ ๋ช…ํ™•ํ•˜๊ฒŒ ์–ด๋–ค ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
    • JPA Repository๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ์ž๋™์œผ๋กœ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์ƒ์‚ฐ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :

    • ํŠน๋ณ„ํžˆ ์—†์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ์›Œ๋‚™ ๊ฐ„๊ฒฐํ•˜์—ฌ ๊ฐ€๋…์„ฑ์— ๋ฌธ์ œ๊ฐ€ ๋  ๋ถ€๋ถ„์€ ์—†์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • ๋ฌธ์ œ์ :

    • ProblemCategory๋กœ๋งŒ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ธฐ๋Šฅ๋งŒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ์ œ๋ชฉ, ๋‚ด์šฉ, ์ž‘์„ฑ์ž ๋“ฑ ๋‹ค์–‘ํ•œ ์กฐ๊ฑด์œผ๋กœ ๊ฒ€์ƒ‰ํ•ด์•ผ ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.
    • ์‚ญ์ œ ๊ธฐ๋Šฅ(delete)์— ๋Œ€ํ•œ ๊ณ ๋ ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์—ฐ๊ด€๊ด€๊ณ„ ์„ค์ •์ด ์ž˜๋ชป๋˜์–ด ์žˆ๋‹ค๋ฉด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: ProblemCategory ์‚ญ์ œ ์‹œ ์—ฐ๊ด€๋œ Problem๋“ค์ด ๋‚จ์•„์žˆ๋Š” ๊ฒฝ์šฐ)
  • ํ•ด๊ฒฐ ๋ฐฉ์•ˆ:

    • ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPQL์„ ์ง์ ‘ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, Spring Data JPA์˜ Specification์„ ํ™œ์šฉํ•˜์—ฌ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์‚ญ์ œ ์‹œ ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ „๋žต์„ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: orphanRemoval = true, @OnDelete ์–ด๋…ธํ…Œ์ด์…˜ ํ™œ์šฉ)

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ๊ฐœ์„  ํฌ์ธํŠธ:
    • ProblemCategory ์—”ํ‹ฐํ‹ฐ์™€์˜ ๊ด€๊ณ„ ์„ค์ • ์‹œ, N+1 ๋ฌธ์ œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. fetch join์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, @EntityGraph ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ ์ฆ‰์‹œ ๋กœ๋”ฉ(eager loading)ํ•˜๋„๋ก ์„ค์ •ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๊ฒ€์ƒ‰ ์กฐ๊ฑด์ด ๋ณต์žกํ•ด์งˆ ๊ฒฝ์šฐ, ์ธ๋ฑ์Šค ํ™œ์šฉ ์ „๋žต์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ProblemCategory ํ•„๋“œ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ด€๋ จํ•˜์—ฌ ์ด ๊ฐœ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด Count Query๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ํŠนํžˆ ๋ณต์žกํ•œ Join์ด ์žˆ๋Š” ๊ฒฝ์šฐ)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์ด์Šˆ:

    • SQL Injection ๊ณต๊ฒฉ์— ์ง์ ‘์ ์œผ๋กœ ๋…ธ์ถœ๋  ๊ฐ€๋Šฅ์„ฑ์€ ๋‚ฎ์Šต๋‹ˆ๋‹ค. Spring Data JPA๋Š” PreparedStatement๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
    • ํ•˜์ง€๋งŒ, ๋งŒ์•ฝ @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPQL์„ ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ JPQL์— ํฌํ•จ์‹œํ‚ค๋ฉด SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ด๊ฒฐ ๋ฐฉ์•ˆ:

    • JPQL ์ž‘์„ฑ ์‹œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ตœ๋Œ€ํ•œ ํ”ผํ•˜๊ณ , ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ํ™œ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ์ฒ ์ €ํžˆ ํ•˜์—ฌ ์ธ๊ฐ€๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  1. ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ง€์›:

    Page<Problem> findByProblemCategoryAndTitleContaining(ProblemCategory category, String title, Pageable pageable);
    
    @Query("SELECT p FROM Problem p WHERE p.problemCategory = :category AND p.content LIKE %:keyword%")
    Page<Problem> findByProblemCategoryAndKeywordInContent(@Param("category") ProblemCategory category, @Param("keyword") String keyword, Pageable pageable);
  2. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ (Problem ์—”ํ‹ฐํ‹ฐ์— ๊ด€๊ณ„ ์„ค์ •์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •):

    @Query("SELECT p FROM Problem p JOIN FETCH p.author WHERE p.problemCategory = :category")
    Page<Problem> findByProblemCategoryWithAuthor(@Param("category") ProblemCategory category, Pageable pageable);
    
    // ๋˜๋Š”
    @EntityGraph(attributePaths = "author")
    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
  3. ์‚ญ์ œ ๊ด€๋ จ ์ฒ˜๋ฆฌ:

    ProblemCategory ์—”ํ‹ฐํ‹ฐ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์—ฐ๊ด€๋œ Problem๋“ค์„ ์ž๋™์œผ๋กœ ์‚ญ์ œํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ์ •์ฑ…์— ๋”ฐ๋ผ ๊ฒฐ์ •)

    @OneToMany(mappedBy = "problemCategory", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Problem> problems;
  4. ์ธ๋ฑ์Šค ์ถ”๊ฐ€:

    Problem ์—”ํ‹ฐํ‹ฐ์— problemCategory ํ•„๋“œ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    @Entity
    public class Problem {
        @ManyToOne
        @JoinColumn(name = "problem_category_id")
        @Index(name = "idx_problem_category") // ์ธ๋ฑ์Šค ์ถ”๊ฐ€
        private ProblemCategory problemCategory;
    
        // ...
    }

๊ฒฐ๋ก :

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ์ž˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ์‹ค์ œ ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด, ์„ฑ๋Šฅ ๋ฌธ์ œ, ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ ๋“ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์ถ”๊ฐ€์ ์ธ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์ œ์•ˆ๋“ค์„ ๋ฐ”ํƒ•์œผ๋กœ ์„œ๋น„์Šค ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐ€๋ฉด ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ๋ฆฌ๋ทฐ

GlobalExceptionHandler.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ (์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž ๊ด€์ )

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. ProblemNotFoundException๊ณผ ์ผ๋ฐ˜์ ์ธ Exception์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‘ ๊ฐœ์˜ ExceptionHandler๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ํ•  ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋ฆ„ ๋ช…๋ช… ๊ทœ์น™๋„ ์ž˜ ์ง€์ผœ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค (handleProblemNotFoundException, handleGenericException).
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ํ˜„์žฌ๋Š” ์ž‘์€ ๊ทœ๋ชจ์˜ ์ฝ”๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์œ ์ง€๋ณด์ˆ˜์„ฑ์€ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์ด ๋ณต์žกํ•ด์งˆ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ ๋” ๊ตฌ์กฐํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํด๋ฆฐ ์ฝ”๋“œ: ํ•จ์ˆ˜๊ฐ€ ์งง๊ณ  ํ•˜๋‚˜์˜ ์—ญํ• ๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ ํด๋ฆฐ ์ฝ”๋“œ ์›์น™์„ ์ค€์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ :

  • ์ผ๋ฐ˜์ ์ธ Exception ์ฒ˜๋ฆฌ: handleGenericException์—์„œ ๋ชจ๋“  Exception์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊นŒ์ง€ ์žก์•„๋ฒ„๋ฆด ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ค‘์š”ํ•œ ์ •๋ณด (์˜ˆ: ๋ณด์•ˆ ๊ด€๋ จ ์˜ˆ์™ธ)๋ฅผ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ชจ๋“  ์˜ˆ์™ธ์— ๋Œ€ํ•ด ๋™์ผํ•œ ๋ฉ”์‹œ์ง€(ex.getMessage())๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ex.getMessage() ์˜์กด์„ฑ: ex.getMessage()๊ฐ€ ํ•ญ์ƒ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•œ๋‹ค๋Š” ๋ณด์žฅ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋•Œ๋กœ๋Š” ๋‚ด๋ถ€์ ์ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋กœ๊น… ๋ถ€์žฌ: ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ์ง€ ์•Š์•„ ๋ฌธ์ œ ๋ฐœ์ƒ ์›์ธ์„ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋น„์šฉ์ด ๋†’์€ ์ž‘์—…์ด๋ฏ€๋กœ, ์˜ˆ์™ธ ๋ฐœ์ƒ ๋นˆ๋„๋ฅผ ์ค„์ด๋Š” ๊ฒƒ์ด ์„ฑ๋Šฅ ๊ฐœ์„ ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. (์˜ˆ: ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฐ•ํ™”)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋…ธ์ถœ: ex.getMessage()๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘์† ์ •๋ณด์™€ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„œ๋น„์Šค ๊ฑฐ๋ถ€ ๊ณต๊ฒฉ (DoS): ์˜๋„์ ์œผ๋กœ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์„œ๋ฒ„ ์ž์›์„ ์†Œ๋ชจ์‹œํ‚ค๋Š” ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • handleGenericException ์ œ๊ฑฐ ๋˜๋Š” ๊ฐœ์„ :
    • ์ œ๊ฑฐ: ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์ผ๋ฐ˜์ ์ธ Exception ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ์˜ˆ์ƒ๋˜๋Š” ๋ชจ๋“  ์˜ˆ์™ธ์— ๋Œ€ํ•ด ๊ตฌ์ฒด์ ์ธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์Šต๋‹ˆ๋‹ค.
    • ๊ตฌ์ฒดํ™”: ์ œ๊ฑฐ๊ฐ€ ์–ด๋ ต๋‹ค๋ฉด, ์ตœ์†Œํ•œ ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜๊ณ , ex.getMessage() ๋Œ€์‹  ์‚ฌ์šฉ์ž์—๊ฒŒ ์•ˆ์ „ํ•˜๊ณ  ์˜๋ฏธ ์žˆ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์˜ˆ์™ธ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋‹ค๋ฅธ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋กœ๊น… ์ถ”๊ฐ€: ๋ชจ๋“  ExceptionHandler์— ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ์ , ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€, ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Logback, Log4j์™€ ๊ฐ™์€ ๋กœ๊น… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์—๋Ÿฌ ์‘๋‹ต DTO: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ String์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹ , ์—๋Ÿฌ ์ฝ”๋“œ, ๋ฉ”์‹œ์ง€, timestamp ๋“ฑ์„ ํฌํ•จํ•˜๋Š” DTO (Data Transfer Object)๋ฅผ ์ •์˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ์—์„œ ์—๋Ÿฌ ์‘๋‹ต์„ ๋” ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ตญ์ œํ™” (i18n) ์ง€์›: ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ properties ํŒŒ์ผ ๋“ฑ์„ ์ด์šฉํ•˜์—ฌ ๊ตญ์ œํ™” ์ง€์›์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
  • Custom Exception ์ •์˜ ๋ฐ ํ™œ์šฉ: ProblemNotFoundException ์™ธ์— ๋‹ค๋ฅธ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์˜ˆ์™ธ๋“ค์„ Custom Exception์œผ๋กœ ์ •์˜ํ•˜์—ฌ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋”์šฑ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • AOP ํ™œ์šฉ: ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ AOP (Aspect-Oriented Programming)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋”์šฑ ๋ชจ๋“ˆํ™”ํ•˜๊ณ  ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ์ œํ•œ: ๋กœ๊น…ํ•  ๋•Œ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ๋ชจ๋‘ ๊ธฐ๋กํ•˜๋Š” ๋Œ€์‹ , ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๊ธฐ๋กํ•˜๊ฑฐ๋‚˜, ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์ „์ฒด ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ๊ธฐ๋กํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ์„  ์˜ˆ์‹œ ์ฝ”๋“œ (์ผ๋ถ€):

package site.haruhana.www.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import site.haruhana.www.dto.ErrorResponse; // ErrorResponse DTO ํ•„์š”
import site.haruhana.www.exception.ProblemNotFoundException;
import site.haruhana.www.exception.CustomBadRequestException; // ์˜ˆ์‹œ

import java.time.LocalDateTime;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(ProblemNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleProblemNotFoundException(ProblemNotFoundException ex) {
        logger.warn("Problem not found: {}", ex.getMessage(), ex);
        ErrorResponse errorResponse = new ErrorResponse("PROBLEM_NOT_FOUND", ex.getMessage(), LocalDateTime.now());
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(CustomBadRequestException.class) // ์˜ˆ์‹œ
    public ResponseEntity<ErrorResponse> handleBadRequestException(CustomBadRequestException ex) {
        logger.error("Bad Request: {}", ex.getMessage(), ex); // error ๋ ˆ๋ฒจ๋กœ ๋กœ๊น…
        ErrorResponse errorResponse = new ErrorResponse("BAD_REQUEST", ex.getMessage(), LocalDateTime.now());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
        logger.error("Unexpected error occurred: {}", ex.getMessage(), ex); // ์—๋Ÿฌ ๋กœ๊น…

        // ์‚ฌ์šฉ์ž์—๊ฒŒ ์•ˆ์ „ํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ œ๊ณต
        String errorMessage = "An unexpected error occurred. Please try again later.";
        ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", errorMessage, LocalDateTime.now());
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

ErrorResponse DTO ์˜ˆ์‹œ:

package site.haruhana.www.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor
public class ErrorResponse {
    private String errorCode;
    private String message;
    private LocalDateTime timestamp;
}

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ž˜ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์ง€๋งŒ, ๋ณด์•ˆ, ๋กœ๊น…, ์ผ๋ฐ˜์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ๋“ฑ์—์„œ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ์ œ์•ˆ๋“ค์„ ํ†ตํ•ด ์ฝ”๋“œ๋ฅผ ๋”์šฑ robustํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฝ๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/Service/ProblemService.java ๋ฆฌ๋ทฐ

ProblemService.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/Service/ProblemService.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ธ ์ , ๋ช…ํ™•ํ•œ ์ฃผ์„, ์ ์ ˆํ•œ ๋กœ๊น… ๋“ฑ์ด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: ๋ฉ”์„œ๋“œ ์ด๋ฆ„์€ ๋™์‚ฌ๋กœ ์‹œ์ž‘ํ•˜์—ฌ ๊ธฐ๋Šฅ์„ ์ž˜ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„๋„ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  • ์ฃผ์„: ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ Javadoc ์Šคํƒ€์ผ์˜ ์ฃผ์„์€ ๋ฉ”์„œ๋“œ์˜ ๋ชฉ์ , ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฐ˜ํ™˜ ๊ฐ’ ๋“ฑ์„ ์„ค๋ช…ํ•˜์—ฌ ์ดํ•ด๋ฅผ ๋•์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ฃผ์„์ด ์„ค๋ช…ํ•˜๋Š” ๋‚ด์šฉ์ด ์ฝ”๋“œ ์ž์ฒด์—์„œ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ถˆํ•„์š”ํ•œ ์ฃผ์„์„ ์ค„์ด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋กœ๊น…: ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๊ฐ€ ์ ์ ˆํ•œ ์ˆ˜์ค€์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ์–ด ๋””๋ฒ„๊น… ๋ฐ ์šด์˜์— ๋„์›€์ด ๋  ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
  • ์ปจ๋ฒ„ํ„ฐ ์‚ฌ์šฉ: ProblemConverter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Entity์™€ DTO ๊ฐ„์˜ ๋ณ€ํ™˜์„ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ Service ๋ ˆ์ด์–ด์˜ ๋ณต์žก์„ฑ์„ ์ค„์ด๊ณ , ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ 

  • updateProblem ๋ฉ”์„œ๋“œ:
    • problemRepository.save(problem) ์ค‘๋ณต ํ˜ธ์ถœ: @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ์œผ๋ฏ€๋กœ, problem.update(updatedProblemDto) ์ดํ›„์— problemRepository.save(problem)์„ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. Spring Data JPA๋Š” ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ์ ์— ๋ณ€๊ฒฝ ๊ฐ์ง€(Dirty Checking)๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ Entity๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์ธ save ํ˜ธ์ถœ์€ ๋ถˆํ•„์š”ํ•˜๋ฉฐ, ์ž ์žฌ์ ์ธ ๋ฌธ์ œ (์˜ˆ: ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ)๋ฅผ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory ๋ฉ”์„œ๋“œ:
    • DTO ๋ณ€ํ™˜ ๋ˆ„๋ฝ: getProblemsByCategory ๋ฉ”์„œ๋“œ๋Š” Problem ์—”ํ‹ฐํ‹ฐ์˜ Page๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. API ๋ ˆ์ด์–ด์—์„œ DTO๋ฅผ ์š”๊ตฌํ•  ๊ฒฝ์šฐ, ์ด ๋ฉ”์„œ๋“œ๋„ DTO๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด DTO๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ:
    • deleteProblem์˜ Exception catch: deleteProblem ๋ฉ”์„œ๋“œ์—์„œ Exception์„ catchํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๊ด‘๋ฒ”์œ„ํ•ฉ๋‹ˆ๋‹ค. ๋ณด๋‹ค ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ (์˜ˆ: DataAccessException)๋ฅผ catchํ•˜๊ณ , ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋งŒ ๋กค๋ฐฑํ•˜๋„๋ก ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
    • RuntimeException ๋ž˜ํ•‘: deleteProblem ๋ฉ”์„œ๋“œ์—์„œ Exception ๋ฐœ์ƒ ์‹œ RuntimeException์œผ๋กœ ๋ž˜ํ•‘ํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ์กด ์˜ˆ์™ธ ์ •๋ณด๋ฅผ ์†์‹ค์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Custom Exception์„ ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” JPA๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” Entity๋ฅผ ์กฐํšŒํ•  ๋•Œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. getAllProblems, getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ Lazy Loading์œผ๋กœ ์„ค์ •๋œ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. @EntityGraph ๋˜๋Š” Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๋„๋ก ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ: updateProblem์—์„œ problemRepository.findById(id)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Entity๋ฅผ ๋กœ๋”ฉํ•œ ํ›„ DTO์˜ ๊ฐ’์„ Entity์— ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Pageable ๊ฐ์ฒด ์‚ฌ์šฉ: ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ pageable ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ์‚ฌ์šฉํ•  ๋•Œ, ํ—ˆ์šฉ๋œ ์ •๋ ฌ ์†์„ฑ ๋ฐ ํŽ˜์ด์ง€ ํฌ๊ธฐ ์ œํ•œ์„ ๋‘๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์•…์˜์ ์ธ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณผ๋„ํ•œ ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ์š”์ฒญํ•˜๊ฑฐ๋‚˜, ์ธ๋ฑ์‹ฑ๋˜์ง€ ์•Š์€ ์†์„ฑ์œผ๋กœ ์ •๋ ฌํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection: ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์ด ์—†์œผ๋ฏ€๋กœ SQL Injection ๊ฐ€๋Šฅ์„ฑ์€ ๋‚ฎ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํ–ฅํ›„ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ SQL Injection์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ Prepared Statement๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, JPA Criteria API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SQL Injection์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ๊ฐ€(Authorization) ์ฒดํฌ: ํ˜„์žฌ ์ฝ”๋“œ์—๋Š” ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ์— ๋Œ€ํ•œ ์ธ๊ฐ€ ์ฒดํฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ ์ ˆํ•œ ์ธ๊ฐ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด (์˜ˆ: ์‚ฌ์šฉ์ž ๊ฐœ์ธ ์ •๋ณด, ๋น„๋ฐ€๋ฒˆํ˜ธ)๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ์ ์ ˆํ•˜๊ฒŒ ์„ค์ •ํ•˜๊ณ , ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๋กœ๊น…ํ•˜๋„๋ก ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. updateProblem ๋ฉ”์„œ๋“œ ์ˆ˜์ •: @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ problemRepository.save(problem) ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
  2. getProblemsByCategory ๋ฉ”์„œ๋“œ: ๋ฐ˜ํ™˜ ํƒ€์ž…์„ Page<ProblemDto>๋กœ ๋ณ€๊ฒฝํ•˜๊ณ , DTO๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  3. deleteProblem ๋ฉ”์„œ๋“œ: Exception catch ๋ฒ”์œ„๋ฅผ ์ขํžˆ๊ณ , Custom Exception์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ ์ •๋ณด๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  4. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: @EntityGraph ๋˜๋Š” Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
  5. ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋ฐฉ์ง€: updateProblem์—์„œ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์ฟผ๋ฆฌ๋ฅผ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.
  6. Pageable ๊ฐ์ฒด ๊ฒ€์ฆ: ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ Pageable ๊ฐ์ฒด์— ๋Œ€ํ•œ ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  7. ์ธ๊ฐ€(Authorization) ์ฒดํฌ: ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ์— ๋Œ€ํ•œ ์ธ๊ฐ€ ์ฒดํฌ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  8. ๋กœ๊ทธ ๋ณด์•ˆ: ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ฉ๋‹ˆ๋‹ค.
  9. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ์˜ˆ์™ธ ์ƒํ™ฉ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ถฉ๋ถ„ํžˆ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„ ์ œ์•ˆ ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/test/java/site/haruhana/www/Service/ProblemServiceTest.java ๋ฆฌ๋ทฐ

src/test/java/site/haruhana/www/Service/ProblemServiceTest.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ, Mockito๋ฅผ ํ™œ์šฉํ•˜์—ฌ Service Layer๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ณ„๋กœ Given-When-Then ํŒจํ„ด์„ ์ž˜ ์ง€์ผœ ๊ฐ€๋…์„ฑ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ๊ณผ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ๊ธ์ •์ ์ธ ๋ถ€๋ถ„:

    • ๊ฐ ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ๋Š” DisplayName ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๋ชฉ์ ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
    • Given-When-Then ํŒจํ„ด์„ ์ž˜ ์ง€์ผœ ํ…Œ์ŠคํŠธ ๋กœ์ง์„ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
    • Mockito๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ์„ ํšจ๊ณผ์ ์œผ๋กœ Mockingํ–ˆ์Šต๋‹ˆ๋‹ค.
    • createTestProblem() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ค‘๋ณต์„ ์ค„์˜€์Šต๋‹ˆ๋‹ค.
    • ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ๋ชฉ์ ์— ๋ถ€ํ•ฉํ•˜๋Š” ์ ์ ˆํ•œ Assertion์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ํ•  ๋ถ€๋ถ„:

    • ProblemDto ๊ฐ์ฒด ์ƒ์„ฑ ์‹œ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์•„ ํ•ญ์ƒ null ๊ฐ’์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ง„ํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ProblemDto expectedDto = new ProblemDto(); ์—์„œ expectedDto ์— ๊ฐ’์„ ์„ธํŒ…ํ•˜์ง€ ์•Š์•„ ํ…Œ์ŠคํŠธ์˜ ์˜๋ฏธ๊ฐ€ ํ‡ด์ƒ‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • updateProblem() ํ…Œ์ŠคํŠธ์—์„œ updateDto์˜ ํ•„๋“œ ๊ฐ’์„ ์„ค์ •ํ•˜์ง€ ์•Š์•„ ์‹ค์ œ ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ๊ฒ€์ฆํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
    • BDDMockito๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ mock ํ–‰์œ„๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐฉ์‹์€ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • getProblemById() ํ…Œ์ŠคํŠธ:

    • ProblemDto expectedDto = new ProblemDto(); ์ƒ์„ฑ๋งŒ ํ•˜๊ณ  expectedDto์˜ ํ•„๋“œ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ProblemService.getProblemById()๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’๊ณผ ๋น„๊ตํ•˜๋Š” assertion์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ์˜ ์˜๋ฏธ๊ฐ€ ํ‡ด์ƒ‰๋ฉ๋‹ˆ๋‹ค. ProblemConverter๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • updateProblem() ํ…Œ์ŠคํŠธ:

    • updateDto์— ๊ฐ’์„ ์„ค์ •ํ•˜์ง€ ์•Š์•„ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฐ’์ด ์—†๋Š” ์ƒํƒœ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ๊ฒ€์ฆํ•˜๋ ค๋ฉด updateDto์— ์—…๋ฐ์ดํŠธํ•  ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , problemService.updateProblem()์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•ด๋‹น ๊ฐ’๊ณผ ๋น„๊ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • existingProblem์„ saveํ•˜๋ฉด ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ๊ฐ€์ง„ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, mock ์„ค์ •์„ ๊ทธ๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š์•„ ์‹ค์ œ ์„œ๋น„์Šค ๋กœ์ง๊ณผ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. given(problemRepository.save(any(Problem.class))).willReturn(existingProblem.toBuilder().title("๋ณ€๊ฒฝ๋œ ์ œ๋ชฉ").build()); ์™€ ๊ฐ™์ด ์‹ค์ œ ์ €์žฅ ํ›„ ๋ณ€๊ฒฝ๋œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋ชจ์‚ฌํ•ด์•ผ ๋” ์ •ํ™•ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ „๋ฐ˜์ ์ธ ๋ฌธ์ œ์ :

    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” assertion์€ ProblemService๊ฐ€ ์‹ค์ œ๋กœ ๊ฐ’์„ ์ œ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์ดˆ์ ์„ ๋งž์ถฐ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ null ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค.
    • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์˜ˆ์™ธ ์ผ€์ด์Šค(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜ค๋ฅ˜ ๋“ฑ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” Mockito๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Repository Layer๋ฅผ Mockingํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ํฌ๊ฒŒ ์—†์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋น„์šฉ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด, ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, ์‹ค์ œ ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ณด์•ˆ ์ทจ์•ฝ์ (SQL Injection, XSS ๋“ฑ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • getProblemById() ํ…Œ์ŠคํŠธ ๊ฐœ์„ :

    @Test
    @DisplayName("ID๋กœ ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•˜๋ฉด ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค")
    void getProblemById() {
        // given: ๋ฌธ์ œ ID์™€ ์˜ˆ์ƒ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ
        Long problemId = 1L;
        Problem problem = createTestProblem();
        ProblemDto expectedDto = ProblemDto.builder() // ProblemDto์— ๊ฐ’์„ ์ฑ„์›Œ๋„ฃ์Šต๋‹ˆ๋‹ค.
            .id(problemId)
            .title("ํ…Œ์ŠคํŠธ ๋ฌธ์ œ")
            .description("ํ…Œ์ŠคํŠธ ์„ค๋ช…")
            .level(1)
            .problemCategory(ProblemCategory.BACKEND)
            .answer("ํ…Œ์ŠคํŠธ ๋‹ต์•ˆ")
            .build();
    
        given(problemRepository.findById(problemId)).willReturn(Optional.of(problem));
        given(problemConverter.toDto(problem)).willReturn(expectedDto);
    
        // when: ID๋กœ ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•˜๋ฉด
        ProblemDto result = problemService.getProblemById(problemId);
    
        // then: ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์ œ DTO๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค
        assertNotNull(result, "๋ฐ˜ํ™˜๋œ ๋ฌธ์ œ๋Š” null์ด ์•„๋‹ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals(expectedDto.getTitle(), result.getTitle(), "๋ฐ˜ํ™˜๋œ ๋ฌธ์ œ ์ œ๋ชฉ์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals(expectedDto.getDescription(), result.getDescription(), "๋ฐ˜ํ™˜๋œ ๋ฌธ์ œ ์„ค๋ช…์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        verify(problemRepository).findById(problemId);
        verify(problemConverter).toDto(problem);
    }
  • updateProblem() ํ…Œ์ŠคํŠธ ๊ฐœ์„ :

    @Test
    @DisplayName("๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์ œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค")
    void updateProblem() {
        // given: ์ˆ˜์ •ํ•  ๋ฌธ์ œ ID์™€ ์—…๋ฐ์ดํŠธํ•  ๋‚ด์šฉ์ด ์ฃผ์–ด์กŒ์„ ๋•Œ
        Long problemId = 1L;
        Problem existingProblem = createTestProblem();
        ProblemDto updateDto = ProblemDto.builder()
            .title("์ˆ˜์ •๋œ ์ œ๋ชฉ")
            .description("์ˆ˜์ •๋œ ์„ค๋ช…")
            .build();
    
        ProblemDto resultDto = ProblemDto.builder()
            .id(problemId)
            .title("์ˆ˜์ •๋œ ์ œ๋ชฉ")
            .description("์ˆ˜์ •๋œ ์„ค๋ช…")
            .level(1)
            .problemCategory(ProblemCategory.BACKEND)
            .answer("ํ…Œ์ŠคํŠธ ๋‹ต์•ˆ")
            .build();
    
        given(problemRepository.findById(problemId)).willReturn(Optional.of(existingProblem));
        given(problemRepository.save(any(Problem.class))).willAnswer(invocation -> invocation.getArgument(0)); // save() ๋ฉ”์„œ๋“œ๊ฐ€ ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •
        given(problemConverter.toDto(any(Problem.class))).willReturn(resultDto);
    
        // when: ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด
        ProblemDto result = problemService.updateProblem(problemId, updateDto);
    
        // then: ์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์ œ๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค
        assertNotNull(result, "์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์ œ๋Š” null์ด ์•„๋‹ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals("์ˆ˜์ •๋œ ์ œ๋ชฉ", result.getTitle(), "์—…๋ฐ์ดํŠธ๋œ ์ œ๋ชฉ์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        assertEquals("์ˆ˜์ •๋œ ์„ค๋ช…", result.getDescription(), "์—…๋ฐ์ดํŠธ๋œ ์„ค๋ช…์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
        verify(problemRepository).findById(problemId);
        verify(problemRepository).save(any(Problem.class));
        verify(problemConverter).toDto(any(Problem.class));
    }
  • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ถ”๊ฐ€:

    • ProblemService์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ ์ผ€์ด์Šค (์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ, ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ์‹คํŒจ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • updateProblem()์—์„œ problemRepository.findById()๊ฐ€ Optional.empty()๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋กœ์ง์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • assertAll ํ™œ์šฉ: ์—ฌ๋Ÿฌ ๊ฐœ์˜ assertion์„ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์—์„œ ์ˆ˜ํ–‰ํ•  ๋•Œ assertAll์„ ์‚ฌ์šฉํ•˜๋ฉด, ๋ชจ๋“  assertion์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•œ ๋ฒˆ์— ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • DTO Builder ํ™œ์šฉ: ProblemDto๋ฅผ ์ƒ์„ฑํ•  ๋•Œ Builder ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ด์ง€๋งŒ, ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ์˜ ์‹ ๋ขฐ๋„๋ฅผ ๋†’์ด๊ณ , ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋ฅผ ์‚ฌ์ „์— ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ Assertion์„ ๊ฐ•ํ™”ํ•˜๊ณ , ๋‹ค์–‘ํ•œ ์˜ˆ์™ธ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

ProblemController.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/controller/ProblemController.java ํŒŒ์ผ์˜ ์ฝ”๋“œ์— ๋Œ€ํ•œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์˜ @RequiredArgsConstructor ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ–ˆ๊ณ , Spring์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ RESTful API๋ฅผ ์ž˜ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. BaseResponse๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต ํ˜•์‹์„ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๋Š” ์ ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฆ„ ๋ช…๋ช… ๊ทœ์น™: ๋ณ€์ˆ˜ ์ด๋ฆ„(์˜ˆ: problemDto, savedProblem)์€ ๋ช…ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„: ์ฝ”๋“œ ์ž์ฒด๋Š” ๋น„๊ต์  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šฐ๋ฏ€๋กœ ์ฃผ์„์ด ํ•„์ˆ˜๋Š” ์•„๋‹ˆ์ง€๋งŒ, ๋ณต์žกํ•œ ๋กœ์ง์ด๋‚˜ ํŠน๋ณ„ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: @PathVariable์˜ ๋ณ€์ˆ˜ ์ด๋ฆ„๊ณผ ๋ฉ”์„œ๋“œ ์ธ์ž์˜ ์ด๋ฆ„(id vs problemId)์ด ์ผ๊ด€๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • createProblem ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: createProblem ๋ฉ”์„œ๋“œ์—์„œ Exception์„ catchํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ ๋„ˆ๋ฌด ๊ด‘๋ฒ”์œ„ํ•œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ (์˜ˆ: ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ ๋ฐœ์ƒํ•˜๋Š” IllegalArgumentException ๋“ฑ)๋ฅผ catchํ•˜์—ฌ ๋” ์ •ํ™•ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • updateProblem: updateProblem์—์„œ @PathVariable๋กœ ๋ฐ›๋Š” id๊ฐ€ ProblemDto์— ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€, ์•„๋‹ˆ๋ฉด ๋ณ„๋„๋กœ ๊ด€๋ฆฌ๋˜๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ProblemDto์— id ํ•„๋“œ๊ฐ€ ์—†๋‹ค๋ฉด, DTO๋ฅผ ์„œ๋น„์Šค ๊ณ„์ธต์— ๋„˜๊ธฐ๊ธฐ ์ „์— id๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • getProblemsByCategory: getProblemsByCategory์—์„œ page.getContent()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ List<Problem>์„ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Page ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ํŽ˜์ด์ง• ์ •๋ณด๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ: problemService.getAllProblems(pageable) ๋ฐ problemService.getProblemsByCategory(category, pageable)์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. JPA๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ Fetch Join์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, QueryDSL์„ ํ™œ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ตœ์ ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • DTO ํ™œ์šฉ: ํ˜„์žฌ Controller์—์„œ Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค (getProblemsByCategory). Entity๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ ๋ฐ€์ ‘ํ•˜๊ฒŒ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, API ์‘๋‹ต์— Entity๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค. DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ๋ฌธ์ œ ๋ชฉ๋ก ์กฐํšŒ (getAllProblems, getProblemsByCategory) ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ์บ์‹ฑ ์ „๋žต์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: createProblem ๋ฐ updateProblem์—์„œ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์ด null์ด ์•„๋‹Œ์ง€, ๋‚ด์šฉ์ด ๋„ˆ๋ฌด ๊ธธ์ง€ ์•Š์€์ง€ ๋“ฑ์„ ๊ฒ€์‚ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. @Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ Bean Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์ธ๊ฐ€/์ธ์ฆ: ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” API ์ ‘๊ทผ ๊ถŒํ•œ์— ๋Œ€ํ•œ ๊ณ ๋ ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ API๋Š” ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ธ๊ฐ€/์ธ์ฆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • SQL Injection: DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์„ ๋ฐ”์ธ๋”ฉํ•˜๋ฏ€๋กœ SQL Injection์˜ ์œ„ํ—˜์€ ๋‚ฎ์ง€๋งŒ, ๋งŒ์•ฝ ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ํ†ตํ•ด SQL Injection์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐœ์„ : createProblem์—์„œ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ๋ฅผ catchํ•˜๊ณ , ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  2. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: @Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ Bean Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  3. DTO ํ™œ์šฉ ๋ฐ Entity ๋…ธ์ถœ ๋ฐฉ์ง€: API ์‘๋‹ต์— Entity๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ , DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. getProblemsByCategory์—์„œ Problem Entity ๋Œ€์‹  ProblemDto๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
  4. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: JPA Fetch Join ๋˜๋Š” QueryDSL์„ ์‚ฌ์šฉํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
  5. ์ธ๊ฐ€/์ธ์ฆ ๊ตฌํ˜„: Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API ์ ‘๊ทผ ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ API๋Š” ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.
  6. ์ผ๊ด€์„ฑ ์œ ์ง€: @PathVariable์˜ ๋ณ€์ˆ˜ ์ด๋ฆ„๊ณผ ๋ฉ”์„œ๋“œ ์ธ์ž ์ด๋ฆ„์„ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: problemId).
  7. getProblemsByCategory ์ˆ˜์ •: getProblemsByCategory์—์„œ Page<Problem> ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ํŽ˜์ด์ง• ์ •๋ณด๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Entity ๋Œ€์‹  DTO๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
  8. BaseResponse ๊ฐœ์„ : BaseResponse์— ์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์—๋Ÿฌ๋ฅผ ๋” ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  9. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: Controller์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค.

์ˆ˜์ • ์˜ˆ์‹œ (์ผ๋ถ€):

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/problems")
public class ProblemController {

    private final ProblemService problemService;

    @GetMapping
    public BaseResponse<Page<ProblemDto>> getAllProblems(Pageable pageable) {
        Page<ProblemDto> problems = problemService.getAllProblems(pageable);
        return problems.isEmpty()
                ? BaseResponse.onSuccess("์กฐํšŒ๋œ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", problems)
                : BaseResponse.onSuccess("๋ฌธ์ œ ๋ชฉ๋ก ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค", problems);
    }

    @GetMapping("/{problemId}")
    public BaseResponse<ProblemDto> getProblemById(@PathVariable Long problemId) {
        try {
            ProblemDto problem = problemService.getProblemById(problemId);
            return BaseResponse.onSuccess("๋ฌธ์ œ ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค", problem);
        } catch (ProblemNotFoundException e) {
            return BaseResponse.onNotFound("๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค");
        }
    }

    @PostMapping
    public BaseResponse<ProblemDto> createProblem(@Valid @RequestBody ProblemDto problemDto) { // @Valid ์ถ”๊ฐ€
        try {
            ProblemDto savedProblem = problemService.createProblem(problemDto);
            return BaseResponse.onCreate("๋ฌธ์ œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", savedProblem);
        } catch (IllegalArgumentException e) { // ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
            return BaseResponse.onBadRequest("๋ฌธ์ œ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: " + e.getMessage());
        } catch (Exception e) {
            return BaseResponse.onBadRequest("๋ฌธ์ œ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: " + e.getMessage());
        }
    }

    @PutMapping("/{problemId}") //PathVariable ์ด๋ฆ„ ๋ณ€๊ฒฝ
    public BaseResponse<ProblemDto> updateProblem(@PathVariable Long problemId, @Valid @RequestBody ProblemDto problemDto) { // @Valid ์ถ”๊ฐ€
        try {
            ProblemDto updatedProblem = problemService.updateProblem(problemId, problemDto);
            return BaseResponse.onSuccess("๋ฌธ์ œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค", updatedProblem);
        } catch (ProblemNotFoundException e) {
            return BaseResponse.onNotFound("์ˆ˜์ •ํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค");
        }
    }

    @DeleteMapping("/{problemId}") //PathVariable ์ด๋ฆ„ ๋ณ€๊ฒฝ
    public BaseResponse<Void> deleteProblem(@PathVariable Long problemId) {
        try {
            problemService.deleteProblem(problemId);
            return BaseResponse.onSuccess("๋ฌธ์ œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", null);
        } catch (ProblemNotFoundException e) {
            return BaseResponse.onNotFound("์‚ญ์ œํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค");
        }
    }

    @GetMapping("/category")
    public BaseResponse<Page<ProblemDto>> getProblemsByCategory( // ๋ฐ˜ํ™˜ ํƒ€์ž… ๋ณ€๊ฒฝ
            @RequestParam("category") ProblemCategory category,
            @PageableDefault Pageable pageable) {

        Page<ProblemDto> problemPage = problemService.getProblemsByCategory(category, pageable);

        if (problemPage.isEmpty()) {
            return BaseResponse.onSuccess(
                    String.format("%s ์นดํ…Œ๊ณ ๋ฆฌ์— ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", category),
                    problemPage // ๋ณ€๊ฒฝ๋œ problemPage ๋ฐ˜ํ™˜
            );
        }

        return BaseResponse.onSuccess(
                String.format("%s ์นดํ…Œ๊ณ ๋ฆฌ ๋ฌธ์ œ ์กฐํšŒ ์„ฑ๊ณต (์ด %d๊ฐœ)",
                        category, problemPage.getTotalElements()),
                problemPage // ๋ณ€๊ฒฝ๋œ problemPage ๋ฐ˜ํ™˜
        );
    }
}

์ด ๋ฆฌ๋ทฐ๊ฐ€ ์ฝ”๋“œ ํ’ˆ์งˆ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/entity/Problem.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/entity/Problem.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ src/main/java/site/haruhana/www/entity/Problem.java ์ฝ”๋“œ ์กฐ๊ฐ์„ ๊ฒ€ํ† ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์ „์ฒด ํŒŒ์ผ ๋‚ด์šฉ์ด ์ œ๊ณต๋˜์ง€ ์•Š์•„ ์ œํ•œ์ ์ธ ๋ฆฌ๋ทฐ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š” ์ ์„ ๊ฐ์•ˆํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋ณ€์ˆ˜๋ช…์ด ๋ช…ํ™•ํ•˜๊ณ , update ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ์ด ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • ํ’ˆ์งˆ: ํ˜„์žฌ ์ œ๊ณต๋œ ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” ์ฝ”๋“œ ํ’ˆ์งˆ์„ ์™„๋ฒฝํ•˜๊ฒŒ ํ‰๊ฐ€ํ•˜๊ธฐ ์–ด๋ ต์ง€๋งŒ, Lombok์˜ @Builder๋ฅผ ์‚ฌ์šฉํ•œ ์ ์€ ๊ฐ์ฒด ์ƒ์„ฑ ํŒจํ„ด์„ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
  • ์•„์‰ฌ์šด ์ :
    • ์ฃผ์„์ด ๋งค์šฐ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ํด๋ž˜์Šค ๋ ˆ๋ฒจ, ํ•„๋“œ ๋ ˆ๋ฒจ, ๋ฉ”์†Œ๋“œ ๋ ˆ๋ฒจ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ๋”์šฑ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, update ๋ฉ”์„œ๋“œ์˜ ์ฃผ์„์€ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…๋งŒ ์žˆ๊ณ  ๋ฉ”์„œ๋“œ์˜ ๋ชฉ์ (Problem entity ์—…๋ฐ์ดํŠธ)์— ๋Œ€ํ•œ ์„ค๋ช…์ด ์—†์Šต๋‹ˆ๋‹ค.
    • DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ๊ดœ์ฐฎ์€์ง€ ์ „์ฒด์ ์ธ ๋งฅ๋ฝ์„ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. Problem๊ณผ ProblemDto์˜ ๊ด€๊ณ„๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๊ณ , DTO์˜ ์—ญํ• ์„ ๋ช…ํ™•ํžˆ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: updatedProblemDto๊ฐ€ null์ธ ๊ฒฝ์šฐ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. update ๋ฉ”์„œ๋“œ ์‹œ์ž‘ ์‹œ null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•„๋“œ ๊ฒ€์ฆ ๋ถ€์กฑ: updatedProblemDto์˜ ํ•„๋“œ ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, title์ด ๋น„์–ด์žˆ๊ฑฐ๋‚˜, level์ด ์œ ํšจํ•œ ๋ฒ”์œ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ์œ„ํ•ด ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • DTO ํ•„๋“œ์™€ Entity ํ•„๋“œ ๋ถˆ์ผ์น˜: ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” DTO์˜ ํ•„๋“œ ์ด๋ฆ„๊ณผ Entity์˜ ํ•„๋“œ ์ด๋ฆ„์ด ๋™์ผํ•˜๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ•„๋“œ ์ด๋ฆ„์ด ๋‹ค๋ฅด๊ฑฐ๋‚˜ ๋ณ€ํ™˜์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ๋ณ„๋„์˜ ๋งคํ•‘ ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: ModelMapper ์‚ฌ์šฉ)
  • ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ: problemCategory๋ฅผ ์ง์ ‘ ํ• ๋‹นํ•˜๋Š” ๋ฐฉ์‹์€ ๊ฐ์ฒด ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ๋‹จ์ˆœํ•œ ๊ฒฝ์šฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ problemCategory๊ฐ€ ๋ณ„๋„์˜ Entity์ด๊ณ  ๋ณต์žกํ•œ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„๋‹ค๋ฉด, ํ•ด๋‹น Entity๋ฅผ ์กฐํšŒํ•˜์—ฌ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ์—…๋ฐ์ดํŠธ ๋กœ์ง ์ตœ์ ํ™”: ๋งŒ์•ฝ ์—…๋ฐ์ดํŠธ๋˜๋Š” ํ•„๋“œ๊ฐ€ ์ผ๋ถ€์— ๋ถˆ๊ณผํ•˜๋‹ค๋ฉด, ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋Œ€์‹  ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ๋กœ์ง์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—…๋ฐ์ดํŠธ๋ฅผ ์ค„์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถˆ๋ณ€ ๊ฐ์ฒด ํ™œ์šฉ: Problem ๊ฐ์ฒด๊ฐ€ ๋ถˆ๋ณ€(immutable) ๊ฐ์ฒด๋ผ๋ฉด, ์—…๋ฐ์ดํŠธ ์‹œ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋™์‹œ์„ฑ ํ™˜๊ฒฝ์—์„œ ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ•˜๊ณ  ์บ์‹ฑ ํšจ์œจ์„ ๋†’์ด๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ์ด ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection ๊ฐ€๋Šฅ์„ฑ: updatedProblemDto์˜ ๊ฐ’์„ ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ๊ฒฝ์šฐ, SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PreparedStatement๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ORM ํ”„๋ ˆ์ž„์›Œํฌ(JPA ๋“ฑ)๋ฅผ ํ†ตํ•ด ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์‚ฌ์šฉํ•˜์—ฌ SQL Injection์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • XSS ๊ณต๊ฒฉ ๊ฐ€๋Šฅ์„ฑ: title์ด๋‚˜ description์— HTML ํƒœ๊ทธ๋‚˜ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, XSS ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ๋Œ€๋น„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. HTML Escape ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(์˜ˆ: OWASP Java HTML Sanitizer)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์„ sanitizeํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: update ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ด€๋ฆฌ์ž๋งŒ ๋ฌธ์ œ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ์•ฝ์„ ๊ฑธ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. NullPointerException ๋ฐฉ์ง€: update ๋ฉ”์„œ๋“œ ์‹œ์ž‘ ์‹œ updatedProblemDto๊ฐ€ null์ธ์ง€ ํ™•์ธํ•˜๊ณ , IllegalArgumentException ๋“ฑ์˜ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    public void update(ProblemDto updatedProblemDto) {
        if (updatedProblemDto == null) {
            throw new IllegalArgumentException("updatedProblemDto cannot be null");
        }
        // ... ๊ธฐ์กด ๋กœ์ง
    }
  2. ํ•„๋“œ ๊ฒ€์ฆ ์ถ”๊ฐ€: DTO์˜ ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. Bean Validation API (JSR-303)๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํŽธ๋ฆฌํ•˜๊ฒŒ ๊ฒ€์ฆ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.Size;
    
    @Data
    public class ProblemDto {
        @NotBlank(message = "Title cannot be blank")
        @Size(max = 255, message = "Title cannot be longer than 255 characters")
        private String title;
        // ... ๋‹ค๋ฅธ ํ•„๋“œ
    }

    Entity์˜ update ๋ฉ”์„œ๋“œ์—์„œ๋„ ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๊ณ , ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
    (Hibernate Validator ์‚ฌ์šฉ ์˜ˆ์‹œ)

    import javax.validation.Validator;
    import javax.validation.ConstraintViolation;
    import java.util.Set;
    
    public void update(ProblemDto updatedProblemDto) {
        // ... null check
    
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<ProblemDto>> violations = validator.validate(updatedProblemDto);
    
        if (!violations.isEmpty()) {
            throw new IllegalArgumentException("Invalid ProblemDto: " + violations);
        }
    
        this.title = updatedProblemDto.getTitle();
        // ...
    }
  3. ORM ํ”„๋ ˆ์ž„์›Œํฌ ์‚ฌ์šฉ: JPA์™€ ๊ฐ™์€ ORM ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ์ฝ”๋“œ๋ฅผ ๊ฐ„์†Œํ™”ํ•˜๊ณ , SQL Injection ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  4. ModelMapper ์‚ฌ์šฉ: DTO์™€ Entity ๊ฐ„์˜ ํ•„๋“œ ๋งคํ•‘์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ModelMapper์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํ•„๋“œ ์ด๋ฆ„์ด ๋‹ค๋ฅด๊ฑฐ๋‚˜ ๋ณ€ํ™˜์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋„ ์‰ฝ๊ฒŒ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  5. ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ ์—…๋ฐ์ดํŠธ ๋ฐฉ์‹ ๋ณ€๊ฒฝ: problemCategory๊ฐ€ Entity์ธ ๊ฒฝ์šฐ, ID๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์กฐํšŒํ•˜์—ฌ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    // ProblemCategoryRepository๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •
    public void update(ProblemDto updatedProblemDto, ProblemCategoryRepository problemCategoryRepository) {
        // ... null check
    
        ProblemCategory problemCategory = problemCategoryRepository.findById(updatedProblemDto.getProblemCategoryId())
                .orElseThrow(() -> new IllegalArgumentException("Invalid problem category id"));
    
        this.problemCategory = problemCategory;
        // ...
    }
  6. ๊ถŒํ•œ ๊ฒ€์‚ฌ: update ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ณณ์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ํ™•์ธํ•˜๊ณ , ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. Spring Security์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  7. ๋ถˆ๋ณ€ ๊ฐ์ฒด ๊ณ ๋ ค: Problem๊ฐ์ฒด๋ฅผ ๋ถˆ๋ณ€์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ทธ๋ ‡๊ฒŒ ํ•œ๋‹ค๋ฉด, update ๋ฉ”์„œ๋“œ๋Š” ์ƒˆ๋กœ์šด Problem ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  8. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: update ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์ •ํ™•์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ œ๊ณต๋œ ์ฝ”๋“œ ์กฐ๊ฐ์€ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์€ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ, ๋ณด์•ˆ, ์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ ๊ฐœ์„ ํ•  ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ์ œ์•ˆ๋“ค์„ ์ ์šฉํ•˜์—ฌ ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์„ ๋†’์ด๊ณ  ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์ฝ”๋“œ์˜ ๋งฅ๋ฝ๊ณผ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์—ฌ ์ ์ ˆํ•œ ๊ฐœ์„  ๋ฐฉ์•ˆ์„ ์„ ํƒํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

ProblemRepository.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/repository/ProblemRepository.java ํŒŒ์ผ์˜ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์žฅ์ :
    • ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
    • Spring Data JPA์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • findByProblemCategory ๋ฉ”์„œ๋“œ๋Š” ๋„ค์ด๋ฐ ๊ทœ์น™์„ ์ž˜ ์ง€์ผœ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :
    • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜์—ฌ ํŠน๋ณ„ํžˆ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์€ ์—†์Šต๋‹ˆ๋‹ค. ์ข€ ๋” ๋ณต์žกํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ด ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ, ์ฃผ์„์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • ๋ฌธ์ œ์ :
    • ํ˜„์žฌ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ๋ˆˆ์— ๋„๋Š” ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ ์€ ์—†์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค ์‚ฌํ•ญ:
    • ProblemCategory๊ฐ€ null์ผ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ProblemCategory๊ฐ€ null์ธ ๊ฒฝ์šฐ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋˜์–ด์•ผ ํ•˜๋Š”์ง€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ชจ๋“  Problem์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์ด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • Pageable ๊ฐ์ฒด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑ๋˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Pageable ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ๋ฒจ์—์„œ ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜์—ฌ ์„ฑ๋Šฅ ๊ฐœ์„ ์˜ ์—ฌ์ง€๊ฐ€ ํฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ž ์žฌ์ ์ธ ๊ฐœ์„ ์ :
    • ์ธ๋ฑ์Šค: Problem.problemCategory ํ•„๋“œ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด findByProblemCategory ๋ฉ”์„œ๋“œ์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Entity ํด๋ž˜์Šค(Problem)์—์„œ @Index ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ฟผ๋ฆฌ ์ตœ์ ํ™”: ๋งŒ์•ฝ ๋ณต์žกํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์ด ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ, @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPQL ๋˜๋Š” Native SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    • ์บ์‹ฑ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ProblemCategory์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ์บ์‹ฑ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ˜„์žฌ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ์ง์ ‘์ ์ธ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค ์‚ฌํ•ญ:
    • Pageable ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ๋ฒจ์—์„œ ํ—ˆ์šฉ๋˜๋Š” ํŽ˜์ด์ง€ ํฌ๊ธฐ ๋ฐ ์ •๋ ฌ ๋ฐฉํ–ฅ์„ ์ œํ•œํ•˜์—ฌ ์˜๋„ํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ ๋…ธ์ถœ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๋งŒ์•ฝ Problem ์—”ํ‹ฐํ‹ฐ์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด, ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ฐ•ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ถ”๊ฐ€ (์„ ํƒ ์‚ฌํ•ญ): ProblemCategory๊ฐ€ null์ผ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    Page<Problem> findByProblemCategory(ProblemCategory category, Pageable pageable);
    
    // ๋งŒ์•ฝ category๊ฐ€ null์ด๋ฉด ๋ชจ๋“  Problem์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋ณ€๊ฒฝ
    default Page<Problem> findByProblemCategoryOrAll(ProblemCategory category, Pageable pageable) {
        if (category == null) {
            return findAll(pageable);
        }
        return findByProblemCategory(category, pageable);
    }
  • ์ธ๋ฑ์Šค ์ถ”๊ฐ€ (๊ถŒ์žฅ): Problem ์—”ํ‹ฐํ‹ฐ์— problemCategory ํ•„๋“œ์— ๋Œ€ํ•œ ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. (Entity ํด๋ž˜์Šค ์ˆ˜์ • ํ•„์š”)

    import javax.persistence.*;
    
    @Entity
    @Table(indexes = @Index(name = "idx_problem_category", columnList = "problemCategory"))
    public class Problem {
        // ...
        @Column(name = "problemCategory")
        private String problemCategory; // ์‹ค์ œ ํ•„๋“œ๋ช…์— ๋งž๊ฒŒ ์ˆ˜์ •
        // ...
    }

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ํ›Œ๋ฅญํ•˜๋ฉฐ, ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์œ„์— ์ œ์‹œ๋œ ์ œ์•ˆ์€ ํ˜„์žฌ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์— ๋Œ€๋น„ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ ์ƒํ™ฉ์— ๋งž๊ฒŒ ํ•„์š”ํ•œ ๊ฐœ์„  ์‚ฌํ•ญ์„ ์„ ํƒ์ ์œผ๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ํŠน๋ณ„ํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ๊ณผ ๊ณ ๋ คํ•ด์•ผ ํ•  ์‚ฌํ•ญ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๋งค์šฐ ์ข‹์Œ: ์ฝ”๋“œ๋Š” ๋งค์šฐ ์งง๊ณ  ๊ฐ„๊ฒฐํ•˜๋ฉฐ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋ช…๋ช… ๊ทœ์น™๋„ ์ž˜ ์ง€์ผœ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ProblemNotFoundException์€ ์˜ˆ์™ธ์˜ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • ์—†์Œ: ์ œ๊ณต๋œ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ ์ด ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค ์‚ฌํ•ญ:
    • Checked vs. Unchecked Exception: RuntimeException์„ ์ƒ์†๋ฐ›์•˜์œผ๋ฏ€๋กœ unchecked exception์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ์ž๊ฐ€ ๋ฐ˜๋“œ์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ธ์ง€ (์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ), ์•„๋‹ˆ๋ฉด ์‹œ์Šคํ…œ์˜ ์ผ์‹œ์ ์ธ ๋ฌธ์ œ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ๊ทธ๋ƒฅ ๋กœ๊น…๋งŒ ํ•ด๋„ ๋˜๋Š” ์ƒํ™ฉ์ธ์ง€์— ๋”ฐ๋ผ checked exception (Exception ์ƒ์†)์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Checked exception์€ ์ปดํŒŒ์ผ ์‹œ์ ์— ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ•์ œํ•˜๋ฏ€๋กœ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ˆ„๋ฝ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฝ”๋“œ๊ฐ€ ์žฅํ™ฉํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Unchecked exception์€ ํŽธ๋ฆฌํ•˜์ง€๋งŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ˆ„๋ฝ์˜ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์˜ˆ์™ธ ์ปจํ…์ŠคํŠธ ์ •๋ณด: ํ˜„์žฌ๋Š” ๋ฉ”์‹œ์ง€๋งŒ ์ „๋‹ฌํ•˜์ง€๋งŒ, ๋ฌธ์ œ ID์™€ ๊ฐ™์ด ์˜ˆ์™ธ ๋ฐœ์ƒ ์ƒํ™ฉ์„ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋ฅผ ๋‹ด์„ ์ˆ˜ ์žˆ๋„๋ก ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ํ•ด๋‹น ์‚ฌํ•ญ ์—†์Œ: ์˜ˆ์™ธ ํด๋ž˜์Šค ์ž์ฒด์˜ ์„ฑ๋Šฅ์€ ํฌ๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์—†์Œ: ์ œ๊ณต๋œ ์ฝ”๋“œ ์ž์ฒด๋Š” ๋ณด์•ˆ์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค ์‚ฌํ•ญ: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด๋‚˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ์˜ ์ •๋ณด๊ฐ€ ๋ฉ”์‹œ์ง€์— ๋…ธ์ถœ๋˜๋ฉด ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • ์ƒ์„ฑ์ž ์˜ค๋ฒ„๋กœ๋”ฉ: ๋ฌธ์ œ ID๋‚˜ ๋ฌธ์ œ์™€ ๊ด€๋ จ๋œ ๋‹ค๋ฅธ ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ƒ์„ฑ์ž๋ฅผ ์˜ค๋ฒ„๋กœ๋”ฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    package site.haruhana.www.exception;
    
    public class ProblemNotFoundException extends RuntimeException {
    
        private Long problemId;
    
        public ProblemNotFoundException(String message) {
            super(message);
        }
    
        public ProblemNotFoundException(Long problemId, String message) {
            super(message);
            this.problemId = problemId;
        }
    
        public Long getProblemId() {
            return problemId;
        }
    }
  • ์˜ˆ์™ธ ๋กœ๊น…: ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋กœ๊น…์„ ํ†ตํ•ด ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๊ฐ์ฒด์˜ printStackTrace() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ๊ธฐ๋กํ•˜๋ฉด ๋ฌธ์ œ ํ•ด๊ฒฐ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

    try {
        // ... ๋ฌธ์ œ ๊ฒ€์ƒ‰ ๋กœ์ง ...
    } catch (ProblemNotFoundException e) {
        logger.error("Problem not found: problemId={}, message={}", e.getProblemId(), e.getMessage(), e);
        throw e; // ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋˜์ ธ์„œ ์ƒ์œ„ ๋ ˆ์ด์–ด์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•จ
    }
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต: ProblemNotFoundException์ด ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ๊ณผ ์˜ˆ์™ธ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ์ „์ฒด์ ์ธ ์‹œ์Šคํ…œ ์„ค๊ณ„๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ฑฐ๋‚˜, ๊ธฐ๋ณธ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

ProblemNotFoundException ํด๋ž˜์Šค๋Š” ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ฝ”๋“œ ํ’ˆ์งˆ๋„ ์ข‹์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต์„ ๊ตฌ์ฒดํ™”ํ•˜๊ณ , ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์˜ˆ์™ธ ๊ฐ์ฒด์— ๋‹ด๋„๋ก ์ˆ˜์ •ํ•˜๋ฉด ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ checked/unchecked exception ์„ ํƒ๊ณผ ์˜ˆ์™ธ ๋กœ๊น…์€ ์ค‘์š”ํ•œ ๊ณ ๋ ค ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

ProblemDto.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ src/main/java/site/haruhana/www/dto/ProblemDto.java ํŒŒ์ผ์˜ ์ฝ”๋“œ๋ฅผ ๋ฉด๋ฐ€ํžˆ ๊ฒ€ํ† ํ•˜๊ณ  ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์ธ ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ๋Œ€์ฒด์ ์œผ๋กœ ๊น”๋”ํ•˜๊ณ  Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ boilerplate ์ฝ”๋“œ๋ฅผ ์ค„์ธ ์ ์€ ์ข‹์Šต๋‹ˆ๋‹ค. DTO์˜ ์—ญํ• ์— ์ถฉ์‹คํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ๋ณ€์ˆ˜๋ช…์€ ์ง๊ด€์ ์ด๋ฉฐ, Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ์˜ ๊ธธ์ด๋ฅผ ์ค„์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: Lombok ์–ด๋…ธํ…Œ์ด์…˜์˜ ์ˆœ์„œ(Getter, Builder, NoArgsConstructor, AllArgsConstructor)๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ๋Š” ์ƒ์„ฑ์ž ๊ด€๋ จ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ฌถ๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • Problem ๊ฐ์ฒด ๊ธฐ๋ฐ˜ ์ƒ์„ฑ์ž ๋ฌธ์ œ: Problem ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ ProblemDto๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ƒ์„ฑ์ž์—์„œ this.problemCategory = getProblemCategory(); ๋ถ€๋ถ„์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Problem ๊ฐ์ฒด๋กœ๋ถ€ํ„ฐ problemCategory๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋Š”๋ฐ, getProblemCategory() ๋ฉ”์„œ๋“œ๋Š” ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•„๋งˆ๋„ problem.getProblemCategory()๋ฅผ ์˜๋„ํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ problemCategory๊ฐ€ null์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค๋ฉด, null ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” Problem ์—”ํ‹ฐํ‹ฐ๋กœ๋ถ€ํ„ฐ ProblemDto๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋นˆ๋ฒˆํ•˜๊ฒŒ ProblemDto๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์ง์ ‘ ํ•„๋“œ๊ฐ’์„ ๋ณต์‚ฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์•ฝ๊ฐ„ ์ €ํ•˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ DTO ์ƒ์„ฑ ๋นˆ๋„๊ฐ€ ๋†’์ง€ ์•Š๋‹ค๋ฉด ํฌ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋ผ๋ฉด, ProblemMapper์™€ ๊ฐ™์€ Mapper ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ ๋งคํ•‘์„ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ex. MapStruct)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: answer ํ•„๋“œ๊ฐ€ DTO์— ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋Š” ์ ์ด ์šฐ๋ ค๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์— ๋ฌธ์ œ๋ฅผ ํ’€๊ธฐ ์œ„ํ•œ ์ •๋‹ต์„ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ์‹œํ‚ค๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ƒ ๋งค์šฐ ์ทจ์•ฝํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ๋ฅผ ํ’€๊ธฐ ์œ„ํ•œ API ์‘๋‹ต์—์„œ answer ํ•„๋“œ๋ฅผ ์ œ์™ธํ•˜๊ฑฐ๋‚˜, ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ answer๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๋ณด์•ˆ: ๋งŒ์•ฝ ProblemDto๊ฐ€ ์™ธ๋ถ€์—์„œ ๋ฐ›๊ฑฐ๋‚˜ ์™ธ๋ถ€๋กœ ์ „์†ก๋˜๋Š” ๊ฒฝ์šฐ (์˜ˆ: JSON ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”), ์ž ์žฌ์ ์ธ ๋ณด์•ˆ ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์—ญ์ง๋ ฌํ™” ๊ณผ์ •์—์„œ ์•…์˜์ ์ธ ๋ฐ์ดํ„ฐ๊ฐ€ ์ฃผ์ž…๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ๋ฐฉ์–ด์ฑ…์œผ๋กœ, DTO ํด๋ž˜์Šค์— validation ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ์—ญ์ง๋ ฌํ™” ์‹œ์— ์˜ˆ์ƒ๋˜๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…๊ณผ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. Problem ๊ธฐ๋ฐ˜ ์ƒ์„ฑ์ž ์ˆ˜์ •:

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = problem.getDescription();
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory(); // ์ˆ˜์ •
    }
    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = problem.getDescription();
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory() != null ? problem.getProblemCategory() : null; // ์ˆ˜์ • (null ์ฒ˜๋ฆฌ)
    }
  2. answer ํ•„๋“œ ๋ณด์•ˆ: ํด๋ผ์ด์–ธํŠธ์— answer ํ•„๋“œ๋ฅผ ์ง์ ‘ ๋…ธ์ถœ์‹œํ‚ค์ง€ ์•Š๋„๋ก API ์‘๋‹ต์—์„œ ์ œ์™ธํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ API๋ฅผ ํ†ตํ•ด ๊ถŒํ•œ ์žˆ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ์ œ๊ณตํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

  3. Mapper ํด๋ž˜์Šค ๋„์ž… (์„ ํƒ ์‚ฌํ•ญ): ์—”ํ‹ฐํ‹ฐ์™€ DTO ๊ฐ„์˜ ๋งคํ•‘์ด ๋ณต์žกํ•˜๊ฑฐ๋‚˜ ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ, MapStruct์™€ ๊ฐ™์€ Mapper ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งคํ•‘ ๋กœ์ง์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.

  4. Validation ์ถ”๊ฐ€ (์„ ํƒ ์‚ฌํ•ญ): ํ•„์š”ํ•œ ๊ฒฝ์šฐ, DTO์— Bean Validation ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, @NotBlank ๋˜๋Š” @Size ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ์˜ ์ œ์•ฝ ์กฐ๊ฑด์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  5. Lombok ์–ด๋…ธํ…Œ์ด์…˜ ์ˆœ์„œ ์กฐ์ • (์„ ํƒ ์‚ฌํ•ญ):

    @Getter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ProblemDto {
       // ...
    }

    ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ (๊ฐœ์ธ์ ์ธ ์„ ํ˜ธ๋„)

    @Getter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ProblemDto {
       // ...
    }

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊น”๋”ํ•˜๊ณ  ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, Problem ๊ธฐ๋ฐ˜ ์ƒ์„ฑ์ž์˜ ์˜ค๋ฅ˜์™€ answer ํ•„๋“œ ๋…ธ์ถœ ๋ฌธ์ œ๋Š” ๋ฐ˜๋“œ์‹œ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„ฑ๋Šฅ ๊ฐœ์„ ๊ณผ validation ์ถ”๊ฐ€๋Š” ํ•„์š”์— ๋”ฐ๋ผ ์„ ํƒ์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ์œผ์‹œ๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”.

Copy link

src/test/java/site/haruhana/www/Service/ProblemServiceTest.java ๋ฆฌ๋ทฐ

ProblemServiceTest.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ ์ œ๊ณต๋œ src/test/java/site/haruhana/www/Service/ProblemServiceTest.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ์ „์ฒด์ ์œผ๋กœ ๊ฐ€๋…์„ฑ์ด ๋†’๊ณ , ๊ฐ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.
  • BDD ์Šคํƒ€์ผ: given-when-then ํŒจํ„ด์„ ์ž˜ ํ™œ์šฉํ•˜์—ฌ ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • Display Name ํ™œ์šฉ: @DisplayName ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ ์ ˆํ•œ Mocking: @Mock ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ mock ๊ฐ์ฒด๋กœ ๋Œ€์ฒดํ•˜๊ณ , @InjectMocks๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๊ฐ์ฒด์— ์ฃผ์ž…ํ•˜์—ฌ ๊ฒฉ๋ฆฌ๋œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: createTestProblem() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ณ , ํ…Œ์ŠคํŠธ ์ „๋ฐ˜์— ๊ฑธ์ณ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • updateProblem ํ…Œ์ŠคํŠธ์˜ ๊ฒ€์ฆ ๋ถ€์กฑ: updateProblem ํ…Œ์ŠคํŠธ์—์„œ updateDto์— ์–ด๋–ค ๊ฐ’์„ ์„ค์ •ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  resultDto์— ์–ด๋–ค ๊ฐ’์ด ๋ฐ˜ํ™˜๋  ๊ฒƒ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค. resultDto๋ฅผ ๊ทธ๋ƒฅ ๋นˆ ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•ด๋†“๊ณ  ๊ฒ€์ฆ์„ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ์˜๋ฏธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์—…๋ฐ์ดํŠธ ๋กœ์ง์ด ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด updateDto์— ๊ฐ’์„ ๋„ฃ๊ณ , ์—…๋ฐ์ดํŠธ๋œ resultDto์˜ ๋‚ด์šฉ์„ existingProblem์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์˜ˆ์ƒ๋˜๋Š” ๊ฐ’๊ณผ ๋น„๊ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • getAllProblems ํ…Œ์ŠคํŠธ์˜ DTO ๋ณ€ํ™˜ ๋ˆ„๋ฝ: problemRepository.findAll(pageRequest)๋Š” Page<Problem>์„ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, problemService.getAllProblems(pageRequest)๋Š” Page<ProblemDto>๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ problemConverter๋ฅผ ์ด์šฉํ•œ DTO ๋ณ€ํ™˜ ๊ณผ์ •์„ mock ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์—, ์‹ค์ œ ์„œ๋น„์Šค ์ฝ”๋“œ์™€ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์‚ฌ์ด์— ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • createTestProblem ๋ฉ”์„œ๋“œ: ํ˜„์žฌ ๋ฌธ์ œ ID๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ID๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŠน์ • ๋กœ์ง์ด ์ˆ˜ํ–‰๋œ๋‹ค๋ฉด, ํ•ด๋‹น ๋ถ€๋ถ„์„ ๊ณ ๋ คํ•˜์—ฌ createTestProblem ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: problem.setId(1L);)
  • Converter Mocking: problemConverter.toDto๋ฅผ Mocking ํ–ˆ์ง€๋งŒ, ์‹ค์ œ ๋กœ์ง์ด DTO ๋ณ€ํ™˜์„ ์ œ๋Œ€๋กœ ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. Converter์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์— ์ง‘์ค‘ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํŠน๋ณ„ํ•œ ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋งŒ์•ฝ ์‹ค์ œ ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ์ฟผ๋ฆฌ ํŠœ๋‹์ด๋‚˜ ์บ์‹ฑ ์ „๋žต์„ ๊ณ ๋ คํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ: createTestProblem() ๋ฉ”์„œ๋“œ๋Š” ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ์ด ํฌ๋‹ค๋ฉด, static final ๋ณ€์ˆ˜๋กœ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋‹ค๋งŒ, ์ด ๊ฒฝ์šฐ์—๋Š” ๊ฐ์ฒด์˜ ๋ถˆ๋ณ€์„ฑ์„ ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ด๋ฏ€๋กœ, ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์‹ค์ œ ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ์—๋Š” XSS, SQL Injection ๋“ฑ์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์— ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • updateProblem ํ…Œ์ŠคํŠธ ๊ฐœ์„ :
    • updateDto์— ์—…๋ฐ์ดํŠธํ•  ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    • problemConverter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ existingProblem์„ resultDto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ Mocking ํ•ฉ๋‹ˆ๋‹ค. (given(problemConverter.toDto(existingProblem)).willReturn(expectedResultDto);)
    • resultDto์˜ ํ•„๋“œ ๊ฐ’์ด updateDto์— ์„ค์ •ํ•œ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • getAllProblems ํ…Œ์ŠคํŠธ ๊ฐœ์„ :
    • problemConverter.toDto๋ฅผ Mocking ํ•˜์—ฌ Problem ๊ฐ์ฒด๋ฅผ ProblemDto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค.
    • problemService.getAllProblems(pageRequest)์˜ ๋ฐ˜ํ™˜ ๊ฐ’์ธ Page<ProblemDto>์˜ ๋‚ด์šฉ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ํ™•์žฅ: ๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ปค๋ฒ„ํ•˜๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์ด ๋งค์šฐ ๊ธด ๋ฌธ์ œ, ์„ค๋ช…์ด ๋งค์šฐ ๊ธด ๋ฌธ์ œ, level์ด ํŠน์ • ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๋ฌธ์ œ ๋“ฑ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ProblemConverter ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€: ProblemConverter์˜ toDto, toEntity ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณ€ํ™˜ ๋กœ์ง์˜ ์ •ํ™•์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ •: JaCoCo์™€ ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์ธก์ •ํ•˜๊ณ , ํ…Œ์ŠคํŠธ๋˜์ง€ ์•Š์€ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค.
  • AssertJ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ณ ๋ ค: AssertJ๋Š” ์ข€ ๋” ์œ ์—ฐํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฌ์šด assertion์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: assertThat(result.getContent()).hasSize(2);)

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ์˜ ์™„์„ฑ๋„๋ฅผ ๋”์šฑ ๋†’์ด๊ณ , ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋ฅผ ์‚ฌ์ „์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, updateProblem ํ…Œ์ŠคํŠธ์™€ getAllProblems ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/converter/ProblemConverter.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/converter/ProblemConverter.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ProblemConverter.java ์ฝ”๋“œ๋ฅผ ๋ฆฌ๋ทฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์ธ ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊น”๋”ํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. Problem ์—”ํ‹ฐํ‹ฐ์™€ ProblemDto DTO ๊ฐ„์˜ ๋ณ€ํ™˜ ๋กœ์ง์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. @Component ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด Spring Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด DI๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•œ ์ ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ๋ณ€ํ™˜ ๋กœ์ง์ด ๋นŒ๋” ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋˜์–ด ๊ฐ€๋…์„ฑ์ด ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค. ๊ฐ ํ•„๋“œ๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ๋งคํ•‘๋˜์–ด ์žˆ์–ด ์–ด๋–ค ํ•„๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™˜๋˜๋Š”์ง€ ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋„ค์ด๋ฐ๋„ ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค (toEntity, toDto).
  • ์ผ๊ด€์„ฑ: DTO์™€ Entity ๊ฐ„์˜ ํ•„๋“œ ๋งคํ•‘์ด ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: dto ๋˜๋Š” entity๊ฐ€ null์ธ ๊ฒฝ์šฐ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์™„๋ฒฝํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋”์šฑ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•„๋“œ ๋ˆ„๋ฝ: Problem ์—”ํ‹ฐํ‹ฐ์— ์ƒˆ๋กœ์šด ํ•„๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์„ ๋•Œ, toDto ๋ฉ”์†Œ๋“œ์—์„œ ํ•ด๋‹น ํ•„๋“œ๋ฅผ DTO๋กœ ๋ณต์‚ฌํ•˜๋Š” ๊ฒƒ์„ ์žŠ์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ProblemDto์— ์ƒˆ๋กœ์šด ํ•„๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด toEntity์—์„œ ์—”ํ‹ฐํ‹ฐ๋กœ ๋ณต์‚ฌํ•˜๋Š” ๊ฒƒ์„ ์žŠ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜๋ฅผ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Error Handling ๋ถ€์กฑ: ๋ณ€ํ™˜ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ (์˜ˆ: ProblemCategory๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ)์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์  ์—†์Œ: ํ˜„์žฌ ์ฝ”๋“œ์˜ ๋‹จ์ˆœํ•œ ๋งคํ•‘ ๋กœ์ง์œผ๋กœ๋Š” ํŠน๋ณ„ํ•œ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ์ง€์ ์ด ์˜ˆ์ƒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • MapStruct ๊ณ ๋ ค: ๋งŒ์•ฝ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ  ๋ณ€ํ™˜ ๋กœ์ง์ด ๋”์šฑ ๋ณต์žกํ•ด์ง„๋‹ค๋ฉด, MapStruct์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MapStruct๋Š” ์ปดํŒŒ์ผ ์‹œ์ ์— ๋งคํ•‘ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ Reflection์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์— ๋น„ํ•ด ์„ฑ๋Šฅ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์žฌ๋Š” ๊ฐ„๋‹จํ•œ ๋ณ€ํ™˜์ด๋ฏ€๋กœ ์˜ค๋ฒ„์—”์ง€๋‹ˆ์–ด๋ง์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection ๊ฐ€๋Šฅ์„ฑ: DTO๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ง์ ‘ ์ €์žฅ๋  ๊ฒฝ์šฐ SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ProblemConverter ์ž์ฒด๋Š” ์ง์ ‘์ ์ธ SQL Injection์„ ์œ ๋ฐœํ•˜์ง€ ์•Š์ง€๋งŒ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆ ์—†์ด ๋ฐ”๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.
  • XSS ๊ณต๊ฒฉ ๊ฐ€๋Šฅ์„ฑ: description ํ•„๋“œ์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ์›น ํŽ˜์ด์ง€์— ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•  ๊ฒฝ์šฐ XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. HTML Escape ์ฒ˜๋ฆฌ ๋“ฑ์„ ํ†ตํ•ด ๋ฐฉ์–ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • NullPointerException ๋ฐฉ์ง€: toEntity ์™€ toDto ๋ฉ”์†Œ๋“œ ์‹œ์ž‘ ๋ถ€๋ถ„์—์„œ dto ์™€ entity ๊ฐ€ null ์ธ์ง€ ํ™•์ธํ•˜๊ณ , IllegalArgumentException ๋“ฑ์˜ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๊ฑฐ๋‚˜, null ๊ฐ’์„ ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
    public Problem toEntity(ProblemDto dto) {
        if (dto == null) {
            throw new IllegalArgumentException("DTO cannot be null.");
        }
        return Problem.builder()
                // ...
                .build();
    }
    
    public ProblemDto toDto(Problem entity) {
        if (entity == null) {
            return null; // or throw IllegalArgumentException
        }
        return ProblemDto.builder()
                // ...
                .build();
    }
  • ๋ถˆ๋ณ€ ๊ฐ์ฒด ์‚ฌ์šฉ: Problem ์—”ํ‹ฐํ‹ฐ์™€ ProblemDto ๋ฅผ ๋ถˆ๋ณ€ ๊ฐ์ฒด๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. ๋ถˆ๋ณ€ ๊ฐ์ฒด๋Š” ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ๋†’์ด๊ณ , ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋” ํŒจํ„ด๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋ถˆ๋ณ€ ๊ฐ์ฒด๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Validation ์ถ”๊ฐ€: DTO์— Bean Validation (JSR-303) ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. Spring MVC๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @Valid ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ DTO์˜ ์œ ํšจ์„ฑ์„ ์ž๋™์œผ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    public class ProblemDto {
        private Long id;
    
        @NotBlank(message = "Title cannot be blank")
        private String title;
    
        // ...
    }
  • ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ ˆ์ด์–ด ์ถ”๊ฐ€: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ธฐ ์ „์— ์ถ”๊ฐ€์ ์ธ ๊ฒ€์ฆ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ณ„๋„์˜ ๋ ˆ์ด์–ด๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ProblemCategory ๊ฐ€ ์œ ํšจํ•œ ๊ฐ’์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • MapStruct ๋„์ž… ๊ณ ๋ ค (๋ณต์žก์„ฑ ์ฆ๊ฐ€ ์‹œ): ๋ณ€ํ™˜ ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๋ฉด MapStruct๋ฅผ ๋„์ž…ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ๋ณด์•ˆ ๊ฐ•ํ™”: SQL Injection ๋ฐ XSS ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ๋ฐฉ์–ด์ฑ…์„ ๋งˆ๋ จํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ฒ€์ฆํ•˜๊ณ  HTML Escape ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: Converter์˜ ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๋Š” ์œ ๋‹› ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, null ๊ฐ’ ์ฒ˜๋ฆฌ, ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ์˜ˆ์ƒ๋˜๋Š” ์˜ˆ์™ธ ๋ฐœ์ƒ ๋“ฑ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊น”๋”ํ•˜๊ณ  ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์œ„์— ์ œ์‹œ๋œ ์ œ์•ˆ๋“ค์€ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ์˜ˆ๋ฐฉํ•˜๊ณ , ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, null ๊ฐ’ ์ฒ˜๋ฆฌ, ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ, ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ์— ๋Œ€ํ•œ ๊ณ ๋ ค๋Š” ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ์™€ ๋ณต์žก์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์ ์ ˆํ•œ ๊ฐœ์„  ๋ฐฉ์•ˆ์„ ์ ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

ProblemController.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/controller/ProblemController.java ํŒŒ์ผ์˜ ์ฝ”๋“œ์— ๋Œ€ํ•œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์˜ @RequiredArgsConstructor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ–ˆ๊ณ , ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: BaseResponse๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต ํ˜•์‹์„ ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ์„ค๊ณ„์ž…๋‹ˆ๋‹ค.
  • ์ฃผ์„: ํ•„์š” ์ด์ƒ์˜ ์ฃผ์„์€ ์—†์ง€๋งŒ, ๋ณต์žกํ•œ ๋กœ์ง์ด๋‚˜ ํŠน์ • ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฝ”๋“œ ์ดํ•ด๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฆ„ ๊ทœ์น™: ๋ณ€์ˆ˜๋ช…, ๋ฉ”์„œ๋“œ๋ช…, ํด๋ž˜์Šค๋ช…์ด Java ๋ช…๋ช… ๊ทœ์น™์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • getAllProblems ๋ฉ”์„œ๋“œ์˜ ๋นˆ ๋ชฉ๋ก ์ฒ˜๋ฆฌ: problems.isEmpty()์ธ ๊ฒฝ์šฐ "์กฐํšŒ๋œ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ ๋นˆ Page ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. API ์ŠคํŽ™ ์ƒ ๋นˆ ๋ชฉ๋ก์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ์ด ๋งž๋Š”์ง€, ์•„๋‹ˆ๋ฉด HTTP 204 No Content๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•œ์ง€ ๊ฒ€ํ† ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋นˆ Page ๊ฐ์ฒด๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • createProblem ๋ฉ”์„œ๋“œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: createProblem ๋ฉ”์„œ๋“œ์—์„œ ์ผ๋ฐ˜์ ์ธ Exception์„ catchํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ (์˜ˆ: DataIntegrityViolationException, IllegalArgumentException)๋ฅผ catchํ•˜์—ฌ ๋” ์ •ํ™•ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•˜๊ณ , ๋กค๋ฐฑ ๋“ฑ์˜ ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ๋„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ Exception์„ catchํ•˜๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊นŒ์ง€ ์ฒ˜๋ฆฌ๋˜์–ด ๋””๋ฒ„๊น…์ด ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • updateProblem ๋ฉ”์„œ๋“œ์˜ ID: @PutMapping("/{id}")์—์„œ ID๋ฅผ PathVariable๋กœ ๋ฐ›๊ณ  ์žˆ์ง€๋งŒ, ProblemDto์—๋„ ID๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋‘ ID๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ DTO์— ID๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory ๋ฉ”์„œ๋“œ์˜ Page -> List ๋ณ€ํ™˜: problemService.getProblemsByCategory์—์„œ Page<Problem>์„ ๋ฐ˜ํ™˜๋ฐ›์•„ page.getContent()๋กœ List<Problem>์„ ์ถ”์ถœํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. BaseResponse<Page<Problem>>์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—์„œ ํŽ˜์ด์ง• ์ •๋ณด๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ๋” ์œ ์—ฐํ•ฉ๋‹ˆ๋‹ค.
  • HTTP ๋ฉ”์„œ๋“œ: @RequestMapping์ด ์ฃผ์„์ฒ˜๋ฆฌ ๋˜์–ด์žˆ์–ด ๊ธฐ๋ณธ URL ๋งคํ•‘์ด ์—†์œผ๋ฏ€๋กœ, ์ „์ฒด URL ํŒจํ„ด์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ชจ๋“  ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๋ฃจํŠธ (/) ์— ๋งคํ•‘๋˜์–ด ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • N+1 ๋ฌธ์ œ: ProblemService์—์„œ Problem ์—”ํ‹ฐํ‹ฐ์™€ ๊ด€๋ จ๋œ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ (์˜ˆ: ์นดํ…Œ๊ณ ๋ฆฌ, ์‚ฌ์šฉ์ž)๋ฅผ ํ•จ๊ป˜ ๋กœ๋”ฉํ•˜๋Š” ๊ฒฝ์šฐ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Fetch Join์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜, Batch Size๋ฅผ ์„ค์ •ํ•˜์—ฌ N+1 ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: getProblemById, getProblemsByCategory์™€ ๊ฐ™์ด ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ๋Š” ์บ์‹ฑ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ์บ์‹ฑ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Pageable ๊ฐ์ฒด ์„ค์ •: Pageable ๊ฐ์ฒด๋ฅผ Controller ์—์„œ ์ง์ ‘ ๋ฐ›๋Š” ๋ฐฉ์‹์€ ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ์•…์˜์ ์ธ ์‚ฌ์šฉ์ž๊ฐ€ Page Size๋ฅผ ๋งค์šฐ ํฌ๊ฒŒ ์„ค์ •ํ•˜์—ฌ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Page Size์˜ ์ตœ๋Œ€๊ฐ’์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ํ—ˆ์šฉ๋˜๋Š” Page Size ๋ชฉ๋ก์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: ProblemDto์— ๋Œ€ํ•œ ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JSR-303 Bean Validation (์˜ˆ: @NotNull, @Size, @Min, @Max)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ , ๋ถ€์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ DB์— ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, SQL Injection์ด๋‚˜ XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ๋Š” ์ž…๋ ฅ ๊ฐ’ (์˜ˆ: ๋ฌธ์ œ ์ œ๋ชฉ, ๋‚ด์šฉ)์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ฒ ์ €ํžˆ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด (RBAC)๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ํŠน์ • ์—ญํ•  (์˜ˆ: ๊ด€๋ฆฌ์ž)๋งŒ ๋ฌธ์ œ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. API ๋ช…์„ธ ๋ช…ํ™•ํ™”: API ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ๊ฐ ์—”๋“œํฌ์ธํŠธ์˜ ์š”์ฒญ/์‘๋‹ต ํ˜•์‹, ์—๋Ÿฌ ์ฝ”๋“œ ๋“ฑ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Swagger ๋˜๋Š” Spring REST Docs๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐœ์„ : createProblem ๋ฉ”์„œ๋“œ์—์„œ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ๋ฅผ catchํ•˜๊ณ , ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋” ์ž์„ธํ•˜๊ฒŒ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  3. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: ProblemDto์— JSR-303 Bean Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  4. ํŽ˜์ด์ง• ์ •๋ณด ์œ ์ง€: getProblemsByCategory ๋ฉ”์„œ๋“œ์—์„œ Page ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—์„œ ํŽ˜์ด์ง• ์ •๋ณด๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  5. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: ProblemService์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก Fetch Join์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, Batch Size๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  6. ์บ์‹ฑ ์ ์šฉ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ๋Š” ์บ์‹ฑ์„ ์ ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  7. ๊ถŒํ•œ ๊ด€๋ฆฌ ๊ตฌํ˜„: Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  8. HTTP ๋ฉ”์„œ๋“œ ๋งคํ•‘: @RequestMapping์„ ํ™œ์„ฑํ™”ํ•˜๊ณ  ๊ฐ API ์—”๋“œํฌ์ธํŠธ์— ์ ์ ˆํ•œ URL ๋งคํ•‘์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  9. Pageable ์„ค์ • ๊ฐ•ํ™”: Pageable ๊ฐ์ฒด์˜ Page Size ์ตœ๋Œ€๊ฐ’์„ ์„ค์ •ํ•˜์—ฌ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  10. DTO ๊ฒ€ํ† : ProblemDto์— ID๊ฐ€ ํฌํ•จ๋  ํ•„์š”๊ฐ€ ์žˆ๋Š”์ง€ ๊ฒ€ํ† ํ•˜๊ณ , ๋ถˆํ•„์š”ํ•˜๋‹ค๋ฉด ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฆฌ๋ทฐ๊ฐ€ ์ฝ”๋“œ ๊ฐœ์„ ์— ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/entity/Problem.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/entity/Problem.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ํ•ด๋‹น ์ฝ”๋“œ ์กฐ๊ฐ์„ ๋ฆฌ๋ทฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ „์ฒด Problem ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์˜ ์ผ๋ถ€์ด๋ฏ€๋กœ, ์ „์ฒด์ ์ธ ๋งฅ๋ฝ์„ ๊ณ ๋ คํ•˜๋ฉฐ ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. update ๋ฉ”์„œ๋“œ๋Š” ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋Š”์ง€ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„: JavaDoc ์Šคํƒ€์ผ์˜ ์ฃผ์„์€ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ฉ”์„œ๋“œ ์ž์ฒด์— ๋Œ€ํ•œ ๋” ์ž์„ธํ•œ ์„ค๋ช… (์˜ˆ: ์–ด๋–ค ๊ฒฝ์šฐ์— ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€, ์ฃผ์˜ํ•  ์ ์€ ๋ฌด์—‡์ธ์ง€ ๋“ฑ)์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋”์šฑ ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  • ๋„ค์ด๋ฐ: ๋ณ€์ˆ˜๋ช… (updatedProblemDto)์€ ๋ช…ํ™•ํ•˜๊ณ  ์˜๋„๋ฅผ ์ž˜ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋˜๋Š” ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: updatedProblemDto๊ฐ€ null์ธ ๊ฒฝ์šฐ, NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. updatedProblemDto์˜ ๊ฐ ํ•„๋“œ (getTitle(), getDescription(), ๋“ฑ)๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•  ๊ฒฝ์šฐ์—๋„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ถ€์žฌ: ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, level์ด ํ—ˆ์šฉ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๊ฐ’์ผ ๊ฒฝ์šฐ, ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. title์ด๋‚˜ description์˜ ๊ธธ์ด๊ฐ€ ๋„ˆ๋ฌด ๊ธด ๊ฒฝ์šฐ DB์— ์ €์žฅ ์‹œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ๋ถˆ๊ฐ€: update ๋ฉ”์„œ๋“œ๋Š” ํ•ญ์ƒ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ ๋ถˆํ•„์š”ํ•œ ๊ฐ’์„ ์ „๋‹ฌํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • @Builder ์œ„์น˜: @Builder ์–ด๋…ธํ…Œ์ด์…˜์ด ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ์žˆ๋Š”์ง€, ๋ฉ”์„œ๋“œ ๋ ˆ๋ฒจ์— ์žˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ์žˆ๋‹ค๋ฉด, Problem ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํŽธ๋ฆฌํ•˜๊ฒŒ ๋นŒ๋” ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋ณ€๊ฒฝ ๊ฐ์ง€ (Dirty Checking) ์ตœ์ ํ™”: JPA๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์—”ํ‹ฐํ‹ฐ์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•œ UPDATE ์ฟผ๋ฆฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋ฌผ๋ก , ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฏ€๋กœ ์ด ๋ฌธ์ œ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์›ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๊ณ ๋ คํ•ด์•ผ ํ•  ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.)
  • ์บ์‹ฑ ํ™œ์šฉ: level์ด๋‚˜ problemCategory ๊ฐ™์€ ํ•„๋“œ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์บ์‹ฑ์„ ํ™œ์šฉํ•˜์—ฌ DB ์ ‘๊ทผ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection ๊ณต๊ฒฉ ๊ฐ€๋Šฅ์„ฑ: title์ด๋‚˜ description ํ•„๋“œ์— ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ง์ ‘ ์ž…๋ ฅ๋ฐ›์€ ๊ฐ’์ด ๋“ค์–ด๊ฐˆ ๊ฒฝ์šฐ, SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ํ•„ํ„ฐ๋ง ๋ฐ escaping ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: update ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ณด์•ˆ์ƒ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. NullPointerException ๋ฐฉ์ง€:

    public void update(ProblemDto updatedProblemDto) {
        if (updatedProblemDto == null) {
            throw new IllegalArgumentException("updatedProblemDto cannot be null");
        }
    
        if (updatedProblemDto.getTitle() != null) {
            this.title = updatedProblemDto.getTitle();
        }
        if (updatedProblemDto.getDescription() != null) {
            this.description = updatedProblemDto.getDescription();
        }
        if (updatedProblemDto.getLevel() != null) {
            this.level = updatedProblemDto.getLevel();
        }
        if (updatedProblemDto.getProblemCategory() != null) {
            this.problemCategory = updatedProblemDto.getProblemCategory();
        }
    }
    • updatedProblemDto ์ž์ฒด๊ฐ€ null์ธ์ง€ ํ™•์ธํ•˜๊ณ , ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•ด null ์ฒดํฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • ํ•„๋“œ๊ฐ€ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • IllegalArgumentException์„ ๋˜์ ธ ์˜ˆ์™ธ ์ƒํ™ฉ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  2. ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€:

    import javax.validation.constraints.Size;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.Max;
    
    // ... Problem ์—”ํ‹ฐํ‹ฐ ๋‚ด๋ถ€ ...
    
    @Size(min = 1, max = 255)
    private String title;
    
    @Size(max = 1000)
    private String description;
    
    @Min(1)
    @Max(5)
    private Integer level;
    
    public void update(ProblemDto updatedProblemDto) {
        // ... null check ...
    
        if (updatedProblemDto.getTitle() != null) {
            // validation check
            if(updatedProblemDto.getTitle().length() < 1 || updatedProblemDto.getTitle().length() > 255) {
                throw new IllegalArgumentException("Title length should be between 1 and 255");
            }
            this.title = updatedProblemDto.getTitle();
        }
        // ... other fields ...
    }
    • javax.validation.constraints ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    • @Size, @Min, @Max ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ œ์•ฝ ์กฐ๊ฑด์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    • update ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ , ์˜ˆ์™ธ๋ฅผ ๋˜์ ธ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
    • Spring Validation์„ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ํšจ๊ณผ์ ์œผ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋ณ„๋„์˜ Validator ํด๋ž˜์Šค ์ƒ์„ฑ)
  3. ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ์ง€์›:

    • ์œ„์˜ NullPointerException ๋ฐฉ์ง€ ์ฝ”๋“œ์™€ ๊ฐ™์ด, null ์ฒดํฌ๋ฅผ ํ†ตํ•ด ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ณด์•ˆ ๊ฐ•ํ™”:

    • SQL Injection ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด, title๊ณผ description ํ•„๋“œ์— ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๊ฑฐ๋‚˜, Prepared Statement๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
    • update ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ์—ญํ• (Role)์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ๋ฌธ์ œ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  5. DTO ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ™œ์šฉ:

    • ProblemDto์—์„œ @NotBlank, @Size ๋“ฑ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ์—”ํ‹ฐํ‹ฐ ์—…๋ฐ์ดํŠธ ์ „์— DTO์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  6. Enum ์‚ฌ์šฉ:

    • problemCategory๊ฐ€ ํŠน์ • ๊ฐ’ ์ง‘ํ•ฉ ๋‚ด์—์„œ๋งŒ ์œ ํšจํ•˜๋‹ค๋ฉด, Enum์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ณ  ๊น”๋”ํ•˜์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ๊ณผ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์œ„์— ์ œ์‹œ๋œ ์ œ์•ˆ๋“ค์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ, ๋ณด์•ˆ, ๊ทธ๋ฆฌ๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, null ์ฒ˜๋ฆฌ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ๋ณด์•ˆ ๋ฌธ์ œ๋Š” ๋ฐ˜๋“œ์‹œ ํ•ด๊ฒฐํ•ด์•ผ ํ•  ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/Service/ProblemService.java ๋ฆฌ๋ทฐ

ProblemService.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/Service/ProblemService.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ:

  • ์ „๋ฐ˜์ ์œผ๋กœ ์ข‹์Œ: ์ฝ”๋“œ๋Š” Spring์˜ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, Lombok์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์˜€์Šต๋‹ˆ๋‹ค. @Slf4j, @Service, @RequiredArgsConstructor ์–ด๋…ธํ…Œ์ด์…˜์€ ์ฝ”๋“œ์˜ ๋ชฉ์ ๊ณผ ์˜์กด์„ฑ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: ๋ฉ”์„œ๋“œ ์ด๋ฆ„์€ ๋ช…ํ™•ํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค. ์ฃผ์„๋„ ์ž˜ ์ž‘์„ฑ๋˜์–ด ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ๊ณผ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • DTO ํ™œ์šฉ: DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—”ํ‹ฐํ‹ฐ์™€ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๊ณ , ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ „์†กํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋กœ๊น…: ์ ์ ˆํ•œ ์ˆ˜์ค€์˜ ๋กœ๊น…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • updateProblem ๋ฉ”์„œ๋“œ: problemRepository.save(problem)์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค. save()๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory ๋ฉ”์„œ๋“œ: ๋ฐ˜ํ™˜ ํƒ€์ž…์ด Page<Problem> ์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ๋“ค์€ ProblemDto๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์—ฌ๊ธฐ๋งŒ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์ผ๊ด€์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค. DTO๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: deleteProblem ๋ฉ”์„œ๋“œ์—์„œ ProblemNotFoundException์„ ์žก์•„์„œ ๋‹ค์‹œ ๋˜์ง€๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๋Š” ์ด๋ฏธ ์˜๋ฏธ ์žˆ๋Š” ์˜ˆ์™ธ์ด๋ฏ€๋กœ, ๊ทธ๋Œ€๋กœ ๋˜์ง€๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. catch ๋ธ”๋ก์—์„œ ๋กœ๊น…๋งŒ ์ˆ˜ํ–‰ํ•ด๋„ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋‹ค๋ฅธ ์˜ˆ์™ธ๋ฅผ ์žก์•„ RuntimeException์œผ๋กœ ๋ž˜ํ•‘ํ•˜๋Š” ๊ฒƒ์€ ์ •๋ณด ์†์‹ค์„ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜, ๊ธฐ์กด ์˜ˆ์™ธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋˜์ง€๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • existsById์™€ deleteById: deleteProblem ๋ฉ”์„œ๋“œ์—์„œ existsById๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ ํ›„ deleteById๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋‘ ๋ฒˆ ์ ‘๊ทผํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. deleteById ํ˜ธ์ถœ ์‹œ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID๋ผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, existsById ํ˜ธ์ถœ์„ ์ œ๊ฑฐํ•˜๊ณ  deleteById์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • N+1 ๋ฌธ์ œ: getAllProblems์—์„œ findAll(pageable) ํ˜ธ์ถœ ํ›„ map(ProblemDto::new)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๊ฐ Problem ์—”ํ‹ฐํ‹ฐ๋งˆ๋‹ค ProblemDto ์ƒ์„ฑ์ž๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ProblemDto ์ƒ์„ฑ์ž ๋‚ด์—์„œ ์ถ”๊ฐ€์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPQL fetch join์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, QueryDSL์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ฝ”๋“œ๋Š” ํ˜„์žฌ ์—”ํ‹ฐํ‹ฐ์—์„œ ๋ฐ”๋กœ DTO๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋งคํ•‘์ด๋ฏ€๋กœ, N+1 ๋ฌธ์ œ ๊ฐ€๋Šฅ์„ฑ์€ ๋‚ฎ์Šต๋‹ˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ: ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ updateProblem ๋ฉ”์„œ๋“œ์˜ save() ํ˜ธ์ถœ๊ณผ deleteProblem ๋ฉ”์„œ๋“œ์˜ existsById ํ˜ธ์ถœ์€ ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ์บ์‹ฑ: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ๋Š” ์บ์‹ฑ์„ ์ ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ฌธ์ œ ๋ชฉ๋ก์ด๋‚˜, ํŠน์ • ID์˜ ๋ฌธ์ œ๋Š” ์บ์‹ฑ์„ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: createProblem ๋ฐ updateProblem ๋ฉ”์„œ๋“œ์—์„œ problemDto์— ๋Œ€ํ•œ ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๋ชฉ์ด ๋น„์–ด์žˆ๋Š”์ง€, ๋‚ด์šฉ์ด ๋„ˆ๋ฌด ๊ธด์ง€ ๋“ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. @Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ Bean Validation API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DTO์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ ์ ˆํ•œ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ, ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • updateProblem ๋ฉ”์„œ๋“œ ์ˆ˜์ •:

    @Transactional
    public ProblemDto updateProblem(Long id, ProblemDto newProblemDto) {
        log.info("๋ฌธ์ œ ์ˆ˜์ • ์‹œ์ž‘ - ๋ฌธ์ œ ID: {}", id);
        Problem problem = problemRepository.findById(id)
                .orElseThrow(() -> new ProblemNotFoundException("์ˆ˜์ •ํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
    
        problem.update(newProblemDto);
    
        // Problem modifiedProblem = problemRepository.save(problem); // ๋ถˆํ•„์š”ํ•œ save() ํ˜ธ์ถœ ์ œ๊ฑฐ
        log.info("๋ฌธ์ œ ์ˆ˜์ • ์™„๋ฃŒ - ๋ฌธ์ œ ID: {}", id);
    
        return problemConverter.toDto(problem); // problemConverter.toDto(problem)์œผ๋กœ ๋ณ€๊ฒฝ
    }
  • getProblemsByCategory ๋ฉ”์„œ๋“œ ์ˆ˜์ •:

    public Page<ProblemDto> getProblemsByCategory(ProblemCategory problemCategory, Pageable pageable) {
        log.info("์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ์กฐํšŒ ์‹œ์ž‘ - ์นดํ…Œ๊ณ ๋ฆฌ: {}", problemCategory);
        Page<Problem> problems = problemRepository.findByProblemCategory(problemCategory, pageable);
    
        log.info("์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ์กฐํšŒ ์™„๋ฃŒ - ์นดํ…Œ๊ณ ๋ฆฌ: {}, ์กฐํšŒ๋œ ๋ฌธ์ œ ์ˆ˜: {}",
                problemCategory, problems.getTotalElements());
        return problems.map(problemConverter::toDto); // DTO๋กœ ๋ณ€ํ™˜
    }
  • deleteProblem ๋ฉ”์„œ๋“œ ์ˆ˜์ •:

    public void deleteProblem(Long id) {
        log.info("๋ฌธ์ œ ์‚ญ์ œ ์‹œ์ž‘ - ๋ฌธ์ œ ID: {}", id);
        try {
            problemRepository.deleteById(id);
            log.info("๋ฌธ์ œ ์‚ญ์ œ ์™„๋ฃŒ - ๋ฌธ์ œ ID: {}", id);
    
        } catch (EmptyResultDataAccessException e) { // Spring Data JPA์—์„œ ์ œ๊ณตํ•˜๋Š” ์˜ˆ์™ธ ์‚ฌ์šฉ
            log.error("๋ฌธ์ œ ์‚ญ์ œ ์‹คํŒจ - ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ (ID: {})", id);
            throw new ProblemNotFoundException("์‚ญ์ œํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); // ProblemNotFoundException์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋˜์ง
    
        } catch (Exception e) {
            log.error("๋ฌธ์ œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ - ๋ฌธ์ œ ID: {}, ์˜ค๋ฅ˜: {}", id, e.getMessage());
            throw new RuntimeException("๋ฌธ์ œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", e);
        }
    }
  • ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: ProblemDto์— Bean Validation API ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ , createProblem ๋ฐ updateProblem ๋ฉ”์„œ๋“œ์—์„œ @Valid ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  • ๊ถŒํ•œ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•˜๋Š” API์— ๋Œ€ํ•œ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ, ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ฑ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ updateProblem ๋ฉ”์„œ๋“œ์˜ ๋ถˆํ•„์š”ํ•œ save() ํ˜ธ์ถœ๊ณผ deleteProblem ๋ฉ”์„œ๋“œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์™€ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด์•ˆ์„ฑ์„ ๊ฐ•ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ์ธ์ƒ:

์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ControllerAdvice๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ํŠน์ • ์˜ˆ์™ธ์™€ ์ผ๋ฐ˜ ์˜ˆ์™ธ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ๊ณผ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ:

  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ์งง๊ณ  ๊ฐ„๊ฒฐํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋ช…ํ™•ํ•œ ๋„ค์ด๋ฐ ๊ทœ์น™์„ ๋”ฐ๋ฅด๊ณ  ์žˆ์–ด ๊ฐ ๋ฉ”์„œ๋“œ์˜ ์—ญํ• ์„ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ์ฝ”๋“œ์˜ ๊ธธ์ด๊ฐ€ ์งง๊ณ , ์—ญํ• ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.
  • ์Šคํƒ€์ผ: Spring ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๊ถŒ์žฅํ•˜๋Š” ์Šคํƒ€์ผ์„ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • ์ผ๋ฐ˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์˜ ์œ„ํ—˜์„ฑ: Exception.class๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์žก์•„๋ƒ…๋‹ˆ๋‹ค. ์ด๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊นŒ์ง€ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋””๋ฒ„๊น…์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๊ณ , ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ๋†“์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, NullPointerException๊ณผ ๊ฐ™์€ ๋Ÿฐํƒ€์ž„ ์˜ˆ์™ธ๋ฅผ ์žก์•„๋ฒ„๋ฆฌ๋ฉด ๋ฌธ์ œ์˜ ์›์ธ์„ ํŒŒ์•…ํ•˜๊ธฐ ํž˜๋“ค์–ด์ง‘๋‹ˆ๋‹ค.
  • ์‘๋‹ต ๋‚ด์šฉ์˜ ๋ถ€์กฑ: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋งŒ์„ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ถฉ๋ถ„ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์›์ธ์— ๋Œ€ํ•œ ์ƒ์„ธ ์ •๋ณด(์Šคํƒ ํŠธ๋ ˆ์ด์Šค, ์—๋Ÿฌ ์ฝ”๋“œ ๋“ฑ)๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
  • HttpStatus.INTERNAL_SERVER_ERROR์˜ ๋‚จ์šฉ: ๋ชจ๋“  Exception์— ๋Œ€ํ•ด HttpStatus.INTERNAL_SERVER_ERROR๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์ ์ ˆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋” ์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ž˜๋ชป๋œ ์ž…๋ ฅ์œผ๋กœ ์ธํ•œ ์˜ˆ์™ธ๋ผ๋ฉด HttpStatus.BAD_REQUEST๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊น… ๋ถ€์กฑ: ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์›์ธ์„ ๋ถ„์„ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ์—๋งŒ ์‹คํ–‰๋˜๋ฏ€๋กœ ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ์ž‘์—…์ด๋ฏ€๋กœ, ์˜ˆ์™ธ๋ฅผ ๋‚จ๋ฐœํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด(์˜ˆ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด, ์‚ฌ์šฉ์ž ๊ฐœ์ธ ์ •๋ณด)๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๊ทธ๋Œ€๋กœ ๋…ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ ์ทจ์•ฝ์ ์œผ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ๋ฉ”์‹œ์ง€์—๋Š” ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ์„ค๋ช…๋งŒ ํฌํ•จํ•˜๊ณ , ์‹ค์ œ ์˜ค๋ฅ˜ ์ •๋ณด๋Š” ๋กœ๊ทธ์—๋งŒ ๊ธฐ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  1. ์˜ˆ์™ธ ์„ธ๋ถ„ํ™” ๋ฐ ๊ตฌ์ฒด์ ์ธ HTTP ์ƒํƒœ ์ฝ”๋“œ ์‚ฌ์šฉ:

    • Exception.class ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜, ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค(์˜ˆ: DataAccessException, IllegalArgumentException)๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    • ๊ฐ ์˜ˆ์™ธ์— ๋Œ€ํ•ด ์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ HttpStatus.BAD_REQUEST๋ฅผ, ๊ถŒํ•œ ๋ถ€์กฑ ์‹œ HttpStatus.FORBIDDEN์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    • ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํŠน์ • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์˜ค๋ฅ˜๋ฅผ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  2. ์‘๋‹ต ๊ฐ์ฒด ๊ฐœ์„ :

    • ๋‹จ์ˆœํžˆ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋งŒ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹ , ์˜ค๋ฅ˜ ์ฝ”๋“œ, ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€, ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ๊ฐ„ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๋‹ด์€ ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    • ํ‘œ์ค€ํ™”๋œ ์‘๋‹ต ํ˜•์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ์ผ๊ด€์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  3. ๋กœ๊น… ์ถ”๊ฐ€:

    • handleGenericException ๋ฉ”์„œ๋“œ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ์Šคํƒ ํŠธ๋ ˆ์ด์Šค์™€ ํ•จ๊ป˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๊ธฐ๋กํ•˜์—ฌ ๋ฌธ์ œ ์›์ธ์„ ๋ถ„์„ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    • ๋กœ๊น… ๋ ˆ๋ฒจ์„ ์กฐ์ ˆํ•˜์—ฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ์ž์„ธํ•œ ์ •๋ณด๋ฅผ, ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๊ธฐ๋กํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ฏผ๊ฐ ์ •๋ณด ํ•„ํ„ฐ๋ง:

    • ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ฉ๋‹ˆ๋‹ค.
    • ๋กœ๊ทธ์— ๊ธฐ๋ก๋˜๋Š” ์ •๋ณด๋„ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๋ฏผ๊ฐ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    • ์‘๋‹ต ๊ฐ์ฒด์— ํฌํ•จ๋˜๋Š” ์ •๋ณด๋„ ๋ฏผ๊ฐ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ฉ๋‹ˆ๋‹ค.
  5. ControllerAdvice ๋ฒ”์œ„ ์ œํ•œ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ):

    • ๋งŒ์•ฝ ํŠน์ • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋งŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, @ControllerAdvice์— basePackages ๋˜๋Š” assignableTypes ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒ”์œ„๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ถˆํ•„์š”ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ์„ ๋œ ์ฝ”๋“œ ์˜ˆ์‹œ (์ผ๋ถ€):

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.server.ResponseStatusException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import site.haruhana.www.exception.ProblemNotFoundException;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(ProblemNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleProblemNotFoundException(ProblemNotFoundException ex) {
        ErrorResponse errorResponse = new ErrorResponse("PROBLEM_NOT_FOUND", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
        ErrorResponse errorResponse = new ErrorResponse("INVALID_INPUT", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

     @ExceptionHandler(ResponseStatusException.class) // Spring ์—์„œ ์ œ๊ณตํ•˜๋Š” ์˜ˆ์™ธ
    public ResponseEntity<ErrorResponse> handleResponseStatusException(ResponseStatusException ex) {
        ErrorResponse errorResponse = new ErrorResponse(String.valueOf(ex.getStatusCode().value()), ex.getReason());
        return new ResponseEntity<>(errorResponse, ex.getStatusCode());
    }


    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
        logger.error("Unexpected exception occurred: ", ex); // ๋กœ๊น… ์ถ”๊ฐ€
        ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", "An unexpected error occurred."); // ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // ErrorResponse ํด๋ž˜์Šค ์˜ˆ์‹œ (์‹ค์ œ ๊ตฌํ˜„์€ ํ•„์š”์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ)
    private static class ErrorResponse {
        private String code;
        private String message;

        public ErrorResponse(String code, String message) {
            this.code = code;
            this.message = message;
        }

        public String getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }
}

์œ„ ์ฝ”๋“œ๋Š” ๋‹จ์ง€ ์˜ˆ์‹œ์ด๋ฉฐ, ์‹ค์ œ ๊ตฌํ˜„์€ ํ”„๋กœ์ ํŠธ์˜ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ์€ ์˜ˆ์™ธ๋ฅผ ์„ธ๋ถ„ํ™”ํ•˜๊ณ , ์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์˜ค๋ฅ˜ ์ •๋ณด๋ฅผ ๋‹ด์€ ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋กœ๊น…์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋””๋ฒ„๊น…์„ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ฏผ๊ฐ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ๋ฆฌ๋ทฐ

ProblemNotFoundException.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ฝ”๋“œ ๊ฐœ์š”:

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ProblemNotFoundException ์ด๋ผ๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๋Š” RuntimeException์„ ์ƒ์†๋ฐ›์•„ checked exception์ด ์•„๋‹Œ unchecked exception์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํด๋ž˜์Šค ์ด๋ฆ„์€ ์˜ˆ์™ธ์˜ ์˜๋ฏธ๋ฅผ ์ž˜ ๋‚˜ํƒ€๋‚ด๊ณ  ์žˆ์œผ๋ฉฐ, ์ƒ์„ฑ์ž๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•„ ์ƒ์œ„ ํด๋ž˜์Šค์— ์ „๋‹ฌํ•˜๋Š” ๊ธฐ๋ณธ์ ์ธ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ : ์ฝ”๋“œ ์ž์ฒด๋Š” ๊ฐ„๊ฒฐํ•˜์ง€๋งŒ, Javadoc์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์˜ˆ์™ธ์˜ ๋ชฉ์ ๊ณผ ์‚ฌ์šฉ ์‹œ์ ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•˜๋ฉด ๋”์šฑ ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋˜๋Š” ๋ฌธ์ œ์ :

  • ์—†์Œ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค ์ •์˜๋กœ์„œ ํŠน๋ณ„ํ•œ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ ์„ ์ฐพ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค์‚ฌํ•ญ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์ถ”๊ฐ€์ ์ธ ์ •๋ณด (์˜ˆ: ๋ฌธ์ œ ID, ๋ฌธ์ œ ์ œ๋ชฉ ๋“ฑ)๋ฅผ ํฌํ•จํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ถ”๊ฐ€ ์ •๋ณด๋Š” ๊ฐ์ฒด ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•˜๊ณ  ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ดˆ๊ธฐํ™”ํ•˜๋„๋ก ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ํ•ด๋‹น ์‚ฌํ•ญ ์—†์Œ: ์˜ˆ์™ธ ํด๋ž˜์Šค ์ž์ฒด๋Š” ์„ฑ๋Šฅ์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์˜ˆ์™ธ์ ์ธ ์ƒํ™ฉ์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, ์˜ˆ์™ธ ํด๋ž˜์Šค ์ž์ฒด์˜ ์„ฑ๋Šฅ๋ณด๋‹ค๋Š” ์˜ˆ์™ธ ๋ฐœ์ƒ ๋นˆ๋„๋ฅผ ์ค„์ด๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ : ์˜ˆ์™ธ๋ฅผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ž์ฃผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Problem ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜์ง€ ์•Š๊ณ  Problem์— ์ ‘๊ทผํ•˜๋ ค๋‹ค ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ, ์‚ฌ์ „์— Problem ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์˜ˆ์™ธ ๋ฐœ์ƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์—†์Œ: ์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ์˜ˆ์™ธ ํด๋ž˜์Šค ์ž์ฒด์ด๋ฏ€๋กœ ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ์ทจ์•ฝ์ ์€ ์—†์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค์‚ฌํ•ญ: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋ฏผ๊ฐํ•œ ์ •๋ณด (์˜ˆ: ์‹œ์Šคํ…œ ๊ฒฝ๋กœ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ)๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๊ฐ€ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ํ†ตํ•ด ์‹œ์Šคํ…œ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • Javadoc ์ถ”๊ฐ€: ํด๋ž˜์Šค ๋ฐ ์ƒ์„ฑ์ž์— Javadoc์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์˜ˆ์™ธ์˜ ๋ชฉ์ , ์‚ฌ์šฉ ์‹œ์ , ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด ๋“ฑ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

    package site.haruhana.www.exception;
    
    /**
     * ์ง€์ •๋œ Problem์„ ์ฐพ์„ ์ˆ˜ ์—†์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค.
     */
    public class ProblemNotFoundException extends RuntimeException {
    
        /**
         * ์ฃผ์–ด์ง„ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ProblemNotFoundException์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
         *
         * @param message ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€
         */
        public ProblemNotFoundException(String message) {
            super(message);
        }
    }
  • Problem ID ํ•„๋“œ ์ถ”๊ฐ€ (์„ ํƒ ์‚ฌํ•ญ): ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋ฌธ์ œ ID๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    package site.haruhana.www.exception;
    
    public class ProblemNotFoundException extends RuntimeException {
        private final Long problemId;
    
        public ProblemNotFoundException(Long problemId, String message) {
            super(message);
            this.problemId = problemId;
        }
    
        public Long getProblemId() {
            return problemId;
        }
    }
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต ๊ฒ€ํ† : ProblemNotFoundException์ด ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ๊ณผ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์„ ์ „๋ฐ˜์ ์œผ๋กœ ๊ฒ€ํ† ํ•˜๊ณ , ๋” ๋‚˜์€ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต์„ ์ˆ˜๋ฆฝํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ์— ์ ‘๊ทผํ•˜๋ ค๊ณ  ํ•  ๋•Œ, ์—๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ฑฐ๋‚˜, ๊ธฐ๋ณธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ œ๊ณต๋œ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค ์ •์˜๋กœ์„œ ํ’ˆ์งˆ์ด ์ข‹๊ณ  ๊ฐ€๋…์„ฑ์ด ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ, Javadoc์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ์˜ ์„ค๋ช…๋ ฅ์„ ๋†’์ด๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ๋ฌธ์ œ ID์™€ ๊ฐ™์€ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์˜ˆ์™ธ ๋ฐœ์ƒ ์ƒํ™ฉ์„ ๋ถ„์„ํ•˜๊ณ  ๋” ์ ์ ˆํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต์„ ์ˆ˜๋ฆฝํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

ProblemRepository.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/repository/ProblemRepository.java ํŒŒ์ผ์˜ ์ฝ”๋“œ์— ๋Œ€ํ•œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์ฝ”๋“œ ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. Spring Data JPA์˜ JpaRepository๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ถ”๊ฐ€์ ์œผ๋กœ findByProblemCategory ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜์—ฌ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ฌธ์ œ๋“ค์„ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌํ•˜์—ฌ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฆ„, ๋ฉ”์„œ๋“œ ์ด๋ฆ„, ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„ ๋“ฑ์ด ์ง๊ด€์ ์ด์–ด์„œ ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋˜๋Š” ๋ฌธ์ œ์ 

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ํŠน๋ณ„ํ•œ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ ์„ ๋ฐœ๊ฒฌํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. Spring Data JPA๊ฐ€ ๋Œ€๋ถ€๋ถ„์˜ ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค ๋กœ์ง์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ฃผ๋ฏ€๋กœ, ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, findByProblemCategory ๋ฉ”์„œ๋“œ๋งŒ์œผ๋กœ๋Š” ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌธ์ œ ์ œ๋ชฉ์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ๋‚œ์ด๋„์˜ ๋ฌธ์ œ๋งŒ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋“ฑ์˜ ์š”๊ตฌ์‚ฌํ•ญ์ด ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ, ์ด ๋ฉ”์„œ๋“œ๋งŒ์œผ๋กœ๋Š” ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ๋ฅผ ์ฐพ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. Spring Data JPA๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ JPA์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค๋งŒ, findByProblemCategory ๋ฉ”์„œ๋“œ์˜ ๊ฒฝ์šฐ, ProblemCategory ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—, ProblemCategory ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ JOIN์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ Problem ์—”ํ‹ฐํ‹ฐ์— problemCategoryId ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜๊ณ , ํ•ด๋‹น ํ•„๋“œ๋กœ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด, JOIN ์—†์ด ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Pageable ๊ฐ์ฒด๊ฐ€ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋ถ€ํ„ฐ ์ œ๋Œ€๋กœ ์ƒ์„ฑ๋˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  Page ๊ฐ์ฒด์— ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋˜์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์บ์‹ฑ ์ „๋žต์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋Œ€ํ•œ ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ณ  ์‘๋‹ต ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ์ ์šฉํ•˜๊ฑฐ๋‚˜, Ehcache, Redis ๋“ฑ์˜ ์บ์‹œ ์‹œ์Šคํ…œ์„ ์—ฐ๋™ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ง์ ‘์ ์œผ๋กœ ๋ฐ›์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด๊ฐ€ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›๋Š” ๊ฐ’์— ์˜ํ•ด ๊ฒฐ์ •๋œ๋‹ค๋ฉด, ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์„ ํ†ตํ•ด SQL Injection๊ณผ ๊ฐ™์€ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ ๋ชฉ๋ก์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€, ๋ฌธ์ œ ์ƒ์„ธ ์ •๋ณด์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ๋“ฑ์„ ๊ฒ€์‚ฌํ•˜์—ฌ ๋ฌด๋‹จ ์ ‘๊ทผ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ง€์›: ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด Spring Data JPA์˜ Querydsl์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, Criteria API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ช…์„ธ(Specification) ํŒจํ„ด ์ ์šฉ: ๊ฒ€์ƒ‰ ์กฐ๊ฑด์ด ๋ณต์žกํ•ด์งˆ ๊ฒฝ์šฐ, ๋ช…์„ธ ํŒจํ„ด์„ ์ ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋”์šฑ ๋ชจ๋“ˆํ™”ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Data JPA๋Š” Specification ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋ช…์„ธ ํŒจํ„ด์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ๋ฑ์Šค ์ถ”๊ฐ€: Problem ํ…Œ์ด๋ธ”์— problemCategoryId ํ•„๋“œ์— ๋Œ€ํ•œ ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด findByProblemCategory ๋ฉ”์„œ๋“œ์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ธ๋ฑ์Šค ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ: Repository ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Spring TestContext Framework๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๋™๋œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ์ถฉ์‹คํžˆ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ฝ”๋“œ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ๋„ ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ์œ„์—์„œ ์ œ์‹œ๋œ ๊ฐœ์„  ์ œ์•ˆ๋“ค์€ ํ˜„์žฌ ์ฝ”๋“œ์— ์ฆ‰์‹œ ์ ์šฉํ•ด์•ผ ํ•  ํ•„์ˆ˜์ ์ธ ์‚ฌํ•ญ์€ ์•„๋‹ˆ์ง€๋งŒ, ํ–ฅํ›„ ์š”๊ตฌ์‚ฌํ•ญ ๋ณ€ํ™”์— ๋”ฐ๋ผ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ์‚ฌํ•ญ๋“ค์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ๋‹ค์–‘ํ•œ ๊ฒ€์ƒ‰ ์กฐ๊ฑด ์ง€์› ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ์„œ๋น„์Šค์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ๋”์šฑ ์ค‘์š”ํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/dto/ProblemDto.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ „๋ฐ˜์ ์œผ๋กœ Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ธ ์ ์€ ๊ธ์ •์ ์ž…๋‹ˆ๋‹ค. DTO์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋Š” ์ž˜ ๊ฐ–์ถฐ์ ธ ์žˆ์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ๋ณด์ž…๋‹ˆ๋‹ค. ํŠนํžˆ Problem ์—”ํ‹ฐํ‹ฐ๋กœ๋ถ€ํ„ฐ ProblemDto๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ƒ์„ฑ์ž์— ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ๊ธ์ •์ :
    • Lombok ์–ด๋…ธํ…Œ์ด์…˜ (@Getter, @Builder, @NoArgsConstructor, @AllArgsConstructor)์„ ํ™œ์šฉํ•˜์—ฌ ์ฝ”๋“œ์˜ ์–‘์„ ์ค„์ด๊ณ  ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.
    • ํด๋ž˜์Šค ์ด๋ฆ„ (ProblemDto)์€ DTO์ž„์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚ด๋ฏ€๋กœ ์ข‹์Šต๋‹ˆ๋‹ค.
    • ๋ณ€์ˆ˜ ์ด๋ฆ„ (id, title, description ๋“ฑ)์€ ์˜๋ฏธ๊ฐ€ ๋ช…ํ™•ํ•˜๊ณ  ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.
  • ๊ฐœ์„  ํ•„์š”:
    • Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” ์ƒ์„ฑ์ž ๋‚ด๋ถ€์—์„œ getProblemCategory()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์€ ์˜คํ•ด์˜ ์†Œ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜„์žฌ ํด๋ž˜์Šค ๋‚ด์— ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ๋ช…ํ™•ํ•œ ์˜๋„๋ฅผ ๊ฐ€์ง€๊ณ  ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • ์‹ฌ๊ฐ: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” ์ƒ์„ฑ์ž ๋‚ด์˜ this.problemCategory = getProblemCategory();๋Š” ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. getProblemCategory() ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. problem.getProblemCategory()๋ฅผ ์˜๋„ํ•œ ๊ฒƒ์ด๋ผ๋ฉด ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ณ ๋ ค: DTO๋Š” ๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ์ฒด์ด๋ฏ€๋กœ, ์—”ํ‹ฐํ‹ฐ์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ๋ณด๋‹ค๋Š” ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ DTO๋กœ ๋ณต์‚ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ๊ณ ๋ ค: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ๋ˆˆ์— ๋„๋Š” ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์€ ์—†์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, Problem ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋งค์šฐ ํฐ ๊ฐ์ฒด์ด๊ณ  ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” DTO๋ผ๋ฉด, ํ•„๋“œ ์„ ํƒ์  ๋ณต์‚ฌ๋ฅผ ํ†ตํ•ด ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MapStruct์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ ํ–ฅ์ƒ ๋ฐ ์ฝ”๋“œ ๊ฐ„๊ฒฐ์„ฑ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ํŠน๋ณ„ํ•œ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. DTO๋Š” ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ๊ฐ์ฒด์ด๋ฏ€๋กœ, ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ์ด๋‚˜ ๊ถŒํ•œ ๊ฒ€์‚ฌ ๋“ฑ์˜ ๋กœ์ง์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Controller๋‚˜ Service Layer์—์„œ ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ์„ ์ฒ ์ €ํžˆ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ answer ํ•„๋“œ์˜ ๊ฒฝ์šฐ, ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์•”ํ˜ธํ™” ๋“ฑ์˜ ๋ณด์•ˆ ์กฐ์น˜๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  1. ์ปดํŒŒ์ผ ์—๋Ÿฌ ์ˆ˜์ •: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” ์ƒ์„ฑ์ž ๋‚ด์˜ this.problemCategory = getProblemCategory();๋ฅผ this.problemCategory = problem.getProblemCategory();๋กœ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = problem.getDescription();
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory(); // ์ˆ˜์ •๋จ
    }
  2. ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋ณต์‚ฌ: ProblemDto์—์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ•„๋“œ๋งŒ ์—”ํ‹ฐํ‹ฐ๋กœ๋ถ€ํ„ฐ ๋ณต์‚ฌํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, answer ํ•„๋“œ๊ฐ€ ํŠน์ • ์ƒํ™ฉ์—์„œ๋งŒ ํ•„์š”ํ•˜๋‹ค๋ฉด, ํ•ด๋‹น ์ƒํ™ฉ์—์„œ๋งŒ ๊ฐ’์„ ์„ค์ •ํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

  3. MapStruct ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ณ ๋ ค: ์—”ํ‹ฐํ‹ฐ์™€ DTO ๊ฐ„์˜ ๋งคํ•‘ ๋กœ์ง์ด ๋ณต์žกํ•ด์งˆ ๊ฒฝ์šฐ, MapStruct์™€ ๊ฐ™์€ ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  4. ๋ถˆ๋ณ€ ๊ฐ์ฒด (Immutable Object) ๊ณ ๋ ค: ProblemDto์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ final๋กœ ์„ ์–ธํ•˜๊ณ  ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ฐ’์„ ์„ค์ •ํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜๋ฉด, ๊ฐ์ฒด์˜ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @Value Lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋‹จ, ์ด ๊ฒฝ์šฐ ๋นŒ๋” ํŒจํ„ด์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

  5. ์ฃผ์„ ์ถ”๊ฐ€: ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ฃผ์„์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋”์šฑ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ˆ˜์ •๋œ ์ฝ”๋“œ ์˜ˆ์‹œ (์ผ๋ถ€):

package site.haruhana.www.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import site.haruhana.www.entity.Problem;
import site.haruhana.www.entity.ProblemCategory;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor

public class ProblemDto {
    private Long id; // ๋ฌธ์ œ ID
    private String title; // ๋ฌธ์ œ ์ œ๋ชฉ
    private String description; // ๋ฌธ์ œ ์„ค๋ช…
    private String answer; // ๋ฌธ์ œ ์ •๋‹ต
    private int level; // ๋ฌธ์ œ ๋‚œ์ด๋„
    private ProblemCategory problemCategory; // ๋ฌธ์ œ ์นดํ…Œ๊ณ ๋ฆฌ

    public ProblemDto(Problem problem) {
        this.id = problem.getId();
        this.title = problem.getTitle();
        this.description = problem.getDescription();
        this.answer = problem.getAnswer();
        this.level = problem.getLevel();
        this.problemCategory = problem.getProblemCategory(); // ์ˆ˜์ •๋จ
    }

}

์œ„์˜ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜์—ฌ ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”.

Copy link

src/test/java/site/haruhana/www/service/ProblemServiceTest.java ๋ฆฌ๋ทฐ

์ฝ”๋“œ ๋ฆฌ๋ทฐ (ProblemServiceTest.java)

๋‹ค์Œ์€ src/test/java/site/haruhana/www/service/ProblemServiceTest.java ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์ธ ํ’ˆ์งˆ: ์ฝ”๋“œ๋Š” ์ „๋ฐ˜์ ์œผ๋กœ ๊น”๋”ํ•˜๊ณ  ์ž˜ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Mockito๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž˜ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋Š” ๋ช…ํ™•ํ•œ ๋ชฉ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ํ…Œ์ŠคํŠธ ์ด๋ฆ„์€ DisplayName ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ํ•œ๊ธ€๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…๋˜์–ด ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Given-When-Then ํŒจํ„ด์„ ์ž˜ ํ™œ์šฉํ•˜์—ฌ ๊ฐ ํ…Œ์ŠคํŠธ์˜ ๋ชฉ์ , ์‹คํ–‰, ๊ฒ€์ฆ ๋‹จ๊ณ„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์„๋„ ์ ์ ˆํ•˜๊ฒŒ ์‚ฌ์šฉ๋˜์–ด ์ฝ”๋“œ์˜ ์ดํ•ด๋„๋ฅผ ๋†’์ž…๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ: ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ ์ด๋ฆ„, ๋ณ€์ˆ˜ ์ด๋ฆ„ ๋“ฑ์ด ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๊ณ  ์žˆ์–ด ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :
    • createTestProblem() ๋ฉ”์„œ๋“œ๋Š” ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ์„ ๋‹ด๋‹นํ•˜๋ฏ€๋กœ, ํ•„์š”์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ํ™•์žฅํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‚œ์ด๋„๋ณ„, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ProblemDto์— ๋Œ€ํ•œ ๊ฐ’ ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ProblemConverter๋ฅผ ํ†ตํ•ด ๋ณ€ํ™˜๋œ ProblemDto์˜ ํŠน์ • ํ•„๋“œ ๊ฐ’์„ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • DTO ํ•„๋“œ ๋ˆ„๋ฝ: ProblemDto์— ๋Œ€ํ•œ mocking ์‹œ ์‹ค์ œ ๊ฐ’์ด ์„ค์ •๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์„œ๋น„์Šค ๋กœ์ง์—์„œ DTO์˜ ํŠน์ • ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Mocking ์‹œ ํ•„์š”ํ•œ ํ•„๋“œ ๊ฐ’์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜๋Š”, ProblemDto ๊ฐ์ฒด๋ฅผ ์ง์ ‘ new ํ•˜์—ฌ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋Š”๊ฒƒ์ด ๋” ๋ช…ํ™•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์—…๋ฐ์ดํŠธ ํ…Œ์ŠคํŠธ: updateProblem ํ…Œ์ŠคํŠธ์—์„œ updateDto์— ์–ด๋–ค ๊ฐ’์„ ๋„ฃ์–ด์•ผ ํ• ์ง€ ๋ช…ํ™•ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ๋  ํ•„๋“œ์— ๋Œ€ํ•œ ๊ตฌ์ฒด์ ์ธ ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , ์—…๋ฐ์ดํŠธ ์ดํ›„ ํ•ด๋‹น ๊ฐ’์ด ์ œ๋Œ€๋กœ ๋ฐ˜์˜๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” ๋‹จ์ˆœํžˆ ๊ฐ์ฒด๊ฐ€ null์ด ์•„๋‹Œ์ง€๋งŒ ํ™•์ธํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • getAllProblems ํ…Œ์ŠคํŠธ: Problem ์—”ํ‹ฐํ‹ฐ๋ฅผ ProblemDto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. problemConverter์— ๋Œ€ํ•œ mocking์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฐ˜ํ™˜๋œ ProblemDto์˜ ๋‚ด์šฉ์ด ์˜ˆ์ƒ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š์ง€๋งŒ, ๋งŽ์€ ์ˆ˜์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋ถ€๋ถ„์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. createTestProblem() ๋ฉ”์„œ๋“œ๋ฅผ ์บ์‹ฑํ•˜๊ฑฐ๋‚˜, ๋นŒ๋” ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” ์„ฑ๋Šฅ ๊ฐœ์„ ์˜ ์—ฌ์ง€๊ฐ€ ํฌ๊ฒŒ ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž์ฒด์—๋Š” ์ง์ ‘์ ์ธ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‹ค์ œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋‚˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ์„ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • DTO ๊ฐ’ ๊ฒ€์ฆ ์ถ”๊ฐ€: getProblemById, updateProblem, getAllProblems ํ…Œ์ŠคํŠธ์—์„œ ๋ฐ˜ํ™˜๋œ ProblemDto ๊ฐ์ฒด์˜ ํŠน์ • ํ•„๋“œ ๊ฐ’(title, description ๋“ฑ)์„ ๊ฒ€์ฆํ•˜์—ฌ, ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋กœ์ง์˜ ์ •ํ™•์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.
  • ์—…๋ฐ์ดํŠธ ํ…Œ์ŠคํŠธ ๊ฐœ์„ : updateProblem ํ…Œ์ŠคํŠธ์—์„œ updateDto์— ์‹ค์ œ ์—…๋ฐ์ดํŠธํ•  ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , ์—…๋ฐ์ดํŠธ๋œ ๊ฐ์ฒด์˜ ํ•ด๋‹น ํ•„๋“œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • getAllProblems ํ…Œ์ŠคํŠธ ๊ฐœ์„ : getAllProblems ํ…Œ์ŠคํŠธ์—์„œ problemConverter์— ๋Œ€ํ•œ mocking์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฐ˜ํ™˜๋œ ProblemDto์˜ ๋‚ด์šฉ์ด ์˜ˆ์ƒ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ: createTestProblem() ๋ฉ”์„œ๋“œ๋ฅผ ํ™•์žฅํ•˜์—ฌ ๋‹ค์–‘ํ•œ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
  • BDD ์Šคํƒ€์ผ ๊ฐ•์กฐ: BDDMockito๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์„ ๋” ์ ๊ทน์ ์œผ๋กœ ํ™œ์šฉํ•˜์—ฌ Given-When-Then ํŒจํ„ด์„ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ: JaCoCo์™€ ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , ํ…Œ์ŠคํŠธ๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์„ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ (DTO ๊ฐ’ ๊ฒ€์ฆ ์ถ”๊ฐ€):

@Test
@DisplayName("ID๋กœ ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•˜๋ฉด ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค")
void getProblemById() {
    // given: ๋ฌธ์ œ ID์™€ ์˜ˆ์ƒ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ฃผ์–ด์กŒ์„ ๋•Œ
    Long problemId = 1L;
    Problem problem = createTestProblem();  // new Problem() ๋Œ€์‹  ์ด ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    ProblemDto expectedDto = ProblemDto.builder() // ์˜ˆ์ƒ๋˜๋Š” DTO ๊ฐ’์„ ์„ค์ •
        .title("ํ…Œ์ŠคํŠธ ๋ฌธ์ œ")
        .description("ํ…Œ์ŠคํŠธ ์„ค๋ช…")
        .build();

    given(problemRepository.findById(problemId)).willReturn(Optional.of(problem));
    given(problemConverter.toDto(problem)).willReturn(expectedDto); // ์ถ”๊ฐ€

    // when: ID๋กœ ๋ฌธ์ œ๋ฅผ ์กฐํšŒํ•˜๋ฉด
    ProblemDto result = problemService.getProblemById(problemId);

    // then: ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์ œ DTO๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค
    assertNotNull(result, "๋ฐ˜ํ™˜๋œ ๋ฌธ์ œ๋Š” null์ด ์•„๋‹ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค");
    assertEquals(expectedDto.getTitle(), result.getTitle(), "์ œ๋ชฉ์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค"); // ์ œ๋ชฉ ๊ฒ€์ฆ
    assertEquals(expectedDto.getDescription(), result.getDescription(), "์„ค๋ช…์ด ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค"); // ์„ค๋ช… ๊ฒ€์ฆ
    verify(problemRepository).findById(problemId);
    verify(problemConverter).toDto(problem); // ์ถ”๊ฐ€
}

๊ฒฐ๋ก :

์ฝ”๋“œ๋Š” ์ „๋ฐ˜์ ์œผ๋กœ ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ProblemService์˜ ๊ธฐ๋Šฅ์„ ๊ฒ€์ฆํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์•ˆ๋œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ๋ฐ˜์˜ํ•˜๋ฉด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ์‹ ๋ขฐ์„ฑ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ DTO ๊ฐ’ ๊ฒ€์ฆ, ์—…๋ฐ์ดํŠธ ํ…Œ์ŠคํŠธ ๊ฐœ์„ , ๊ทธ๋ฆฌ๊ณ  getAllProblems ํ…Œ์ŠคํŠธ ๊ฐœ์„ ์— ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์ „๋ฐ˜์ ์ธ ํ‰๊ฐ€:

์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๋‹จํ•˜๊ณ  ๊ธฐ๋Šฅ์ ์œผ๋กœ๋Š” ๊ธ€๋กœ๋ฒŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ์€ ๊ดœ์ฐฎ์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ์„ ํ†ตํ•ด ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ์žฅ์ :
    • @ControllerAdvice ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปจํŠธ๋กค๋Ÿฌ ์ „์—ญ์—์„œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค์ •ํ•œ ๊ฒƒ์€ ์ข‹์Šต๋‹ˆ๋‹ค.
    • @ExceptionHandler ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • ProblemNotFoundException์— ๋Œ€ํ•œ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ํŠน์ • ์ƒํ™ฉ์— ๋งž๋Š” ์‘๋‹ต์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์€ ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :
    • ๊ฐœํ–‰ ๋ฌธ์ž ๋ฌธ์ œ: ํŒŒ์ผ ๋งˆ์ง€๋ง‰ ์ค„์— ๊ฐœํ–‰ ๋ฌธ์ž๊ฐ€ ์—†๋Š” ๊ฒƒ์€ ์ผ๋ฐ˜์ ์ธ ์ฝ”๋”ฉ ๊ทœ์น™์— ์–ด๊ธ‹๋‚ฉ๋‹ˆ๋‹ค.
    • ์ฃผ์„ ๋ถ€์žฌ: ์ฝ”๋“œ์˜ ๋ชฉ์ ๊ณผ ํŠน์ • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ฃผ์„์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ํ˜ธ์ถœ๋˜๋Š”์ง€, ์‘๋‹ต์— ํฌํ•จ๋˜๋Š” ๋ฉ”์‹œ์ง€์˜ ์˜๋ฏธ ๋“ฑ์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋กœ๊น… ๋ถ€์žฌ: ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜์—ฌ ๋””๋ฒ„๊น… ๋ฐ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ ๋ฐ ๋ฌธ์ œ์ :

  • ์ผ๋ฐ˜์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ(Exception.class):
    • Exception.class์— ๋Œ€ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋Š” ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ catchํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊นŒ์ง€ catchํ•˜์—ฌ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŠน์ • ์˜ˆ์™ธ์— ๋Œ€ํ•œ ๋” ์„ธ๋ฐ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ๋ฐฉํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
    • ๋ชจ๋“  ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ƒ ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ํฌํ•จ๋  ๊ฒฝ์šฐ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ProblemNotFoundException์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ:
    • ํ˜„์žฌ๋Š” ๋‹จ์ˆœํžˆ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ (์˜ˆ: ์š”์ฒญ URL, ๋ฌธ์ œ ID ๋“ฑ) ์‘๋‹ต์— ํฌํ•จ์‹œ์ผœ ๋””๋ฒ„๊น…์„ ๋•๊ฑฐ๋‚˜, ๋” ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋น„์šฉ: ์˜ˆ์™ธ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ƒํ™ฉ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ์— ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์œผ๋กœ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ์ƒํ™ฉ์„ ์˜ˆ์™ธ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊น… ๋ ˆ๋ฒจ: handleGenericException์—์„œ ์žกํžˆ๋Š” ์˜ˆ์™ธ๋“ค์€ ERROR ๋ ˆ๋ฒจ๋กœ ๋กœ๊น…ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ProblemNotFoundException์€ WARN ๋ ˆ๋ฒจ๋กœ ๋กœ๊น…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ๋…ธ์ถœ: ๋ชจ๋“  ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋‚ด๋ถ€ ์‹œ์Šคํ…œ ์ •๋ณด๋‚˜ ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
    • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์‹œ์ง€๋Š” ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ์„ค๋ช…์œผ๋กœ ์ œํ•œํ•˜๊ณ , ๋‚ด๋ถ€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ๋กœ๊ทธ์—๋งŒ ๊ธฐ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๊ณ ์œ ํ•œ ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋กœ๊ทธ์— ์˜ค๋ฅ˜ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ „์ฒด ์˜ˆ์™ธ ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๋…ธ์ถœ: ์˜ˆ์™ธ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋Š” ๋‚ด๋ถ€ ๊ตฌํ˜„ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  1. ๊ฐœํ–‰ ๋ฌธ์ž ์ถ”๊ฐ€: ํŒŒ์ผ ๋งˆ์ง€๋ง‰ ์ค„์— ๊ฐœํ–‰ ๋ฌธ์ž๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  2. ๋กœ๊น… ์ถ”๊ฐ€: ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊น… ๋ ˆ๋ฒจ์„ ์ ์ ˆํ•˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: Exception.class ๋Œ€์‹  ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ณด์•ˆ ๊ฐ•ํ™”: ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ , ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ์„ค๋ช…์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ๋กœ๊ทธ์—๋งŒ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
  5. ์‘๋‹ต ํฌ๋งท ์ •์˜: ๋ชจ๋“  ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ์ผ๊ด€๋œ ํฌ๋งท์œผ๋กœ ์‘๋‹ตํ•˜๋„๋ก ErrorResponse ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
// src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import site.haruhana.www.exception.ProblemNotFoundException;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(ProblemNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleProblemNotFoundException(ProblemNotFoundException ex) {
        logger.warn("Problem not found: {}", ex.getMessage()); // ๋ฌธ์ œ ID ๋กœ๊น… ์ถ”๊ฐ€ ๊ณ ๋ ค
        ErrorResponse errorResponse = new ErrorResponse("PROBLEM_NOT_FOUND", "๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
        logger.error("Unexpected exception: {}", ex.getMessage(), ex); // ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๋กœ๊น…
        ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", "์„œ๋ฒ„์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// src/main/java/site/haruhana/www/dto/ErrorResponse.java
package site.haruhana.www.dto;

public class ErrorResponse {
    private String errorCode;
    private String errorMessage;

    public ErrorResponse(String errorCode, String errorMessage) {
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
}

์œ„์˜ ์˜ˆ์‹œ ์ฝ”๋“œ๋Š” ๋กœ๊น…, ์—๋Ÿฌ ์‘๋‹ต ํฌ๋งท ์ •์˜, ๋ณด์•ˆ ๊ฐ•ํ™”๋ฅผ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ์„ ๋ฐ˜์˜ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ErrorResponse DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜๋˜๋Š” ์˜ค๋ฅ˜ ์‘๋‹ต์„ ์ผ๊ด€๋œ ํฌ๋งท์œผ๋กœ ์œ ์ง€ํ•˜๊ณ , ์‹ค์ œ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋Š” ๋กœ๊น…์„ ํ†ตํ•ด ํ™•์ธํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. handleGenericException์—์„œ๋Š” ์˜ˆ์™ธ์˜ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ๋กœ๊น…ํ•˜์—ฌ ๋””๋ฒ„๊น…์„ ์šฉ์ดํ•˜๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ๋ฌธ์ œ ID์™€ ๊ฐ™์€ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ๋กœ๊น…์— ํฌํ•จํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ, ๊ฐ€๋…์„ฑ, ๋ณด์•ˆ์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/converter/ProblemConverter.java ๋ฆฌ๋ทฐ

ProblemConverter.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์•ˆ๋…•ํ•˜์„ธ์š”, ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ProblemConverter.java ์ฝ”๋“œ๋ฅผ ๋ฆฌ๋ทฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ „์ฒด์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•˜์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ์ ๊ณผ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ

  • ์žฅ์ :
    • ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. toEntity์™€ toDto ๋ฉ”์„œ๋“œ๋Š” ๊ฐ๊ฐ ProblemDto์—์„œ Problem ์—”ํ‹ฐํ‹ฐ๋กœ, Problem ์—”ํ‹ฐํ‹ฐ์—์„œ ProblemDto๋กœ์˜ ๋ณ€ํ™˜ ๋กœ์ง์„ ์ž˜ ํ‘œํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋นŒ๋” ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด ์ƒ์„ฑ์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ‘œํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
    • @Component ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ Spring Bean์œผ๋กœ ๋“ฑ๋กํ•˜์—ฌ ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ์„ ์ :
    • ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ์˜ ๋ชฉ์ ๊ณผ ๋™์ž‘ ๋ฐฉ์‹์„ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ๊ฐ ํ•„๋“œ๊ฐ€ ์–ด๋–ค ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋Š” ์ฃผ์„์ด ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ:
    • ProblemDto ๋˜๋Š” Problem ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ ์ค‘ ์ผ๋ถ€๊ฐ€ null์ธ ๊ฒฝ์šฐ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, dto.getTitle()์ด null์ด๋ฉด toEntity ๋ฉ”์„œ๋“œ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    • ํ•ด๊ฒฐ์ฑ…: null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, DTO์™€ Entity์—์„œ ํ•ด๋‹น ํ•„๋“œ์˜ @NotNull ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ณ , ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ null ๊ฐ’์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ ๊ฐ€๋Šฅ์„ฑ:
    • Problem ์—”ํ‹ฐํ‹ฐ์— ์ƒˆ๋กœ์šด ํ•„๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์ง€๋งŒ ProblemDto์— ํ•ด๋‹น ํ•„๋“œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ, ๋ณ€ํ™˜ ๊ณผ์ •์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์†์‹ค๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ•ด๊ฒฐ์ฑ…: Problem ์—”ํ‹ฐํ‹ฐ์™€ ProblemDto์˜ ํ•„๋“œ๋ฅผ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๊ณ , ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„ ์ฒ˜๋ฆฌ ๋ถ€์žฌ:
    • ๋งŒ์•ฝ Problem ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์™€ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, toEntity ๋ฉ”์„œ๋“œ์—์„œ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋‹จ๋ฐฉํ–ฅ ๋ณ€ํ™˜๋งŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋ถˆํ•„์š”ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ:
    • ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๊ฐ ๋ณ€ํ™˜๋งˆ๋‹ค ์ƒˆ๋กœ์šด Problem ๋˜๋Š” ProblemDto ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ณ€ํ™˜ ์ž‘์—…์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ, ๊ฐ์ฒด ํ’€๋ง(Object Pooling)์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (ํ•˜์ง€๋งŒ ์ด ๊ฒฝ์šฐ๋Š” ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ๊ณผ๊ฐ€ ๋ฏธ๋ฏธํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.)
  • ๋งคํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ณ ๋ ค:
    • ModelMapper, MapStruct์™€ ๊ฐ™์€ ๋งคํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ๋”์šฑ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งคํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Reflection์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ํ•„๋“œ ๋งคํ•‘์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ํ•„๋“œ๋ฅผ ๋งคํ•‘ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. (๋‹จ, ์„ค์ • ๋ฐ ํ•™์Šต ๋น„์šฉ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ:
    • ProblemDto์— ๋น„๋ฐ€๋ฒˆํ˜ธ, ๊ฐœ์ธ ์ •๋ณด ๋“ฑ ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ •๋ณด๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ํ•ด๊ฒฐ์ฑ…: DTO์— ๋…ธ์ถœํ•ด๋„ ์•ˆ์ „ํ•œ ์ •๋ณด๋งŒ ํฌํ•จ์‹œํ‚ค๊ณ , ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ๋ณ„๋„์˜ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ํ†ตํ•ด ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด ์•”ํ˜ธํ™” ๋“ฑ์˜ ๋ณด์•ˆ ์กฐ์น˜๋ฅผ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection ๊ณต๊ฒฉ:
    • ProblemDto์˜ ํ•„๋“œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ SQL ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ•ด๊ฒฐ์ฑ…: Prepared Statement๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SQL ์ฟผ๋ฆฌ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ตฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์ž๋™์œผ๋กœ Prepared Statement๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. NullPointerException ๋ฐฉ์ง€:

    public Problem toEntity(ProblemDto dto) {
        if (dto == null) {
            return null; // or throw IllegalArgumentException
        }
    
        return Problem.builder()
                .title(dto.getTitle() != null ? dto.getTitle() : "") // or default value
                .description(dto.getDescription() != null ? dto.getDescription() : "")
                .level(dto.getLevel())
                .answer(dto.getAnswer() != null ? dto.getAnswer() : "")
                .problemCategory(dto.getProblemCategory() != null ? dto.getProblemCategory() : "")
                .build();
    }
  2. ์ฃผ์„ ์ถ”๊ฐ€:

    /**
     * ProblemDto๋ฅผ Problem ์—”ํ‹ฐํ‹ฐ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
     *
     * @param dto ProblemDto ๊ฐ์ฒด
     * @return Problem ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด
     */
    public Problem toEntity(ProblemDto dto) {
        // ...
    }
  3. ๋งคํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ณ ๋ ค (MapStruct ์˜ˆ์‹œ):

    • ์˜์กด์„ฑ ์ถ”๊ฐ€ (pom.xml ๋˜๋Š” build.gradle):

      <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct</artifactId>
          <version>1.4.2.Final</version>
      </dependency>
      <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct-processor</artifactId>
          <version>1.4.2.Final</version>
          <scope>provided</scope>
      </dependency>
    • ProblemMapper ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ:

      import org.mapstruct.Mapper;
      import org.mapstruct.factory.Mappers;
      
      @Mapper
      public interface ProblemMapper {
      
          ProblemMapper INSTANCE = Mappers.getMapper(ProblemMapper.class);
      
          Problem toEntity(ProblemDto dto);
      
          ProblemDto toDto(Problem entity);
      }
    • ProblemConverter์—์„œ ProblemMapper ์‚ฌ์šฉ:

      import org.springframework.stereotype.Component;
      import site.haruhana.www.dto.ProblemDto;
      import site.haruhana.www.entity.Problem;
      
      @Component
      public class ProblemConverter {
      
          public Problem toEntity(ProblemDto dto) {
              return ProblemMapper.INSTANCE.toEntity(dto);
          }
      
          public ProblemDto toDto(Problem entity) {
              return ProblemMapper.INSTANCE.toDto(entity);
          }
      }
  4. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€:
    ProblemDto์— @NotNull, @Size ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ , Spring Validation์„ ํ†ตํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

ProblemConverter.java ์ฝ”๋“œ๋Š” ๊ธฐ๋ณธ์ ์ธ ๋ณ€ํ™˜ ๋กœ์ง์„ ์ž˜ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์ง€๋งŒ, NullPointerException ๋ฐฉ์ง€, ์ฃผ์„ ์ถ”๊ฐ€, ๋งคํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ๊ณ ๋ ค, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€ ๋“ฑ์˜ ๊ฐœ์„ ์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ, ๊ฐ€๋…์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋Š” ๊ฐœ๋ฐœ ๋‹จ๊ณ„์—์„œ ํ•ญ์ƒ ์—ผ๋‘์— ๋‘๊ณ , ์ ์ ˆํ•œ ์กฐ์น˜๋ฅผ ์ทจํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ์œผ์‹œ๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

ProblemController.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

๋‹ค์Œ์€ src/main/java/site/haruhana/www/controller/ProblemController.java ํŒŒ์ผ์— ๋Œ€ํ•œ ๋ฆฌ๋ทฐ์ž…๋‹ˆ๋‹ค.

1. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐ€๋…์„ฑ:

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Lombok์˜ @RequiredArgsConstructor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•œ ์ ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋ช…๋ช… ๊ทœ์น™ ์ค€์ˆ˜: ๋ณ€์ˆ˜, ๋ฉ”์„œ๋“œ ์ด๋ฆ„๋“ค์ด ์ง๊ด€์ ์ด๊ณ  ์˜๋ฏธ๋ฅผ ์ž˜ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ์ผ๊ด€์„ฑ ์œ ์ง€: BaseResponse๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต ํ˜•์‹์„ ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„ ํ™œ์šฉ ๋ถ€์กฑ: ์ฝ”๋“œ ์ž์ฒด๋Š” ๋น„๊ต์  ๋ช…ํ™•ํ•˜์ง€๋งŒ, ํŠน์ • ๋กœ์ง์ด๋‚˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์˜ ์ด์œ  ๋“ฑ์— ๋Œ€ํ•œ ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํ–ฅ์ƒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ :

  • getAllProblems ๋ฉ”์„œ๋“œ: problems.isEmpty() ์ฒดํฌ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. BaseResponse.onSuccess๋Š” ํ•ญ์ƒ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์กฐ๊ฑด๋ฌธ ์—†์ด๋„ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. (์•„๋ž˜ ๊ฐœ์„  ์ œ์•ˆ ์ฐธ๊ณ )
  • getProblemsByCategory ๋ฉ”์„œ๋“œ: Page<Problem>์„ ๋ฐ›์•„์™€์„œ List<Problem>์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์ด ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ํ•„์š”ํ•œ ์ •๋ณด๋Š” ํŽ˜์ด์ง• ์ •๋ณด์™€ ๋ฌธ์ œ ๋ชฉ๋ก์ด๋ฏ€๋กœ Page<Problem> ์ž์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. (์•„๋ž˜ ๊ฐœ์„  ์ œ์•ˆ ์ฐธ๊ณ )
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: createProblem ๋ฉ”์„œ๋“œ์—์„œ Exception์„ catchํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๊ด‘๋ฒ”์œ„ํ•ฉ๋‹ˆ๋‹ค. ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ (์˜ˆ: DataIntegrityViolationException, IllegalArgumentException ๋“ฑ)๋ฅผ catchํ•˜์—ฌ ์ ์ ˆํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, createProblem์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ƒ ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ: ProblemService ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์ž‘์—…์ด ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์— ๊ฑธ์ณ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ, ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ์ •ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์ด ๋ถ€๋ถ„์€ ProblemService ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด์•ผ ์ •ํ™•ํžˆ ํŒ๋‹จ ๊ฐ€๋Šฅ)
  • ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ: ProblemDto์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌธ์ œ ์ œ๋ชฉ์ด๋‚˜ ๋‚ด์šฉ์ด ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด @NotBlank, @Size ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:

  • N+1 ๋ฌธ์ œ: ProblemService์—์„œ Problem ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€๋œ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ๋“ค์„ ํ•จ๊ป˜ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JPA์˜ Fetch Join์ด๋‚˜ Entity Graph ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๋„๋ก ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด ๋ถ€๋ถ„์€ ProblemService ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด์•ผ ์ •ํ™•ํžˆ ํŒ๋‹จ ๊ฐ€๋Šฅ)
  • ์บ์‹ฑ: ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ๋ฌธ์ œ๋Š” ์บ์‹ฑ์„ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Cache Abstraction์„ ํ™œ์šฉํ•˜์—ฌ ์บ์‹ฑ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง• ์ตœ์ ํ™”: ProblemService์—์„œ ํŽ˜์ด์ง• ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ, Count ์ฟผ๋ฆฌ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Count ์ฟผ๋ฆฌ๋ฅผ ์ตœ์ ํ™”ํ•˜๊ฑฐ๋‚˜, ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์‹คํ–‰ํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด ๋ถ€๋ถ„์€ ProblemService ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด์•ผ ์ •ํ™•ํžˆ ํŒ๋‹จ ๊ฐ€๋Šฅ)

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ:

  • ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ: ProblemDto๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜๋Š” ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. SQL Injection, XSS ๊ณต๊ฒฉ ๋“ฑ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์„ ๊ฐ•ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ HTML ํƒœ๊ทธ๋‚˜ ํŠน์ˆ˜๋ฌธ์ž๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋Š” ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋Š” ํŠน๋ณ„ํ•œ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ด€๋ฆฌ: ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌธ์ œ๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•ด ํŠน์ • ์‚ฌ์šฉ์ž๋งŒ ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Security ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋…ธ์ถœ: createProblem์—์„œ e.getMessage()๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ƒ ์ทจ์•ฝ์ ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์—๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ:

  • getAllProblems ๋ฉ”์„œ๋“œ ๊ฐœ์„ :

    @GetMapping
    public BaseResponse<Page<ProblemDto>> getAllProblems(Pageable pageable) {
        Page<ProblemDto> problems = problemService.getAllProblems(pageable);
        return BaseResponse.onSuccess("๋ฌธ์ œ ๋ชฉ๋ก ์กฐํšŒ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค", problems);
    }
  • getProblemsByCategory ๋ฉ”์„œ๋“œ ๊ฐœ์„ :

    @GetMapping("/category")
    public BaseResponse<Page<Problem>> getProblemsByCategory(
            @RequestParam("category") ProblemCategory category,
            @PageableDefault Pageable pageable) {
        Page<Problem> page = problemService.getProblemsByCategory(category, pageable);
    
        if (page.isEmpty()) {
            return BaseResponse.onSuccess(
                    String.format("%s ์นดํ…Œ๊ณ ๋ฆฌ์— ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", category),
                    page
            );
        }
    
        return BaseResponse.onSuccess(
                String.format("%s ์นดํ…Œ๊ณ ๋ฆฌ ๋ฌธ์ œ ์กฐํšŒ ์„ฑ๊ณต (์ด %d๊ฐœ)",
                        category, page.getTotalElements()),
                page
        );
    }
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐœ์„ : createProblem ๋ฉ”์„œ๋“œ์—์„œ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ๋ฅผ catchํ•˜๊ณ , ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    @PostMapping
    public BaseResponse<ProblemDto> createProblem(@RequestBody ProblemDto problemDto) {
        try {
            ProblemDto savedProblem = problemService.createProblem(problemDto);
            return BaseResponse.onCreate("๋ฌธ์ œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", savedProblem);
        } catch (DataIntegrityViolationException e) {
            return BaseResponse.onBadRequest("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜ค๋ฅ˜๋กœ ๋ฌธ์ œ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
        } catch (IllegalArgumentException e) {
            return BaseResponse.onBadRequest("์ž˜๋ชป๋œ ์ž…๋ ฅ ๊ฐ’์œผ๋กœ ๋ฌธ์ œ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
        } catch (Exception e) {
            // ๋กœ๊น… ์ฒ˜๋ฆฌ ์ถ”๊ฐ€
            return BaseResponse.onBadRequest("๋ฌธ์ œ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
        }
    }
  • ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ถ”๊ฐ€: ProblemDto์— @NotBlank, @Size ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    public class ProblemDto {
        @NotBlank(message = "์ œ๋ชฉ์€ ํ•„์ˆ˜ ์ž…๋ ฅ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.")
        @Size(min = 5, max = 255, message = "์ œ๋ชฉ์€ 5์ž ์ด์ƒ 255์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
        private String title;
    
        // ๋‹ค๋ฅธ ํ•„๋“œ๋“ค๋„ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์–ด๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€
    }
  • ๋ณด์•ˆ ๊ฐ•ํ™”: ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ, ๊ถŒํ•œ ๊ด€๋ฆฌ, ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋…ธ์ถœ ๋ฐฉ์ง€ ๋“ฑ์˜ ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก :

์ „๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋Š” ์ž˜ ์ž‘์„ฑ๋˜์—ˆ์ง€๋งŒ, ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๊ฐœ์„  ์‚ฌํ•ญ๋“ค์„ ์ ์šฉํ•˜๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ฑ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ์œ ํšจ์„ฑ ๊ฒ€์ฆ, ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ๋Š” ์ฃผ์˜ ๊นŠ๊ฒŒ ๊ฒ€ํ† ํ•˜๊ณ  ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ProblemService ์ฝ”๋“œ์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ํ•จ๊ป˜ ๊ฒ€ํ† ํ•˜๋ฉด ๋” ์ •ํ™•ํ•˜๊ณ  ๊ตฌ์ฒด์ ์ธ ๋ฆฌ๋ทฐ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/entity/Problem.java ๋ฆฌ๋ทฐ

src/main/java/site/haruhana/www/entity/Problem.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ์ œ์‹œ๋œ ์ฝ”๋“œ ์กฐ๊ฐ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ๋ฆฌ๋ทฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ผ๋ถ€๋งŒ ์ฃผ์–ด์ ธ ์žˆ์–ด์„œ ์ œํ•œ์ ์ธ ๋ถ€๋ถ„๋„ ์žˆ๋‹ค๋Š” ์ ์„ ๊ฐ์•ˆํ•ด์ฃผ์„ธ์š”.

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ œํ•œ์ ์ธ ์ •๋ณด: ์ฝ”๋“œ ์กฐ๊ฐ๋งŒ์œผ๋กœ๋Š” ์ „์ฒด์ ์ธ ํ’ˆ์งˆ์„ ํ‰๊ฐ€ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. Entity ํด๋ž˜์Šค์˜ ์ผ๋ถ€ ๋ฉ”์†Œ๋“œ๋งŒ ์ œ๊ณต๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ๊ฐ€๋…์„ฑ: ์ฝ”๋“œ๋Š” ๋น„๊ต์  ์งง๊ณ  ๊ฐ„๋‹จํ•˜์—ฌ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„: ์ฃผ์„์ด ์ž˜ ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ update ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ํฌํ•จํ•˜์—ฌ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค. @param ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์€ ์ข‹์€ ์Šต๊ด€์ž…๋‹ˆ๋‹ค.
  • ๋ช…๋ช… ๊ทœ์น™: ๋ณ€์ˆ˜ ์ด๋ฆ„ (updatedProblemDto, title, description ๋“ฑ)์€ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” Java ๋ช…๋ช… ๊ทœ์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • NullPointerException ๊ฐ€๋Šฅ์„ฑ: updatedProblemDto๊ฐ€ null์ธ ๊ฒฝ์šฐ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. updatedProblemDto.getTitle() ๋“ฑ์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— null ์ฒดํฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•„๋“œ ๋ณ€๊ฒฝ ๋ˆ„๋ฝ: ์ฝ”๋“œ ์กฐ๊ฐ๋งŒ์œผ๋กœ๋Š” ์•Œ ์ˆ˜ ์—†์ง€๋งŒ, Entity์— ๋” ๋งŽ์€ ํ•„๋“œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. update ๋ฉ”์†Œ๋“œ์—์„œ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๊ฐฑ์‹ ํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ƒ์„ฑ์ผ์‹œ, ์ˆ˜์ •์ผ์‹œ ๋“ฑ์˜ ํ•„๋“œ ์—…๋ฐ์ดํŠธ ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ถ€์žฌ: updatedProblemDto์˜ ๊ฐ’์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, level์ด ํ—ˆ์šฉ๋œ ๋ฒ”์œ„ ๋‚ด์˜ ๊ฐ’์ธ์ง€, title์˜ ๊ธธ์ด๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์ง€ ์•Š์€์ง€ ๋“ฑ์„ ๊ฒ€์‚ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฒ€์ฆ ๋กœ์ง์ด ์—†์œผ๋ฉด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ProblemCategory enum ํƒ€์ž… ๊ณ ๋ ค: ProblemCategory๊ฐ€ enum ํƒ€์ž…์ด๋ผ๋ฉด, updatedProblemDto.getProblemCategory()์˜ ๋ฐ˜ํ™˜๊ฐ’์ด null์ด ์•„๋‹Œ์ง€, ์œ ํšจํ•œ enum ๊ฐ’์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ฌธ์ž์—ด ๋“ฑ์œผ๋กœ ๋ฐ›์•„์„œ ๋ณ€ํ™˜ํ•œ๋‹ค๋ฉด ๋ณ€ํ™˜ ์‹คํŒจ์— ๋Œ€ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ: ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋ฌด์กฐ๊ฑด ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋Œ€์‹ , ์‹ค์ œ๋กœ ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์ด ์„ฑ๋Šฅ์ƒ ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. updatedProblemDto์™€ ํ˜„์žฌ Problem ๊ฐ์ฒด์˜ ๊ฐ’์„ ๋น„๊ตํ•˜์—ฌ ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—…๋ฐ์ดํŠธ ํšŸ์ˆ˜๋ฅผ ์ค„์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•„๋“œ ๋ถˆ๋ณ€์„ฑ ๊ณ ๋ ค: Entity์—์„œ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•„์•ผ ํ•˜๋Š” ํ•„๋“œ(์˜ˆ: ์ƒ์„ฑ์ผ์‹œ)๊ฐ€ ์žˆ๋‹ค๋ฉด update ๋ฉ”์†Œ๋“œ์—์„œ ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • SQL Injection: title ๋˜๋Š” description์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ํฌํ•จ๋  ๊ฒฝ์šฐ SQL Injection ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ORM ํ”„๋ ˆ์ž„์›Œํฌ(Hibernate, JPA ๋“ฑ)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ํ†ตํ•ด ์ž๋™์œผ๋กœ ๋ฐฉ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ง์ ‘ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ PreparedStatement๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Cross-Site Scripting (XSS): title ๋˜๋Š” description์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ํฌํ•จ๋  ๊ฒฝ์šฐ XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. HTML ํƒœ๊ทธ๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•˜๊ฑฐ๋‚˜ ์ ์ ˆํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ๊ฐ€(Authorization) ์ฒดํฌ ๋ถ€์žฌ: update ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น Problem Entity๋ฅผ ์ˆ˜์ •ํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค. ์ธ๊ฐ€ ์ •์ฑ…์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๊ถŒํ•œ ์ฒดํฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  • Null ์ฒดํฌ: updatedProblemDto์™€ ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ null ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Objects.requireNonNull(updatedProblemDto, "updatedProblemDto cannot be null")๊ณผ ๊ฐ™์ด Guava ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ Objects.requireNonNull ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: Bean Validation API (JSR 303)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ProblemDto์— @NotNull, @Size, @Min, @Max ๋“ฑ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ , update ๋ฉ”์†Œ๋“œ์—์„œ Validator๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•„๋“œ ๋น„๊ต ์—…๋ฐ์ดํŠธ: ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ๋กœ์ง์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ถˆ๋ณ€ ํ•„๋“œ ๋ณดํ˜ธ: ๋ถˆ๋ณ€ ํ•„๋“œ๋Š” update ๋ฉ”์†Œ๋“œ์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณด์•ˆ ๊ฐ•ํ™”: SQL Injection ๋ฐ XSS ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ๋ฐฉ์–ด ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ๊ฐ€(Authorization) ๋กœ์ง: update ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ ์ „์— ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. Spring Security์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Audit ํ•„๋“œ ์ž๋™ ์—…๋ฐ์ดํŠธ: ์ƒ์„ฑ์ผ์‹œ, ์ˆ˜์ •์ผ์‹œ ๋“ฑ์˜ audit ํ•„๋“œ๋Š” Entity Listener ๋˜๋Š” Interceptor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: update ๋ฉ”์†Œ๋“œ ๋‚ด์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ(์˜ˆ: ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ)์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Custom Exception์„ ์ •์˜ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ: update ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฝ”๋“œ์˜ ์ •ํ™•์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ ์ฝ”๋“œ (์ผ๋ถ€)

import lombok.Builder;
import site.haruhana.www.dto.ProblemDto;

import java.util.Objects;

@Builder
public class Problem {

    private String title;
    private String description;
    private Integer level;
    private String problemCategory; // Enum ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ ค

    /**
     * @param updatedProblemDto ๊ฐฑ์‹ ํ•  ๋ฌธ์ œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” DTO
     */
    public void update(ProblemDto updatedProblemDto) {
        Objects.requireNonNull(updatedProblemDto, "updatedProblemDto cannot be null");

        if (updatedProblemDto.getTitle() != null && !this.title.equals(updatedProblemDto.getTitle())) {
            this.title = updatedProblemDto.getTitle();
        }
        if (updatedProblemDto.getDescription() != null && !this.description.equals(updatedProblemDto.getDescription())) {
            this.description = updatedProblemDto.getDescription();
        }
        if (updatedProblemDto.getLevel() != null && !this.level.equals(updatedProblemDto.getLevel())) {
            this.level = updatedProblemDto.getLevel();
        }
        if (updatedProblemDto.getProblemCategory() != null && !this.problemCategory.equals(updatedProblemDto.getProblemCategory())) {
            this.problemCategory = updatedProblemDto.getProblemCategory();
        }
    }
}

๊ฒฐ๋ก 

์ฝ”๋“œ ์ž์ฒด๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ, ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ๊ณผ ๊ฐœ์„ ํ•ด์•ผ ํ•  ๋ถ€๋ถ„๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. Null ์ฒดํฌ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ๋ณด์•ˆ ๊ฐ•ํ™”, ์„ฑ๋Šฅ ๊ฐœ์„  ๋“ฑ์„ ํ†ตํ•ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ๊ณผ ํšจ์œจ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ „์ฒด ์ฝ”๋“œ ๋งฅ๋ฝ์„ ํŒŒ์•…ํ•˜์—ฌ ๋ˆ„๋ฝ๋œ ํ•„๋“œ ์—…๋ฐ์ดํŠธ๋‚˜ ๋‹ค๋ฅธ ์ž ์žฌ์  ๋ฌธ์ œ์ ๋“ค์„ ์ฐพ์•„ ํ•ด๊ฒฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/service/ProblemService.java ๋ฆฌ๋ทฐ

ProblemService.java ์ฝ”๋“œ ๋ฆฌ๋ทฐ

1. ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ๊ฐ€๋…์„ฑ

  • ์ „๋ฐ˜์ ์œผ๋กœ ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ ์Šคํƒ€์ผ์ด ์ผ๊ด€์ ์ด๊ณ , Lombok์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์˜€์œผ๋ฉฐ, SLF4J ๋กœ๊น…์„ ํ†ตํ•ด ๋””๋ฒ„๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์šฉ์ดํ•˜๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ฃผ์„์ด ์ƒ์„ธํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์ด ๋ช…ํ™•ํ•˜๋ฉฐ, ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋ฐ˜ํ™˜ ๊ฐ’์— ๋Œ€ํ•œ ์ •๋ณด๋„ ์ž˜ ์ œ๊ณต๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ์˜ ๋ชฉ์ ์„ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ช…๋ช…๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • ProblemConverter์˜ ์—ญํ• ์ด ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. DTO์™€ Entity ๊ฐ„์˜ ๋ณ€ํ™˜ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ Service ํด๋ž˜์Šค์˜ ์ฑ…์ž„์„ ์ค„์˜€์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž ์žฌ์ ์ธ ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ 

  • updateProblem ๋ฉ”์„œ๋“œ์—์„œ problemRepository.save(problem)์ด ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋˜์—ˆ์œผ๋ฏ€๋กœ, Entity์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ ์‹œ ์ž๋™์œผ๋กœ DB์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์ธ save ํ˜ธ์ถœ์€ ๋ถˆํ•„์š”ํ•˜๋ฉฐ, ์˜คํžˆ๋ ค ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜๋ฅผ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. problem.update(newProblemDto) ๋‚ด๋ถ€ ๊ตฌํ˜„์ด Entity์— ๊ฐ’์„ ์ง์ ‘ ํ• ๋‹นํ•˜๋Š” ๋ฐฉ์‹์ด์–ด์•ผ @transactional์— ์˜ํ•ด ๋ณ€๊ฒฝ๊ฐ์ง€๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • deleteProblem ๋ฉ”์„œ๋“œ์˜ try-catch ๋ธ”๋ก์ด ๊ณผ๋„ํ•ฉ๋‹ˆ๋‹ค. ProblemNotFoundException์„ ์žก์•„์„œ ๋‹ค์‹œ ๋˜์ง€๋Š” ๊ฒƒ์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. existsById ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋ฆฌ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ–ˆ์œผ๋ฏ€๋กœ, deleteById ๋ฉ”์„œ๋“œ์—์„œ EmptyResultDataAccessException์ด ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ฐœ์ƒํ•˜๋”๋ผ๋„, Spring์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค. RuntimeException์œผ๋กœ ๋ž˜ํ•‘ํ•˜๋Š” ๋ถ€๋ถ„์€ ํ•„์š”ํ•˜๋‹ค๊ณ  ํŒ๋‹จ๋ฉ๋‹ˆ๋‹ค. DB layer์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ๋ฅผ service layer์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ด RuntimeException์€ ์ข€ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (ex. ProblemDeletionException)
  • getProblemsByCategory ๋ฉ”์„œ๋“œ๊ฐ€ Problem Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. API ์‘๋‹ต์œผ๋กœ Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Entity๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด API ๊ณ„์•ฝ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋  ์œ„ํ—˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

3. ์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ

  • getAllProblems ๋ฉ”์„œ๋“œ์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ Problem Entity๊ฐ€ ๋‹ค๋ฅธ Entity์™€ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ๊ฐ Problem์„ ProblemDto๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ถ”๊ฐ€์ ์ธ DB ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. fetch join์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜, DTO projection์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemById ๋ฉ”์„œ๋“œ์—์„œ problemConverter.toDto ํ˜ธ์ถœ ์ „์— ๋กœ๊น…์„ ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. ProblemConverter ๋‚ด๋ถ€์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋กœ๊ทธ๊ฐ€ ๊ธฐ๋ก๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • getProblemsByCategory์—์„œ category ๋กœ๊ทธ๋ฅผ ์ฐ์„ ๋•Œ toString()์ด ์•„๋‹Œ category name์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ProblemCategory๊ฐ€ enum์ด๋ผ๋ฉด problemCategory.name() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

4. ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ

  • DTO๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ์ œํ•œํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ๋ณธ์ ์ธ ๋ณด์•ˆ ์กฐ์น˜์ž…๋‹ˆ๋‹ค. Entity๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์˜๋„ํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. createProblem, updateProblem ๋ฉ”์„œ๋“œ์—์„œ problemDto์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜์—ฌ ์•…์˜์ ์ธ ๋ฐ์ดํ„ฐ๊ฐ€ DB์— ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Spring Validation์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. updateProblem, deleteProblem ๋ฉ”์„œ๋“œ๋Š” ํŠน์ • ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ตฌ์ฒด์ ์ธ ์ œ์•ˆ

  1. updateProblem ๋ฉ”์„œ๋“œ์—์„œ problemRepository.save(problem) ํ˜ธ์ถœ ์ œ๊ฑฐ: @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    @Transactional
    public ProblemDto updateProblem(Long id, ProblemDto newProblemDto) {
        log.info("๋ฌธ์ œ ์ˆ˜์ • ์‹œ์ž‘ - ๋ฌธ์ œ ID: {}", id);
        Problem problem = problemRepository.findById(id)
                .orElseThrow(() -> new ProblemNotFoundException("์ˆ˜์ •ํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
    
        problem.update(newProblemDto); // problem ๋‚ด๋ถ€์—์„œ setter๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ
    
        log.info("๋ฌธ์ œ ์ˆ˜์ • ์™„๋ฃŒ - ๋ฌธ์ œ ID: {}", id);
    
        return problemConverter.toDto(problem);
    }
  2. deleteProblem ๋ฉ”์„œ๋“œ์˜ try-catch ๋ธ”๋ก ๊ฐ„์†Œํ™” ๋ฐ ์˜ˆ์™ธ ๊ตฌ์ฒดํ™”: ๋ถˆํ•„์š”ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

    public void deleteProblem(Long id) {
        log.info("๋ฌธ์ œ ์‚ญ์ œ ์‹œ์ž‘ - ๋ฌธ์ œ ID: {}", id);
        if (!problemRepository.existsById(id)) {
            throw new ProblemNotFoundException("์‚ญ์ œํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
        }
    
        try {
            problemRepository.deleteById(id);
            log.info("๋ฌธ์ œ ์‚ญ์ œ ์™„๋ฃŒ - ๋ฌธ์ œ ID: {}", id);
        } catch (Exception e) {
            log.error("๋ฌธ์ œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ - ๋ฌธ์ œ ID: {}, ์˜ค๋ฅ˜: {}", id, e.getMessage());
            throw new ProblemDeletionException("๋ฌธ์ œ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", e); // ์ข€ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ์™ธ ์‚ฌ์šฉ
        }
    }
  3. getProblemsByCategory ๋ฉ”์„œ๋“œ๊ฐ€ ProblemDto ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •: Entity ๋Œ€์‹  DTO๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ API ๊ณ„์•ฝ์„ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค.

    public Page<ProblemDto> getProblemsByCategory(ProblemCategory problemCategory, Pageable pageable) {
        log.info("์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ์กฐํšŒ ์‹œ์ž‘ - ์นดํ…Œ๊ณ ๋ฆฌ: {}", problemCategory);
        Page<Problem> problems = problemRepository.findByProblemCategory(problemCategory, pageable);
    
        log.info("์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ์กฐํšŒ ์™„๋ฃŒ - ์นดํ…Œ๊ณ ๋ฆฌ: {}, ์กฐํšŒ๋œ ๋ฌธ์ œ ์ˆ˜: {}",
                problemCategory, problems.getTotalElements());
        return problems.map(problemConverter::toDto);
    }
  4. getAllProblems ๋ฉ”์„œ๋“œ์—์„œ N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ: fetch join ๋˜๋Š” DTO projection์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, fetch join์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ Repository ์ธํ„ฐํŽ˜์ด์Šค์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @Repository
    public interface ProblemRepository extends JpaRepository<Problem, Long> {
        @Query("SELECT p FROM Problem p JOIN FETCH p.category WHERE p.id = :id")
        Optional<Problem> findByIdWithCategory(@Param("id") Long id);
    
        Page<Problem> findByProblemCategory(ProblemCategory problemCategory, Pageable pageable);
    
        @Query("SELECT p FROM Problem p")
        Page<Problem> findAllProblems(Pageable pageable);
    }

    ๊ทธ๋ฆฌ๊ณ  getAllProblems ๋ฉ”์„œ๋“œ์—์„œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (DTO projection ์˜ˆ์‹œ๋Š” ์ƒ๋žต)

    public Page<ProblemDto> getAllProblems(Pageable pageable) {
        return problemRepository.findAllProblems(pageable)
                .map(problemConverter::toDto);
    }
  5. ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ์ถ”๊ฐ€: createProblem, updateProblem ๋ฉ”์„œ๋“œ์—์„œ problemDto์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. @Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ Spring Validation์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    public ProblemDto createProblem(@Valid ProblemDto problemDto) {
        // ...
    }
    
    public ProblemDto updateProblem(Long id, @Valid ProblemDto newProblemDto) {
        // ...
    }
  6. ๊ถŒํ•œ ๊ฒ€์‚ฌ ์ถ”๊ฐ€: updateProblem, deleteProblem ๋ฉ”์„œ๋“œ์— ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฌด๋‹จ ์ ‘๊ทผ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Role ๊ธฐ๋ฐ˜์˜ ์ ‘๊ทผ ์ œ์–ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  7. ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๊ทธ๋ฅผ ์ฐ์„ ๋•Œ category name์„ ์ง์ ‘ ์‚ฌ์šฉ:

    log.info("์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ฌธ์ œ ์กฐํšŒ ์‹œ์ž‘ - ์นดํ…Œ๊ณ ๋ฆฌ: {}", problemCategory.name());

์ด๋Ÿฌํ•œ ์ œ์•ˆ๋“ค์„ ํ†ตํ•ด ์ฝ”๋“œ ํ’ˆ์งˆ, ์„ฑ๋Šฅ, ๋ณด์•ˆ์„ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์ œ์•ˆ์€ ํ”„๋กœ์ ํŠธ์˜ ์ƒํ™ฉ๊ณผ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์ ์ ˆํžˆ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Copy link

src/main/java/site/haruhana/www/entity/Problem.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/controller/ProblemController.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/exception/ProblemNotFoundException.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/controller/GlobalExceptionHandler.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/repository/ProblemRepository.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/converter/ProblemConverter.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/test/java/site/haruhana/www/service/ProblemServiceTest.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/service/ProblemService.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Copy link

src/main/java/site/haruhana/www/dto/ProblemDto.java ๋ฆฌ๋ทฐ

โš ๏ธ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: [GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).
๋‹ค์‹œ ์‹œ๋„ํ•˜์‹œ๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•ด์ฃผ์„ธ์š”.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant