@TransactionalEventListener
Event๋ฅผ ์ฌ์ฉํ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๋ @EventListener๋ event๋ฅผ publishing ํ๋ ์ฝ๋ ์์ ์ ๋ฐ๋ก publishing ํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ๋ event๋ฅผ ํผ๋ธ๋ฆฌ์ฑ ํ ๋๋ ๋๋ถ๋ถ ๋ฉ์ธ ์์
์ด ์๋ ์๋ธ์ ์์
์ด ๋ง๊ณ ๋น๋๊ธฐ๋ก ์งํํด๋ ๋๋ ๊ฒฝ์ฐ๋ ๋ง์ต๋๋ค. ๋ค๋ฅธ ๋๋ฉ์ธ ๋ก์ง์ธ ๊ฒฝ์ฐ๋ ์์ฃ . ์ด๋ด ๊ฒฝ์ฐ ์กฐ๊ธ ์ ๋งคํด์ง๊ธฐ๋ ํฉ๋๋ค.
์๋์ ์์ ๋ก ์ํฉ์ ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ์๋์ฝ๋๋ @Transactional๋ก ๋ฉ์๋๋ฅผ ํ๋์ ํธ๋์ญ์
์ผ๋ก ๋ฌถ์ด๋์์ต๋๋ค. 1๋ฒ๊ณผ 2๋ฒ์ด ์ ์์ ์ผ๋ก ๋ง๋ฌด๋ฆฌ๋๊ณ 3๋ฒ์ด ๋ฐ์ํ๋ ๋์ค์ ์์ธ์ฒ๋ฆฌ๊ฐ ๋ฐ์ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์ ? 3๋ฒ์ ์คํจํ์ผ๋ฉฐ 1๋ฒ๋ ๊ฐ์ ํธ๋์ญ์
์ผ๋ก ๋ฌถ์ฌ ์๊ธฐ๋๋ฌธ์ ์คํจํ ๊ฒ์
๋๋ค. ํ์ง๋ง 2๋ฒ์ rollback์ด ์ด๋ฃจ์ด์ง์ง ์๊ธฐ๋๋ฌธ์ ๊ฒฐ๊ณผ์ ์ผ๋ก ๋ถ์ผ์น๊ฐ ๋ฐ์ ํ ์ ๋ฐ์ ์๊ฒ ๋๋ ๊ฒ์
๋๋ค.
Copy @Transactional
public void function() {
reviewRepository.save() // 1. A ์ ์ฅ
applicationEventPublisher.publishEvent(); // 2. A์ ์ํ ์ด๋ฒคํธ ๋ฐ์
userRepository.save() // 3. B ์ ์ฅ
}
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ @TransactionEventListener
๊ฐ ๋์์ต๋๋ค. @TransactionEventListener
๋ Event์ ์ค์ง์ ์ธ ๋ฐ์์ ํธ๋์ญ์
์ ์ข
๋ฃ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ผ๋๊ฒ์
๋๋ค.
@TransactionalEventListener ์ต์
@TransactionalEventListener
์ ์ด์ฉํ๋ฉด ํธ๋์ญ์
์ ์ด๋ค ํ์ด๋ฐ์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํฌ ์ง ์ ํ ์ ์์ต๋๋ค. ์ต์
์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ TransactionPhase
์ ์ด์ฉํ๋ ๊ฒ์ด๋ฉฐ ์๋์ ๊ฐ์ ์ต์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
AFTER_COMMIT (๊ธฐ๋ณธ๊ฐ) - ํธ๋์ญ์
์ด ์ฑ๊ณต์ ์ผ๋ก ๋ง๋ฌด๋ฆฌ(commit)๋ฌ์ ๋ ์ด๋ฒคํธ ์คํ
AFTER_ROLLBACK โ ํธ๋์ญ์
์ด rollback ๋ฌ์ ๋ ์ด๋ฒคํธ ์คํ
AFTER_COMPLETION โ ํธ๋์ญ์
์ด ๋ง๋ฌด๋ฆฌ ๋ฌ์ ๋(commit or rollback) ์ด๋ฒคํธ ์คํ
BEFORE_COMMIT - ํธ๋์ญ์
์ ์ปค๋ฐ ์ ์ ์ด๋ฒคํธ ์คํ
์ค์ต ์ฌ์ ์ค๋น
๊ทธ๋ ๋ค๋ฉด ์ฌ์ฉํ๋ฉด์ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ์ค๋ ์ฌ์ฉํ ์ค์ต ์์ ์ฝ๋๋ ์๋์ ๊ฐ์ต๋๋ค. ์ด์ ์ฝ๋์์ Model์ ๋์ผํ๋ฉฐ Service์ Listener์ ์ฝ๋๋ ์ผ๋ถ ๋ณ๊ฒฝ๋ ๋ถ๋ถ์ด ์์ผ๋ ํ์ธํด์ฃผ์๊ธฐ ๋ฐ๋๋๋ค.
Service ์ฝ๋
์๋๋ Service ์ฝ๋์
๋๋ค. @Transactional
์ ์ด์ฉํ์ฌ ํธ๋์ญ์
์ ์ฌ์ฉํ๋ฉฐ review์ user๋ฅผ DB์ ์ ๊ทผํ๋ฉด์ ๊ทธ ์ฌ์ด์ ์ด๋ฒคํธ๋ฅผ ํธ์ถํ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
Copy @Slf4j
@Component
public class EventTestService {
private final UserRepositoryV1 userRepositoryV1;
private final ReviewRepositoryV1 reviewRepositoryV1;
private final ApplicationEventPublisher applicationEventPublisher;
public EventTestService(UserRepositoryV1 userRepositoryV1,
ReviewRepositoryV1 reviewRepositoryV1,
ApplicationEventPublisher applicationEventPublisher) {
this.userRepositoryV1 = userRepositoryV1;
this.reviewRepositoryV1 = reviewRepositoryV1;
this.applicationEventPublisher = applicationEventPublisher;
}
@Transactional // ํธ๋์ญ์
์ฌ์ฉ
public void publishTransactionalCustomEvent() {
Review review = reviewRepositoryV1.findById(1L)
.orElseThrow();
User user = userRepositoryV1.findById(review.getUserId())
.orElseThrow();
reviewRepositoryV1.save(review);
applicationEventPublisher.publishEvent(new DomainEvent("karol", 15));
userRepositoryV1.save(user);
}
}
Listener ์ฝ๋
์๋๋ Listener ์ฝ๋์
๋๋ค. @EventListener
๋์ @TransactionalEventListener
์ ์ฌ์ฉํฉ๋๋ค.
Copy @Slf4j
@Component
public class TransactionalEventTestListener {
@TransactionalEventListener
public void handleContextStart2(DomainEvent cse) {
log.info("name = " + cse.getName() + ", age = " + cse.getAge());
log.info("TransactionalEventListener ์ด๋ฒคํธ ๋ง๋ฌด๋ฆฌ");
}
}
Model ์ฝ๋
์๋๋ ๋ชจ๋ธ ์ฝ๋๋ก ์ด์ ์ฝ๋์ ๋ฌ๋ผ์ง์ ์ ์์ต๋๋ค.
Copy public class DomainEvent {
private final String name;
private final int age;
public DomainEvent(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
์ค์ต
์์ ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์ค์ต์ ์งํํด๋ณป๋ก ํ๊ฒ ์ต๋๋ค.
๊ธฐ๋ณธ์ ์ธ ์ํฉ
์ ์ฝ๋๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํํด๋ณด๊ฒ ์ต๋๋ค. ์คํ ์์๋ฅผ ํ์ธํ๊ธฐ ์ํด์ log๋ฅผ ์ฌ์ด๋๋๋ก ํ๊ฒ ์ต๋๋ค.
Copy @Transactional
public void publishTransactionalCustomEvent(Boolean isException) {
log.info("๋ฉ์๋ ์์");
Review review = reviewRepositoryV1.findById(1L)
.orElseThrow();
User user = userRepositoryV1.findById(review.getUserId())
.orElseThrow();
reviewRepositoryV1.save(review);
log.info("reviewRepositoryV1 ์ ์ฅ");
applicationEventPublisher.publishEvent(new DomainEvent("karol", 15));
log.info("์ด๋ฒคํธ ๋ฐ์ ์์ฒญ");
if (isException) {
log.info("์๋ฌ ๋ฐ์");
throw new RuntimeException("exception");
}
userRepositoryV1.save(user);
log.info("userRepositoryV1 ์ ์ฅ");
log.info("๋ฉ์๋ ์ข
๋ฃ");
}
๊ฒฐ๊ณผ๋ฅผ ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋ ์์๋ฅผ ๋ณด๋ฉด publishEvent
์ ์๊ฐ Event๊ฐ ์คํ๋์ง ์๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. Event๊ฐ ์คํ๋๋ ์๊ฐ์ ๋ฐ๋ก ๋ฉ์๋๊ฐ ์ข
๋ฃ๋์ ํธ๋์ญ์
์ด ์ข
๋ฃ๋๋ ์๊ฐ์ด๋ผ๋ ์ฌ์ค์ ํ์ธํ ์ ์์์ต๋๋ค.
Copy 2021-08-22 22:34:28.268 INFO 13835 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์์
2021-08-22 22:34:28.355 INFO 13835 --- [ Test worker] c.p.a.api.game.service.EventTestService : reviewRepositoryV1 ์ ์ฅ
2021-08-22 22:34:28.356 INFO 13835 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์ด๋ฒคํธ ๋ฐ์ ์์ฒญ
2021-08-22 22:34:28.359 INFO 13835 --- [ Test worker] c.p.a.api.game.service.EventTestService : userRepositoryV1 ์ ์ฅ
2021-08-22 22:34:28.359 INFO 13835 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์ข
๋ฃ
2021-08-22 22:34:28.378 INFO 13835 --- [ Test worker] p.a.a.g.s.TransactionalEventTestListener : TransactionalEventListener ์ด๋ฒคํธ ๋ง๋ฌด๋ฆฌ
์๋ฌ ๋ฐ์
์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น์ ? ์ ๋ฉ์๋์์ isException
์ true๋ก ํ๋ผ๋ฏธํฐ๋ฅผํ์ฌ ๋ฉ์๋๋ฅผ ์คํ์ํค๋ฉด ์๋์ ๊ฐ์ ๋ก๊ทธ๊ฐ ์ฐํ๋๋ค. ์๋ฌ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์
์ ๋กค๋ฐฑ๋๋ฉฐ Event๋ ์คํ๋์ง ์๋๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
Copy 2021-08-22 22:36:15.959 INFO 13944 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์์
2021-08-22 22:36:16.048 INFO 13944 --- [ Test worker] c.p.a.api.game.service.EventTestService : reviewRepositoryV1 ์ ์ฅ
2021-08-22 22:36:16.049 INFO 13944 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์ด๋ฒคํธ ๋ฐ์ ์์ฒญ
2021-08-22 22:36:16.050 INFO 13944 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์๋ฌ ๋ฐ์
java.lang.RuntimeException: exception
์ต์
๋ณ๊ฒฝ
๋ชจ๋ ์ต์
์ ํ๋์ฉ ํ
์คํธ ํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ํ
์คํธํ TransactionPhase
๋ฅผ ํ๋์ฉ ๋ง๋ค๊ณ ํ
์คํธํด๋ณด๊ฒ ์ต๋๋ค. ๋จผ์ ์ฑ๊ณต์ ๊ฒฝ์ฐ 3๊ฐ์ง ํ์
์ Event๊ฐ ๋ฐ์ํ๋๊ฒ์ ํ์ธํ์์ต๋๋ค. ๊ทธ ์ด๋ฒคํธ๋ค์ ์๋์ ๊ฐ์ต๋๋ค.
Copy @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
Copy @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
Copy @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
์์์ ์ผ๋ก๋ ์๋์ ๊ฐ์ด ๋
ธ์ถ๋์์ต๋๋ค. AFTER_COMMIT
๊ณผ AFTER_COMPLETION
์ ORDER์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.
Copy 2021-08-22 22:49:03.816 INFO 14681 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์์
2021-08-22 22:49:03.891 INFO 14681 --- [ Test worker] c.p.a.api.game.service.EventTestService : reviewRepositoryV1 ์ ์ฅ
2021-08-22 22:49:03.893 INFO 14681 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์ด๋ฒคํธ ๋ฐ์ ์์ฒญ
2021-08-22 22:49:03.895 INFO 14681 --- [ Test worker] c.p.a.api.game.service.EventTestService : userRepositoryV1 ์ ์ฅ
2021-08-22 22:49:03.895 INFO 14681 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์ข
๋ฃ
2021-08-22 22:49:03.896 INFO 14681 --- [ Test worker] p.a.a.g.s.TransactionalEventTestListener : TransactionalEventListener BEFORE_COMMIT
2021-08-22 22:49:03.909 INFO 14681 --- [ Test worker] p.a.a.g.s.TransactionalEventTestListener : TransactionalEventListener AFTER_COMMIT
2021-08-22 22:49:03.909 INFO 14681 --- [ Test worker] p.a.a.g.s.TransactionalEventTestListener : TransactionalEventListener AFTER_COMPLETION
์คํจ์ ๊ฒฝ์ฐ๋ ์๋์ ๊ฐ์ Event๊ฐ ๋ฐ์ํ์์ผ๋ฉฐ ์์๋ ์๋์ ๊ฐ์์ต๋๋ค. ์ด ์์์ญ์ ORDER
์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.
Copy @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
Copy @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
Copy 2021-08-22 22:51:38.000 INFO 14833 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์์
2021-08-22 22:51:38.073 INFO 14833 --- [ Test worker] c.p.a.api.game.service.EventTestService : reviewRepositoryV1 ์ ์ฅ
2021-08-22 22:51:38.075 INFO 14833 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์ด๋ฒคํธ ๋ฐ์ ์์ฒญ
2021-08-22 22:51:38.075 INFO 14833 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์๋ฌ ๋ฐ์
2021-08-22 22:51:38.082 INFO 14833 --- [ Test worker] p.a.a.g.s.TransactionalEventTestListener : TransactionalEventListener AFTER_COMPLETION
2021-08-22 22:51:38.083 INFO 14833 --- [ Test worker] p.a.a.g.s.TransactionalEventTestListener : TransactionalEventListener AFTER_ROLLBACK
@Transactional ์์ ๊ฒฝ์ฐ
๋ง์ง๋ง์ผ๋ก๋ @Transactional
์ด ์์๊ฒฝ์ฐ๋ฅผ ํ
์คํธ ํด๋ณด์์ต๋๋ค. @TransactionalEventListener
๋ ํธ๋์ญ์
์ ์์กดํ์ฌ ๋ฐ์ํฉ๋๋ค. ๋ฐ๋ผ์ @Trasnactional
์ด ์์๋๋ Event๊ฐ ๋ฐ์ํ์ง ์๋๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
Copy 2021-08-22 22:38:18.191 INFO 14087 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์์
2021-08-22 22:38:18.329 INFO 14087 --- [ Test worker] c.p.a.api.game.service.EventTestService : reviewRepositoryV1 ์ ์ฅ
2021-08-22 22:38:18.330 INFO 14087 --- [ Test worker] c.p.a.api.game.service.EventTestService : ์ด๋ฒคํธ ๋ฐ์ ์์ฒญ
2021-08-22 22:38:18.340 INFO 14087 --- [ Test worker] c.p.a.api.game.service.EventTestService : userRepositoryV1 ์ ์ฅ
2021-08-22 22:38:18.340 INFO 14087 --- [ Test worker] c.p.a.api.game.service.EventTestService : ๋ฉ์๋ ์ข
๋ฃ
๋ง๋ฌด๋ฆฌ
์ค๋์ ์ด๋ ๊ฒ TrnasactionalEventListener์ ๋ํด์ ์ด๋ก ์ ์ธ ๋ถ๋ถ์ ๋ํด์ ์์๋ณด์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ค์ต์ ํตํด ์ด๋ค ํ์ด๋ฐ์ ์ด๋ฒคํธ๊ฐ ํธ์ถ๋๋์ง ์์๋ณด์์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค.
์ฐธ์กฐ
baeldung_spring-events#transaction-bound-events
baeldung_transaction-configuration-with-jpa-and-spring
stackoverflow_transactionaleventlistener-doesnt-works-where-as-eventlistener-works-like-cha