ํ์์ ๋ํด์ ๋ฆฌ๋ทฐ๋ฅผ ๋ฑ๋กํ ์ ์๋ ๊ธฐ๋ฅ์ด ์๋ค.
ํ์์ ์กฐํํ ๋ ๋ฑ๋ก๋ ๋ฆฌ๋ทฐ ์นด์ดํธ๋ฅผ ๋น ๋ฅด๊ฒ ์กฐํํ ํ์๊ฐ ์์๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ฆฌ๋ทฐ ๋ฑ๋กํ ๋ ๋ฆฌ๋ทฐ๋ก ์ ํํ ํ์ ์นด์ดํธ๊ฐ ์ฆ๊ฐ๋์ด์ผ ํ๋ค.
POST ๋ฉ์๋๋ก ์์ฒญ ์ฒ๋ฆฌํ๋๋ฐ, ๊ฐ ๊ฐฑ์ PATCH ๋ฉ์๋ ๋์์ด ํฌํจ๋๋ ๊ฒ์ด๋ค.
์๋ก ๋ค๋ฅธ ๋๋ฉ์ธ ๊ฐ์ ์์กด์ฑ์ ์ค์ผ ๋ฐฉ๋ฒ
์ด ํ์ํ๋ค.
์ด๋ฒคํธ ๋ฆฌ์ค๋
๊ทธ๊ฒ ๋ต์ด๋ผ ์๊ฐํ๋ค.
์ค์ง ํ์์ ๋ํ ๋ฆฌ๋ทฐ ์นด์ดํธ ์กฐํ ์ฑ๋ฅ ์ํด ๋ฐ์ ๊ทํ ํ๋ค.
academy
๋๋ฉ์ธacademies
ํ
์ด๋ธId
: ์๋ณ์academyName
: ํ์ ์ด๋ฆreview_counts
ํ
์ด๋ธkindnessCount
: ์น์ ํจ ๋ฆฌ๋ทฐ ๊ฐ์goodFacilityCount
: ์ข์ ์์ค ๋ฆฌ๋ทฐ ๊ฐ์cheapFeeCount
: ๊ฐ์ผ ๊ต์ก๋น ๋ฆฌ๋ทฐ ๊ฐ์goodManagementCount
: ์ข์ ๊ด๋ฆฌ ๋ฆฌ๋ทฐ ๊ฐ์reviews
๋๋ฉ์ธreviews
ํ
์ด๋ธkindness
: ์น์ ํจ ๋ฆฌ๋ทฐ ๊ฐ์goodFacility
: ์ข์ ์์ค ๋ฆฌ๋ทฐ ๊ฐ์cheapFee
: ๊ฐ์ผ ๊ต์ก๋น ๋ฆฌ๋ทฐ ๊ฐ์goodManagement
: ์ข์ ๊ด๋ฆฌ ๋ฆฌ๋ทฐ ๊ฐ์๋ค์ด๋ฒ ๋ฆฌ๋ทฐ์ฒ๋ผ ๋ฆฌ๋ทฐ ๋ฑ๋ก ์ ํน์ ํค์๋๋ฅผ ์ ํํด์ ๋ฆฌ๋ทฐ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์ํค๋ ๋ฐฉ์์ด๋ค.
์ง๋์์ ํ์์ ๊ฒ์ํ๋ฉด, ์ฌํ๊น์ง ๊ฐฑ์ ๋ ๋ฆฌ๋ทฐ ์นด์ดํธ๋ฅผ ์กฐํํด์ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ค๋ค.
EventDispatcher
์๊ฒ ์ ๋ฌEventDispatcher
๊ฐ ๋งคํ๋๋ EventListener
์ฐ๊ฒฐEventListener
๊ฐ ์ ๋ฌ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํด ์ฝ๋ ์คํSpring Web ์์กด์ฑ์ด ํ์ํ๋ค.
๊ทผ๋ฐ ์คํ๋ง ์ฐ๋ฉด์ ์น ์์กด์ฑ ์ ํ์ํ ๋๊ฐ ์๋๋ชฐ๋
implementation 'org.springframework.boot:spring-boot-starter-web'
์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๊ตฌํํ๋ ๋ฐฉ์์ ๊ฐ๋จํ๋ค.
์ฐ๋ฆฌ ํจ์ ์คํ๋ง์ด EventDispatcher
๋ฅผ ์ด๋ฏธ ๊ตฌํํด๋์๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๋ ํธํ๊ฒ ์ด๋ฒคํธ์ ๋ฆฌ์ค๋๋ง ๊ตฌํํ๊ณ ๋ฑ๋กํด์ ์ฐ๋ฉด ๋๋ค.
์คํ๋ง 4.2 ์ดํ ๋ฒ์
2023๋ ๊ธฐ์ค ์คํ๋ง์ 4๋ฒ์ ๊น์ง ๋ฎ์ถฐ์ ์ฌ์ฉํ๋ ์ฌ๋์ ์์๊ฑฐ๋ผ ์๊ฐํด์ ์ต์ ๊ธฐ์ค์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ค.
4.2 ์ด์ ์ฝ๋์์ ์ฐจ์ด์ ์ ApplicationEventPublisher
ํด๋์ค๋ฅผ ์์ํ๋ ์ฝ๋ ์ ๋ฌด ์ ๋๋ค.
์ฐ๋ฆฌ๋ ์คํ๋ง์ ํธ์๋ฅผ ๊ฐ์ฌํ ๋ฐ์๋ค์ด๋ฉด ๋๋ค.
์ด๋ฒคํธ๋ฅผ ๋ฑ๋ก, ํธ์ถํ๋ ์ฑ ์์ ๊ฐ๋๋ค.
์ด๋ฒคํธ๋ก ํธ์ถํ๊ณ ํ ์ฝ๋์ ํ์ํ ์ ๋ณด๋ฅผ ์์งํ๋ ํ๋๋ฅผ ๋ฃ์ด๋จ๋ค.
public class NewReviewEvent {
private final Review review;
public NewReviewEvent(Review review) {
this.review = review;
}
public long getAcademyId() {
return review.getAcademyId();
}
public Map<String, Boolean> getReviews() {
return Map.of(
"kindness", review.isKindness(),
"goodFacility", review.isGoodFacility(),
"cheapFee", review.isCheapFee(),
"goodManagement", review.isGoodManagement());
}
}
์ด๋ฒคํธ ํด๋์ค์ ์ํด ํธ์ถ๋๋ ํด๋์ค
@Component
์คํ๋ง ๋น์ผ๋ก ๋ง๋ค์ด ์ฌ์ฉํด์ผ ํ๋ค. ๊ทธ๋์ผ EventDispatcher
๊ฐ ๋ฐํ์์ Event ํด๋์ค์ ๋งคํํด์ ์ฐ๊ฒฐํด์ค ์ ์๋ค.@EventListener
๋ฆฌ์ค๋๋ก ๋ฑ๋กํ๋ค๋ ์๋ฏธ. ๋งค๊ฐ๋ณ์๋ก EventPublisher ๋ฅผ ๋ฐ์์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ Event์ ๋งคํ๋๋ ๊ฒ. ํ ๋ฆฌ์ค๋ ํด๋์ค์ ์ฌ๋ฌ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๋ฑ๋กํด๋ ๋๋ค. ์ด๋๋ ๋ชจ๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ํธ์ถ๋๋ค. ๋ฐ๋ก ๋ค์ ์ปฌ๋ผ์ ์ค๋ช
์์ @TransactionalEventListener
๋ฆฌ์ค๋์ ๋์๊น์ง๋ ํธ๋์ญ์
์ ํฌํจ์ํค๋ ์ด๋
ธํ
์ด์
์ด๋ค. ๋๋ DB ๊ฐฑ์ ์ ํด๋นํ๋ ์ฝ๋๊ฐ ๋ฆฌ์ค๋์์ ํ์ํ ๋ด์ฉ์ด๊ธฐ ๋๋ฌธ์ ์ด ์ด๋
ธํ
์ด์
์ ์ผ๋ค. ๋ง์ฝ ๋ฆฌ์ค๋ ๋ฉ์๋ ํ๋ก์์ ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค๋ฉด, ํด๋น ์ด๋ฒคํธ์ ํด๋นํ๋ ํธ๋์ญ์
์์ฒด๊ฐ ๋ชจ๋ ๋กค๋ฐฑ๋๋ค.
์ต์ (phase) | ์ค๋ช |
---|---|
TransactionPhase.AFTER_COMMIT | (๊ธฐ๋ณธ๊ฐ) ํธ๋์ญ์ ์ปค๋ฐ๋ ๋ ์ด๋ฒคํธ ์คํ |
TransactionPhase.ROLLBACK | ํธ๋์ญ์ ๋กค๋ฐฑ๋ ๋ ์ด๋ฒคํธ ์คํ |
TransactionPhase.AFTER_COMPLETION | ํธ๋์ญ์ ์ปค๋ฐ ํน์ ๋กค๋ฐฑ๋ ๋ ์ด๋ฒคํธ ์คํ |
TransactionPhase.BEFORE_COMMIT | ํธ๋์ญ์ ์ปค๋ฐ ์ ์ ์ด๋ฒคํธ ์คํ |
@Component
public class NewReviewEventListener {
private final AcademyService academyService;
public NewReviewEventListener(AcademyService academyService) {
this.academyService = academyService;
}
// @EventListener
@TransactionalEventListener
public void sendPush(NewReviewEvent event) {
long academyId = event.getAcademyId();
Map<String, Boolean> reviews = event.getReviews();
academyService.updateReviewCount(academyId, reviews);
}
}
๋ฆฌ๋ทฐ ๋ฑ๋ก ๋ฉ์๋์์ ์ด๋ฒคํธ๋ฅผ ํ์ฉํ๋ค.
์ด๋ ๊ฒ ์ํ๋ ๋ฉ์๋์์ ์ด๋ฒคํธ๋ฅผ ์์ฑํด์ ๋ฑ๋กํด์ฃผ๋ฉด ๋๋ค.
publishEvent
๋ฉ์๋๋ฅผ ํ์ฉํด์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค.
@Service
@Transactional(readOnly = true)
public class ReviewService {
// ์ด๋ฒคํธ ๋ฑ๋ก์ ์ฃผ์
private final ApplicationEventPublisher eventPublisher;
private final ReviewJpaRepository reviewJpaRepository;
public ReviewService(
ApplicationEventPublisher eventPublisher,
ReviewJpaRepository reviewJpaRepository
) {
this.eventPublisher = eventPublisher;
this.reviewJpaRepository = reviewJpaRepository;
}
@Transactional
public ReviewResult createReview(long academyId, ReviewParam param) {
Review review = param.toEntity(academyId);
Review save = reviewJpaRepository.save(review);
// ๋ฑ๋กํ ์ด๋ฒคํธ ์์ฑ
NewReviewEvent newReviewEvent = new NewReviewEvent(save);
// ์ด๋ฒคํธ ๋ฑ๋ก
eventPublisher.publishEvent(newReviewEvent);
return ReviewResult.from(save);
}
}
๋ฐ๊ฐ์ด ์ฐ๋ฆฌ ๋๊ธฐ ํ์ .
๋๊ธฐ๋ ์๋ต ๊ธฐ๋ค๋ฆฌ๊ณ ๋ค์๊บผ ์คํ.
๋น๋๊ธฐ๋ ์๋ต ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค์๊บผ ์คํ.
์ฌ๊ธฐ์ ์ด์ ๋๋ก ์ ๋ฆฌ ๊ฐ๋ฅํ๋ค.
์คํ๋ง ์ด๋ฒคํธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ๋ค.
์ฝ๊ฒ ๋งํด์ ๋ฆฌ์ค๋์ ๋ชจ๋ ๋ฉ์๋๊ฐ ๊ฐ์ ์คํ โ ์๋ต ํ์ ๋ค์ ๋ฉ์๋๊ฐ ์คํ๋๋ค๋ ๊ฒ.
๋น๋๊ธฐ๋ ์๋ต ์ ์ ๋ค๋ฅธ ๋ฉ์๋๋ ์คํํ๋ค.
๋ฑ ๋ด๋ ์ฑ๋ฅ๊ณผ ์ ํฉ์ฑ ํธ๋ ์ด๋ ์คํ๋ผ๋ ๊ฑธ ์ ์ ์๋ค.
๊ทธ๋ ๋ค๋ฉด ๊ถ๊ธํ ์ ์ด ์๊ธด๋ค.
๋๊ธฐ ์ฒ๋ฆฌํ ๋ ์คํ ์์๋ ์ด๋ป๊ฒ ์ ํด์ง๋?
@Slf4j
@Component
public class NewReviewEventListener {
@EventListener
public void sendPush(NewReviewEvent event) {
log.info("์ฒซ ๋ฒ์งธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋ ์คํ");
}
@EventListener
public void sendPush2(NewReviewEvent event) {
log.info("๋ ๋ฒ์งธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋ ์คํ");
}
}
์๋ต์ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด ์ฝ๋ ์์ ๋๋ก๋ค.
๊ทธ๋ฌ๋ฉด ๋น๋๊ธฐ์ผ ๋๋?
@Slf4j
@Component
public class NewReviewEventListener {
@Async
@EventListener
public void sendPush(NewReviewEvent event) throws InterruptedException {
log.info("์ฒซ ๋ฒ์งธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋ ์คํ");
Thread.sleep(1_000);
log.info("์ฒซ ๋ฒ์งธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋ ์ข
๋ฃ");
}
@Async
@EventListener
public void sendPush2(NewReviewEvent event) {
log.info("๋ ๋ฒ์งธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋ ์คํ");
log.info("๋ ๋ฒ์งธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฉ์๋ ์ข
๋ฃ");
}
}
@Async
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํด์ ๋น๋๊ธฐ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ง๋ค.
์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๊ตฌํํ๋ฉด์ ๊ณ ๋ฏผ๊ฑฐ๋ฆฌ๊ฐ ํ๋ ์์๋ค.
๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด, ์๊ฐ์ ์ค๋ฅ ๋๋ฌธ์ ํ๋ ๊ณ ๋ฏผ์ด์๊ณ ์ฝ๊ฒ ํด๊ฒฐ ๊ฐ๋ฅํ๋ค.
๊ทธ๋ฆผ์ด ์๋นํ ์ถ์ํ๋์ด ์์ด์ ์์๋ณด๊ธฐ ํ๋ค ์ ์๋ค.
๋ ํํ์ ๊ตฌ์กฐ์์ ๊ฐ๊ฐ ๋ฌธ์ ๊ฐ ์์ด ๊ณ ๋ฏผ ํ๋ค.
๊ทธ๋ฆผ ์๋ ์ ๋ฆฌํ๋ค.
์ข ๋ ์ค๋ช ํ์๋ฉด,
DTO ๋ฅผ ์ด๋์ ๊ตฌํํ ์ง ๊ณ ๋ฏผํ ํ์๊ฐ ์์ด, ๋ชจ๋ ์ ๋ณด๋ฅผ ํํด์ณ์ ๊ธฐ๋ณธ ํ์ ์ผ๋ก ๋๊ธฐ๋ฉด ๋๋ ๋ฌธ์ ์๋ค.
์์ ์ด๋ฒคํธ ํด๋์ค ์ฝ๋๋ฅผ ๋ณด๋ฉด DTO ๊ฐ ์๋ค.
long
ํ์
์ ๋ณ์์ Map
์๋ฃ๊ตฌ์กฐ๋ฅผ ํ์ฉํด์ ๋๋ฉ์ธ์ ์์กดํ์ง ์๊ณ ๋ฐ์ดํฐ ์ ๋ฌ์ด ๊ฐ๋ฅํ๋ค.