๐Ÿ“Œโ€œ๋ฆฌ๋ทฐ๋Š” ๋‹จ์ˆœ ๋Œ“๊ธ€์ด ์•„๋‹ˆ๋‹ค โ€“ ReviewService๋กœ ๋ณธ ๊ถŒํ•œ ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ์‹œ์Šคํ…œโ€

๐Ÿ—“ 2025๋…„ 5์›” โœ๏ธ by ๋ฐ•์ฐฌํฌ

๐Ÿงญ ๊ฐœ๋ฐœ ๋™๊ธฐ: ๋ฆฌ๋ทฐ๋Š” ์‹ ๋ขฐ ๊ธฐ๋ฐ˜์˜ ์ฝ˜ํ…์ธ ์ด๋‹ค

๋งŽ์€ ์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ์—์„œ ๋ฆฌ๋ทฐ๋Š” ๋‹จ์ˆœํ•œ ํ‰๊ฐ€ ๊ธฐ๋Šฅ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ,

์‹ค์ œ๋ก  ์‚ฌ์šฉ์ž์˜ ์‹ ๋ขฐ, ๊ฐ•์‚ฌ์˜ ํ‰ํŒ, ์„œ๋น„์Šค ์„ฑ์žฅ์˜ ํ•ต์‹ฌ ์ถ•์ด๋‹ค.

์ด๋ฒˆ์— ๊ตฌํ˜„ํ•œ ReviewService๋Š” ๋‹จ์ˆœํ•œ ๋Œ“๊ธ€ ์ €์žฅ ๊ธฐ๋Šฅ์ด ์•„๋‹Œ,

โ€œ์ˆ˜๊ฐ•์ž๋งŒ ์ž‘์„ฑ ๊ฐ€๋Šฅ + ์ค‘๋ณต ๋ฆฌ๋ทฐ ๋ฐฉ์ง€ + ์ž‘์„ฑ ์‹œ๊ฐ„ ํฌ๋งท ์ฒ˜๋ฆฌ + ๊ตฌ์กฐ์  ํ™•์žฅ์„ฑ ํ™•๋ณดโ€

๋ฅผ ๋ชฉํ‘œ๋กœ ์„ค๊ณ„๋œ ๋ฆฌ๋ทฐ ๊ด€๋ฆฌ ๋ชจ๋“ˆ์ด๋‹ค.


๐ŸŽฏ ํ•ต์‹ฌ ์„ค๊ณ„ ์š”์•ฝ

๊ธฐ๋Šฅ ์„ค๊ณ„ ๋ฐฉํ–ฅ
๋ฆฌ๋ทฐ ์กฐํšŒ ์ž‘์„ฑ์ผ์ž ํฌ๋งท ํฌํ•จํ•˜์—ฌ ์‚ฌ์šฉ์ž ์นœํ™”์ ์œผ๋กœ ์ œ๊ณต
๋ฆฌ๋ทฐ ๋“ฑ๋ก SRP ๊ธฐ๋ฐ˜, ์ค‘๋ณต ์—†๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ
๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ถŒํ•œ ์ฒดํฌ ์ˆ˜๊ฐ• ์—ฌ๋ถ€ + ์ค‘๋ณต ์ž‘์„ฑ ์—ฌ๋ถ€ ์ด์ค‘ ๊ฒ€์ฆ
DAO ๋ถ„๋ฆฌ DB ์ž‘์—…๊ณผ ์„œ๋น„์Šค ๋กœ์ง์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ
์˜ˆ์™ธ/ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์ถ”ํ›„ ํ™•์žฅ ๊ฐ€๋Šฅ ๊ตฌ์กฐ ํ™•๋ณด (Spring ๋Œ€์‘ ๊ณ ๋ ค)

๐Ÿ”ง ์‚ฌ์šฉ ๊ธฐ์ˆ  ์Šคํƒ

ํ•ญ๋ชฉ ๋‚ด์šฉ
์–ธ์–ด Java 17
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค MySQL
ORM ๋ฐฉ์‹ ์ง์ ‘ DAO ๋ฐฉ์‹ (MyBatis ์•„๋‹˜)
์‹œ๊ฐ„ ์ฒ˜๋ฆฌ LocalDateTime + DateTimeFormatter
๊ตฌ์กฐ ํŒจํ„ด Service Layer + DAO + DTO
์˜ˆ์™ธ ์ฒ˜๋ฆฌ RuntimeException ๊ธฐ๋ฐ˜, ๊ธ€๋กœ๋ฒŒ ์˜ˆ์™ธ ์ „ํŒŒ ๊ณ ๋ ค

๐Ÿง  ์ฃผ์š” ๋กœ์ง ๊ตฌํ˜„

1๏ธโƒฃ ๋ฆฌ๋ทฐ ์กฐํšŒ: getReviewsByLectureId

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
for (ReviewDTO dto : list) {
    if (dto.getCreatedAt() != null) {
        dto.setCreatedAtFormatted(dto.getCreatedAt().format(formatter));
    } else {
        dto.setCreatedAtFormatted("");
    }
}

