๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

@TransactionalEventListener ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์ 

by dkswnkk 2024. 7. 3.

๋ฌธ์ œ ์ƒํ™ฉ

@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 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

saveAndFlush()๋กœ ๊ฐ•์ œ๋กœ ๋ฐ˜์˜ํ•˜๋ ค๊ณ  ํ–ˆ์„ ๋•Œ

์žฌํ˜„ ์ฝ”๋“œ: 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());
}

๋Œ“๊ธ€