TransactionalEventListener

@TransactionalEventListener

Event๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” @EventListener๋Š” event๋ฅผ publishing ํ•˜๋Š” ์ฝ”๋“œ ์‹œ์ ์— ๋ฐ”๋กœ publishingํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์šฐ๋ฆฌ๋Š” event๋ฅผ ํผ๋ธ”๋ฆฌ์‹ฑ ํ• ๋•Œ๋Š” ๋Œ€๋ถ€๋ถ„ ๋ฉ”์ธ ์ž‘์—…์ด ์•„๋‹Œ ์„œ๋ธŒ์˜ ์ž‘์—…์ด ๋งŽ๊ณ  ๋น„๋™๊ธฐ๋กœ ์ง„ํ–‰ํ•ด๋„ ๋˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋„๋ฉ”์ธ ๋กœ์ง์ธ ๊ฒฝ์šฐ๋„ ์žˆ์ฃ . ์ด๋Ÿด ๊ฒฝ์šฐ ์กฐ๊ธˆ ์• ๋งคํ•ด์ง€๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์˜ ์˜ˆ์ œ๋กœ ์ƒํ™ฉ์„ ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์ฝ”๋“œ๋Š” @Transactional๋กœ ๋ฉ”์„œ๋“œ๋ฅผ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์–ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค. 1๋ฒˆ๊ณผ 2๋ฒˆ์ด ์ •์ƒ์ ์œผ๋กœ ๋งˆ๋ฌด๋ฆฌ๋˜๊ณ  3๋ฒˆ์ด ๋ฐœ์ƒํ•˜๋Š” ๋„์ค‘์— ์˜ˆ์™ธ์ฒ˜๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š” ? 3๋ฒˆ์€ ์‹คํŒจํ–ˆ์œผ๋ฉฐ 1๋ฒˆ๋„ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์—ฌ ์žˆ๊ธฐ๋•Œ๋ฌธ์— ์‹คํŒจํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ 2๋ฒˆ์€ rollback์ด ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์— ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ๋ฐ–์— ์—†๊ฒŒ ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@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์— ์ ‘๊ทผํ•˜๋ฉด์„œ ๊ทธ ์‚ฌ์ด์— ์ด๋ฒคํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@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์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

@Slf4j
@Component
public class TransactionalEventTestListener {

    @TransactionalEventListener
    public void handleContextStart2(DomainEvent cse) {
        log.info("name = " + cse.getName() + ", age = " + cse.getAge());
        log.info("TransactionalEventListener ์ด๋ฒคํŠธ ๋งˆ๋ฌด๋ฆฌ");
    }
}

Model ์ฝ”๋“œ

์•„๋ž˜๋Š” ๋ชจ๋ธ ์ฝ”๋“œ๋กœ ์ด์ „ ์ฝ”๋“œ์™€ ๋‹ฌ๋ผ์ง„์ ์€ ์—†์Šต๋‹ˆ๋‹ค.

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๋ฅผ ์‹ฌ์–ด๋†“๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

@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๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ˆœ๊ฐ„์€ ๋ฐ”๋กœ ๋ฉ”์„œ๋“œ๊ฐ€ ์ข…๋ฃŒ๋˜์„œ ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ๋˜๋Š” ์ˆœ๊ฐ„์ด๋ผ๋Š” ์‚ฌ์‹ค์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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๋„ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๊ฒƒ์„ ํ™•์ธํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด๋ฒคํŠธ๋“ค์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)

์ˆœ์„œ์ ์œผ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋…ธ์ถœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. AFTER_COMMIT๊ณผ AFTER_COMPLETION์€ ORDER์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
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๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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

Last updated