โœ… ์„œ๋ฒ„ ๋‹จ์—์„œ ๋‚ ์งœ ํฌ๋งท ์ฒ˜๋ฆฌ โ†’ ํ”„๋ก ํŠธ๋Š” ๋‹จ์ˆœ ์ถœ๋ ฅ๋งŒ ๋‹ด๋‹น

โœ… null ๋ฐฉ์–ด๋กœ ์˜ˆ์™ธ ์•ˆ์ „์„ฑ ํ™•๋ณด

๐Ÿ“Œ ์‚ฌ์šฉ์ž UX ๊ด€์ ์—์„œ โ€œ๋ฆฌ๋ทฐ ์‹œ๊ฐ„ ํ‘œ์‹œโ€๋Š” ์ฝ˜ํ…์ธ  ์‹ ๋ขฐ๋„์— ํฐ ์˜ํ–ฅ์„ ์ค€๋‹ค.


2๏ธโƒฃ ๋ฆฌ๋ทฐ ๋“ฑ๋ก: insertReview & addReview

public void addReview(ReviewDTO dto) {
    reviewDAO.insertReview(dto);
}

โœ… ํ–ฅํ›„ API ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋”๋ผ๋„ ๋‚ด๋ถ€ ๋กœ์ง์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ ๊ฐ€๋Šฅ


3๏ธโƒฃ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ถŒํ•œ ์ฒดํฌ: canWriteReview

public boolean canWriteReview(int userId, int lectureId) {
    return hasEnrolled(userId, lectureId) && !hasReviewed(userId, lectureId);
}

โœ… ์‹ค๋ฌด์—์„œ๋Š” ๋ฆฌ๋ทฐ ์กฐ์ž‘ ๋ฐฉ์ง€์™€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๋ณดํ˜ธ๋ฅผ ์œ„ํ•œ ํ•„์ˆ˜ ์„ค๊ณ„ ํฌ์ธํŠธ


4๏ธโƒฃ ๊ตฌ์กฐ ๋ถ„๋ฆฌ: DAO ๊ธฐ๋ฐ˜ ๊ณ„์ธตํ™” ์„ค๊ณ„

this.reviewDAO = new ReviewDAO(); // DAO ์ฃผ์ž…
๊ณ„์ธต ์—ญํ• 
Controller ์‚ฌ์šฉ์ž ์š”์ฒญ ๋ถ„๊ธฐ (์˜ˆ: /review/register)
Service (ReviewService) ๋น„์ฆˆ๋‹ˆ์Šค ํŒ๋‹จ (๊ถŒํ•œ, ์ค‘๋ณต ์—ฌ๋ถ€, ํฌ๋งท ์ฒ˜๋ฆฌ ๋“ฑ)
DAO (ReviewDAO) DB ์ž‘์—… (insert, select, count ๋“ฑ)
DTO (ReviewDTO) ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๊ฐ์ฒด

โœ… ์œ ์ง€๋ณด์ˆ˜ ์‹œ ์„œ๋น„์Šค ๋กœ์ง๋งŒ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

โœ… DB ๋ณ€๊ฒฝ์ด ์žˆ์–ด๋„ DAO๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋จ โ†’ ๊ณ„์ธต ๋…๋ฆฝ์„ฑ ํ™•๋ณด


๐Ÿ” ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ํŠธ๋žœ์žญ์…˜ ์ „๋žต

try {
   ...
} catch (Exception e) {
   throw new RuntimeException("โŒ ๋ฆฌ๋ทฐ ๋“ฑ๋ก ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ", e);
}

โ–ถ ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋Š” ๋กœ๊น… ์‹œ์Šคํ…œ ์—ฐ๋™์„ ์œ„ํ•œ ์ถœ๋ฐœ์ ์ด๋ฉฐ,

ํ–ฅํ›„ AOP ๊ธฐ๋ฐ˜ ๊ธ€๋กœ๋ฒŒ ์˜ˆ์™ธ ํ•ธ๋“ค๋ง ๊ตฌ์กฐ๋กœ๋„ ํ™•์žฅ ๊ฐ€๋Šฅ


๐Ÿ“ˆ ์‹ค๋ฌด ์„ค๊ณ„ ํฌ์ธํŠธ & ๋ฆฌ๋ทฐ ํŒจํ„ดํ™”

