@TransactionalEventListener ์ฌ์ฉ ์ ์ฃผ์์
๋ฌธ์ ์ํฉ
@Service
@RequiredArgsConstructor
public class CurrentValueChangeService {
private final ExampleRepository exampleRepository;
private final ApplicationEventPublisher applicationEventPublisher;
@Transactional
public void changeValue(String value) {
Example example = exampleRepository.find(1L);
String beforeValue = example.getCurrentValue();
example.updateCurrentValue(value);
applicationEventPublisher.publishEvent(new ValueChangeEvent(beforeValue));
}
}
@Component
@RequiredArgsConstructor
public class ValueChangeEventListener {
private final BeforeValueChangeService beforeValueChangeService;
@TransactionalEventListener
public void onExampleEvent(ValueChangeEvent valueChangeEvent) {
beforeValueChangeService.changeBeforeValue(valueChangeEvent.beforeValue());
}
}
@Service
@RequiredArgsConstructor
@Slf4j
public class BeforeValueChangeService {
private final ExampleRepository exampleRepository;
@Transactional
public void changeBeforeValue(String value) {
log.info("์ ์์ ์ผ๋ก ์ง์
ํจ.");
Example example = exampleRepository.find(1L);
example.updateBeforeValue(value); // ๋์ํ์ง ์์.
}
}
@TransactionalEventListener์ ๊ธฐ๋ณธ ๊ฐ์ธ phase = TransactionPhase.AFTER_COMMIT์ ์ฌ์ฉํ๋ ๊ณผ์ ์์ ์ด๋ฒคํธ๋ ์ ์์ ์ผ๋ก ์์ ๋์ง๋ง ๊ทธ ์ดํ๋ก ์ด๋ ํ insert ์ฟผ๋ฆฌ(์ปค๋ฐ)๋ ์ํ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
๋๊ตฐ๋ค๋ ๋ณ๋ค๋ฅธ ์๋ฌ๋ ๋ฐ์ํ์ง ์๊ณ ๋จ์ํ ์ปค๋ฐ๋ง ๋์ง ์์ ์ค์ ๋ก ๋ฐ์ดํฐ๊ฐ ๋ฐ์๋์๋์ง ํ์ธํ๊ธฐ ์ ๊น์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง์กฐ์ฐจ ์ ์ ์์ด์ ๋งค์ฐ ๋นํฉ์ค๋ฌ์ ๋ค.
์์์ฑ์ ํฌํจ๋์ง ์์๋ ์ถ์ด entityManager๋ฅผ ํตํด ํ์ธํ๋๋ฐ ํด๋น ์ํฐํฐ๋ ์์์ฑ ์ํ์์๋ ๋ถ๊ตฌํ๊ณ select๋ง ์ํ๋๊ณ , insert ๋ฐ update ์ฟผ๋ฆฌ๋ ๋๊ฐ์ง ์์์ ํ์ธํ ์ ์์๋ค. ํน์๋ ํ๋ ๋ง์์ ์ฌ๊ธฐ์ saveAndFlush()๋ฅผ ํตํด ๋ณ๊ฒฝ์ฌํญ์ ์ฆ์ ๋ฐ์ํด ๋ณด๊ณ ์ ์๋ํ๋๋ฐ, ์ด ๊ฒฝ์ฐ์๋ TransactionSynchronization.afterCompletion threw exception ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์ฌํ ์ฝ๋: https://github.com/dkswnkk/transactionaleventlistener
์์ธ
์ผ๋จ ์ด ๋ฌธ์ ์ ์ฃผ์ ์์ธ์ @TransactionalEventListener์ AFTER_COMMIT์ ์ฌ์ฉํ๊ฒ ๋๋ฉด ํธ๋์ญ์ ์ด ์ปค๋ฐ๋ ํ์ ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ด๋ฒคํธ๊ฐ ์ฒ๋ฆฌ๋ ์์ ์๋ ๊ธฐ์กด ํธ๋์ญ์ ์ ์ปค๋ฐ์ด ๋๋ ์ํ์์ ๊ทธ ํธ๋์ญ์ ์ ๋ค์ ์ฐธ์ฌํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์๋ก์ด ํธ๋์ญ์ ์ ์์ํ์ง ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ณ๊ฒฝ ์ฌํญ์ ์ ์ฉํ ์ ์๊ฒ ๋๋ค.
์ฐธ๊ณ ๋ก ๊ธฐ์กด์ ํธ๋์ญ์ ์ด ์ปค๋ฐ๋๋ค๊ณ ํด์ ํธ๋์ญ์ ๋ฆฌ์์ค๊ฐ ํด์ ๋๋ ๊ฒ์ ์๋๊ธฐ ๋๋ฌธ์ ๊ณ์ ์ก์ธ์ค ๋ ์ ์์ผ๋ฉฐ, ์ปค๋ฅ์ ํ์์ ์ปค๋ฅ์ ์ด ๋ฐํ๋์ง ์๊ณ ๊ณ์ํด์ ์ฐ๊ด๋ ์์์ฑ ์ฝํ ์คํธ๊ฐ ์ด๋ ค์๋๋ค(HikariCP์ ํ์ฑ๋ ์ฐ๊ฒฐ์ ์๋ฏธํ๋ active๊ฐ 1์ ๊ฐ์ง). ๋ฐ๋ผ์ AFTER_COMMIT์ด ์ ์ฉ๋ @TransactionalEventListener ๋ฉ์๋๋ ๋ฐํํ ํธ๋์ญ์ ์ธ๋ถ์์ ์คํ๋๋ ๊ฒ์ด ์๋๋ผ ๊ณ์ํด์ ์ด์ ์ ํธ๋์ญ์ ์ ์ฐธ์ฌํ๋ค.
์ด์ ๊ด๋ จ๋ ์์ธํ ๋ด์ฉ์ ๊ณต์ ๋ฌธ์์์ ํ์ธํ ์ ์๋ค.
phase๋ ํธ๋์ญ์ ์ ์ด๋ ์์ ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ํธ์ถ๋์ด์ผ ํ๋์ง๋ฅผ ๊ฒฐ์ ํ๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ ๊ฐ๋ค์ ๊ฐ์ง ์ ์๋ค.
- AFTER_COMMIT : ๊ธฐ๋ณธ ๊ฐ์ผ๋ก, ํธ๋์ญ์ ์ด ์ฑ๊ณต์ ์ผ๋ก ์ปค๋ฐ๋ ํ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํ
- AFTER_ROLLBACK: ํธ๋์ญ์ ์ด ๋กค๋ฐฑ๋ ํ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํ
- AFTER_COMPLETION: ํธ๋์ญ์ ์ด ์ปค๋ฐ๋๊ฑฐ๋ ๋กค๋ฐฑ๋ ํ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํ
- BEFORE_COMMIT: ํธ๋์ญ์ ์ด ์ปค๋ฐ๋๊ธฐ ์ ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์คํ
์ฌ๊ธฐ์ AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION์ ์ฌ์ฉํ ๊ฒฝ์ฐ, ์ด๋ฒคํธ๋ฅผ ํธ์ถํ๋ ๋ฉ์ธ ํธ๋์ญ์ ์์ ์ปค๋ฐ์ด ์๋ฃ๋ ํ ํ์ ์์ ์์ ๋ณ๊ฒฝ ์ฌํญ์ ์๋ํ๋ ค๋ฉด ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ์ด๋ฒคํธ ์คํ ์ ์คํํ๋ ๋ก์ง์์ ํธ๋์ญ์ ์ ์๋ก ์ด๊ฑฐ๋, ์ด๋ฒคํธ ์คํ ์์ ์ AFTER_COMMIT์ด ์๋ BEFORE_COMMIT์ผ๋ก ์ค์ ํ์ฌ ํธ๋์ญ์ ์ ๋๊น์ง ์ ์งํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. ์๋๋ ์ด ๋ฐฉ๋ฒ๋ค์ ํ์ฉํ๋ ์ธ ๊ฐ์ง ๋ฐฉ์์ด๋ค.
๊ฐ๊ฐ์ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ ์ฉ๋ ์ ์์ผ๋ฉฐ, ๊ฐ ๋ฐฉ๋ฒ์ ์ฅ๋จ์ ์ ๊ณ ๋ คํ์ฌ ์ ํํ๋ฉด ๋๋ค.
1. TransactionPhase.BEFORE_COMMIT๋ก ๋ณ๊ฒฝ
์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ํธ๋์ญ์ ์ปค๋ฐ ์ ์ ํธ์ถ๋๋๋ก ์ค์ ํ๋ค.
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void onExampleEvent(ValueChangeEvent valueChangeEvent) {
beforeValueChangeService.changeBeforeValue(valueChangeEvent.beforeValue());
}
๋ค๋ง BEFORE_COMMIT์ผ๋ก ์ค์ ํ๋ฉด ์ด๋ฒคํธ๊ฐ ๊ธฐ์กด ํธ๋์ญ์ ๋ด์์ ์คํ๋๋ฏ๋ก, ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ก์ง์ด ์คํจํ๋ฉด ๋ฉ์ธ ํธ๋์ญ์ ๋ ๋กค๋ฐฑ๋ ์ ์์ผ๋ ์ด ์ ์ ๊ณ ๋ คํ๋ฉด ์ข๋ค.
2. ๋ณ๋์ ํธ๋์ญ์ ์์
์ด๋ฒคํธ ๋ฆฌ์ค๋์์ ํธ์ถํ๋ ๋ฉ์๋์์ Propagation.REQUIRES_NEW๋ฅผ ํตํด ์๋ก์ด ํธ๋์ญ์ ์ ์์ํ๋ค.
@Service
@RequiredArgsConstructor
public class BeforeValueChangeService {
private final ExampleRepository exampleRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW) // ์๋ก์ด ํธ๋์ญ์
์์
public void changeBeforeValue(String value) {
Example example = exampleRepository.find(1L);
example.updateBeforeValue(value); // ์ด์ ๋์ํจ
}
}
์ด ๊ฒฝ์ฐ์๋ ์๋ก์ด ํธ๋์ญ์ ์ ์์ํ๋ฉด ๋ฉ์ธ ํธ๋์ญ์ ๊ณผ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๊ฒ ๋๋ฏ๋ก ๋ฉ์ธ ํธ๋์ญ์ ์ด ๋กค๋ฐฑ๋๋๋ผ๋ ์๋ก์ด ํธ๋์ญ์ ์์์ ๋ณ๊ฒฝ ์ฌํญ์ ์ ์ง๋๊ธฐ์ ์ด ์ ์ ๊ณ ๋ คํด์ผ ํ๋ค. ์ฆ ์ด๋ฒคํธ ๋ก์ง์ ์คํจ๊ฐ ๋ฉ์ธ ํธ๋์ญ์ ์ ์ํฅ์ ๋ฏธ์น์ง ์๋๋ค.
๋ํ ์ด ์๊ฐ ๋์ 2๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ด ํ์ฑํ๋๋ค. ์ ์์ ์ฝ๋์์๋ ๋ฐํํ ์ชฝ ํธ๋์ญ์ (CurrentValueChangeService), ์์ ํ ์ชฝ ํธ๋์ญ์ (BeforeValueChangeService)
3. ๋น๋๊ธฐ๋ก ์ํ
์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๋น๋๊ธฐ๋ก ์ํํ์ฌ ๋ฉ์ธ ํธ๋์ญ์ ๊ณผ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๋๋ก ํ๋ค. ์ด ๊ฒฝ์ฐ์๋ ๋ฉ์ธ ํธ๋์ญ์ ์ ์ฑ๋ฅ์ ์ํฅ์ ์ฃผ์ง ์๊ณ , ๋ณ๋์ ์ค๋ ๋์์ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋๋ฏ๋ก ๋ฉ์ธ ํธ๋์ญ์ ์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์๋๋ค. ๋ค๋ง ์ค๋ ๋๊ฐ ๋ฌ๋ผ์ ธ ์์ธ์ฒ๋ฆฌ๋ ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฑ ๊ณ ๋ คํด์ผ ํ ๊ฒ ๋ง์์ง๋๋ฐ, ์ด์ ์ ์์ฑํ ์คํ๋ง์์ @Async๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ์์ ๊ธ์ด ์์ผ๋ ์ฐธ๊ณ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
@Async
@TransactionalEventListener
public void onExampleEvent(ValueChangeEvent valueChangeEvent) {
beforeValueChangeService.changeBeforeValue(valueChangeEvent.beforeValue());
}