๊ธฐ๋Šฅ ์š”์†Œ ์‹ค๋ฌด ์ „๋žต
์‚ฌ์šฉ์ž ๊ถŒํ•œ ์ฒดํฌ ๊ฐ•์˜ ์ˆ˜๊ฐ• ์—ฌ๋ถ€๋Š” enrollment ํ…Œ์ด๋ธ” ๊ธฐ์ค€
์ค‘๋ณต ๋ฆฌ๋ทฐ ๋ฐฉ์ง€ lectureId + userId ์กฐํ•ฉ ์œ ์ผ์„ฑ ์ฒดํฌ
ํฌ๋งท ์ฒ˜๋ฆฌ ์ฑ…์ž„ ๋ทฐ๋‹จ์ด ์•„๋‹Œ ์„œ๋น„์Šค๋‹จ์—์„œ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ผ๊ด€์„ฑ ํ™•๋ณด
ํ…Œ์ŠคํŠธ ์„ค๊ณ„ canWriteReview()์™€ getReviewsByLectureId()๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ•ต์‹ฌ ๋Œ€์ƒ
ํ™•์žฅ ๊ณ ๋ ค ์ถ”์ฒœ ๋ฆฌ๋ทฐ ์ •๋ ฌ, ์ฒจ๋ถ€ ์ด๋ฏธ์ง€ ๋“ฑ ํ™•์žฅ ์‹œ ๊ตฌ์กฐ ์œ ์ง€ ๊ฐ€๋Šฅ

๐Ÿ“š ๊ฐœ์„  ๋ฐ ํ™•์žฅ ์•„์ด๋””์–ด

ํ•ญ๋ชฉ ์ œ์•ˆ
๋ณ„์  ํ•„๋“œ ์ถ”๊ฐ€ rating ํ•„๋“œ ํฌํ•จํ•˜์—ฌ 1~5์  ๊ธฐ์ค€ ์ •๋Ÿ‰ ํ‰๊ฐ€
๋ฆฌ๋ทฐ ์‹ ๊ณ  ๊ธฐ๋Šฅ ๋ถ€์ ์ ˆํ•œ ๋ฆฌ๋ทฐ ๋Œ€์‘์„ ์œ„ํ•œ ์‹ ๊ณ /์ฐจ๋‹จ API ๋„์ž…
๊ด€๋ฆฌ์ž ๊ฒ€ํ†  ๊ธฐ๋Šฅ ๋ฆฌ๋ทฐ ๋“ฑ๋ก ํ›„ ๊ด€๋ฆฌ์ž ์Šน์ธ ๊ตฌ์กฐ ๋„์ž… ๊ฐ€๋Šฅ
ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ๋ฆฌ๋ทฐ ์ˆ˜ ๋งŽ์„ ๊ฒฝ์šฐ, offset ๊ธฐ๋ฐ˜ LIMIT ์ฒ˜๋ฆฌ ๋„์ž…
๋ฆฌ๋ทฐ ํ†ต๊ณ„ API ํ‰๊ท  ํ‰์ , ๋ฆฌ๋ทฐ ์ˆ˜ ๋“ฑ์„ ์ œ๊ณตํ•˜๋Š” ํ†ต๊ณ„ API ๊ตฌ์„ฑ

โœ… ๊ธฐ๋Šฅ ์š”์•ฝ

๊ธฐ๋Šฅ ์„ค๋ช…
๋ฆฌ๋ทฐ ์กฐํšŒ ์ž‘์„ฑ์ผ ํฌ๋งท ํฌํ•จ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
๋ฆฌ๋ทฐ ๋“ฑ๋ก DTO ๊ธฐ๋ฐ˜ insert ์ฒ˜๋ฆฌ, alias ๋ฉ”์„œ๋“œ ํฌํ•จ
๊ถŒํ•œ ๊ฒ€์‚ฌ ์ˆ˜๊ฐ• ์—ฌ๋ถ€ + ๊ธฐ์กด ์ž‘์„ฑ ์—ฌ๋ถ€ ์ฒดํฌ
๊ณ„์ธต ๊ตฌ์กฐ Service โ†” DAO โ†” DB ๋ถ„๋ฆฌ ์„ค๊ณ„
์˜ˆ์™ธ ์ฒ˜๋ฆฌ RuntimeException์œผ๋กœ ์˜ˆ์™ธ ์ถ”์ƒํ™” ๋ฐ ํ™•์žฅ ์—ฌ์ง€ ํ™•๋ณด

๐Ÿงพ ํšŒ๊ณ  โ€“ ๋ฆฌ๋ทฐ๋ž€, ์œ ์ €์™€ ์ฝ˜ํ…์ธ  ์‚ฌ์ด์˜ ์ธํ„ฐ๋ž™์…˜์ด๋‹ค

์ด๋ฒˆ ReviewService ๊ตฌํ˜„์„ ํ†ตํ•ด ๋А๋‚€ ์ ์€ ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ,

โ€œ์ฝ˜ํ…์ธ  ์†Œ๋น„ ์ดํ›„์˜ ํ–‰๋™์„ ์ •์ œ๋œ ๋กœ์ง์œผ๋กœ ๊ธฐ๋กํ•˜๋Š” ๊ฒƒโ€์ด๋ผ๋Š” ์ ์ด์—ˆ๋‹ค.

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