๐ง ์๋ก
๋๋ฉ์ธ ์ด๋ฒคํธ๋ฅผ ํ๋ฒ ์ฌ์ฉํด๋ณด๊ณ ์ ์ด๋ฐ์ ๋ฐ ์๋ฃ๋ค์ ์ฐธ๊ณ ํ๋ฉฐ ์ฌ์ฉ์ ํ๋ ์ค์ ๋ฌธ์ ๊ฐ ํ๋ ๋ฐ์ํ์ต๋๋ค.
์ ๊ฐ ์ด๋ฒคํธ๋ฅผ ํตํด ํ๊ณ ์ ํ๋ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ชจ์ ์์ฑ (๋ชจ์ ์์ฑ ์ด๋ฒคํธ ๋ฐํ)
-> ์ด๋ฒคํธ ์ฒ๋ฆฌ (๋ชจ์ ๊ธฐ๋ณธ ์ญํ ๋ฑ๋ก) & ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ๋ฑ๋ก ์ด๋ฒคํธ ๋ฐํ
-> ์ด๋ฒคํธ ์ฒ๋ฆฌ (๋ชจ์์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ๋ฑ๋ก)
์ฐ์ ๋ชจ์๊ณผ, ๋ชจ์์ ์ญํ , ๋ชจ์ ์ฐธ๊ฐ์๋ ๋ฐ๋ก๋ฐ๋ก ์์ ๋๊ณ ์ ๊ฑฐ๋ ์ ์์ผ๋ฏ๋ก, ๋ค๋ฅธ ์๊ทธ๋ฆฌ๊ฑฐํธ๋ผ ํ๋จํ์ฌ ์ด๋ค์ ๋ถ๋ฆฌํ์์ต๋๋ค.
์ ์ํฉ์์ ์ ๋ @TransactionalEventListener๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค.
@Transactional
@Service
public class CreateClub implements CreateClubUseCase {
@Override
public Long command(final Command command) {
// ... ๋ชจ์ ์์ฑํ๋ ์ฝ๋
// CreateClubEvent ๋ฅผ ๋ฐ์ผ๋ฉด -> ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ๋ฑ๋ก(๋ฑ๋ก ์ดํ ๋ชจ์ ์์ฑํ ์ฌ๋์ ํ์ฅ์ผ๋ก ๋ง๋ค๊ธฐ)
Event.publish(new CreateClubEvent(this, member, club));
return club.id();
}
}
์์ ๊ฐ์ด ๋ชจ์ ์์ฑ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๋ฉด,
@Component
public class CreateDefaultRoleWithCreateClubEventHandler extends EventHandler<CreateClubEvent> {
@Transactional
@TransactionalEventListener
@Override
public void handle(final CreateClubEvent event) {
// ... ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ์์ฑํ๋ ์ฝ๋
// ๊ธฐ๋ณธ ์ญํ ์์ฑ ์ด๋ฒคํธ -> ๋ชจ์์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ๋ง๋ค๊ธฐ
Event.publish(new CreateDefaultRoleEvent(...));
}
}
์์ ๊ฐ์ด ํด๋น ์ด๋ฒคํธ๋ฅผ ๋ฐ์ ๋ชจ์์ ๊ธฐ๋ณธ ์ญํ ์ ์์ฑํ ๋ค, ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ์์ฑ ์ด๋ฒคํธ๋ฅผ ๋ฐํํฉ๋๋ค.
@Component
public class RegisterPresidentWithCreateDefaultRoleEventHandler extends EventHandler<CreateDefaultRoleEvent> {
@Transactional
@TransactionalEventListener
@Override
public void handle(final CreateDefaultRoleEvent event) {
// ... ๋ชจ์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ์ ์ฅํ๋ ์ฝ๋
}
}
์์ฒ๋ผ ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ์์ฑ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ผ๋ฉด ๋ชจ์์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ์ ์ฅํ๋ ค ํ์์ต๋๋ค.
๐ง ๋ฌธ์ ์ํฉ
์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์คํํด๋ณด๋ ์ด๋ฒคํธ๊ฐ ํ๋๋ ์ฒ๋ฆฌ๋์ง ์์์ต๋๋ค.
์ ํํ๋ ์๋ ์ฝ๋๋ ํธ์ถ๋์ง๋ง ๋ชจ์์ ๊ธฐ๋ณธ ์ญํ ์ด ์ ์ฅ๋์ง ์์์ต๋๋ค.
@Component
public class CreateDefaultRoleWithCreateClubEventHandler extends EventHandler<CreateClubEvent> {
@Transactional
@TransactionalEventListener
@Override
public void handle(final CreateClubEvent event) {
// ... ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ์์ฑํ๋ ์ฝ๋
// ๊ธฐ๋ณธ ์ญํ ์์ฑ ์ด๋ฒคํธ -> ๋ชจ์์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ๋ง๋ค๊ธฐ
Event.publish(new CreateDefaultRoleEvent(...));
}
}
๋ํ ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ์์ฑ ์ด๋ฒคํธ๋ฅผ ๋ฐ์ ๋ชจ์์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ์ ์ฅํ๋ ค๋ ์ฝ๋๋ ํธ์ถ๋์ง๋ ์์์ต๋๋ค.
์ฆ ์๋ ์ฝ๋๊ฐ ํธ์ถ๋์ง ์์์ต๋๋ค.
@Component
public class RegisterPresidentWithCreateDefaultRoleEventHandler extends EventHandler<CreateDefaultRoleEvent> {
@Transactional
@TransactionalEventListener
@Override
public void handle(final CreateDefaultRoleEvent event) {
// ... ๋ชจ์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ์ ์ฅํ๋ ์ฝ๋
}
}
๐ง ์์ธ ๋ถ์
(ํน์ ํ๋ฆฐ ๋ถ๋ถ์ด ์๋ค๋ฉด ์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค. ์๋๋ ์ ๊ฐ ์กฐ๊ธ ๋๋ฒ๊น ํด ๋ณธ ๊ฒฐ๊ณผ๋ฅผ ํ ๋๋ก ์์ฑํฉ๋๋ค.)
@TransactionalEventListener ์ phase ์์ฑ์ ๊ธฐ๋ณธ๊ฐ์ AFTER_COMMIT ์ ๋๋ค.
์ฆ ํธ๋์ญ์ ์ด ์ปค๋ฐ๋ ์ดํ ์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ๋์ํ๊ฒ ๋ฉ๋๋ค.
ํธ๋์ญ์ ์ด ์ปค๋ฐ๋ ์ดํ ๋ฆฌ์ค๋๊ฐ ๋์ํ๋ค๊ณ ํด๋, ์ฌ์ ํ ํ๋์ ํธ๋์ญ์ ์ ๋ฌถ์ฌ์์์ ์ฃผ์ํด์ผ ํฉ๋๋ค.
์ปค๋ฐ๋ ํธ๋์ญ์ ์ ๋ค์ commitํ๋๊ฒ์ ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์, ์ฝ๋๊ฐ ์ ๋๋ก ์๋ํ์ง ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ์ ๋๋ค.
AbstractPlatformTransactionManager.processCommit() ๋ฉ์๋๋ฅผ ์ดํด๋ณด๋ฉด, ๋ค์๊ณผ ๊ฐ์ ํ๋ฆ์ ๊ฐ์ง๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
prepareForCommit(status);
triggerBeforeCommit(status); // BEFORE_COMMIT
triggerBeforeCompletion(status);
doCommit(status); // ์ค์ ์ปค๋ฐ ์ํ
triggerAfterCommit(status); // AFTER_COMMIT
cleanupAfterCompletion(status); // AFTER_COMPLETION (ํธ๋์ญ์
๋ฆฌ์์ค ์ ๋ฆฌ)
} catch(...) {
doRollbackOnCommitException(status, ex); // ๋กค๋ฐฑ
}
}
๋ฐ๋ผ์ AFTER_COMMIT ์์๋ ์ค์ ์ปค๋ฐ์ด ์คํ๋ ์ํ์ด๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ์ ์ธ insert sql์ด ์ปค๋ฐ๋์ง ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ๋๋ค.
๐ง ํด๊ฒฐ
@TransactionalEventListener์ phase๋ฅผ BEFORE_COMMIT์ผ๋ก ์ค์ ํ๊ฑฐ๋,
@Transactional์ propagation์ REQUIRES_NEW๋ก ์ค์ ํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
(์ ํํ๋ @TransactionalEventListener์ phase๋ฅผ BEFORE_COMMIT์ผ๋ก ์ค์ ํ๋ฉด, ์ฒซ๋ฒ์งธ ์ด๋ฒคํธ์ ์ฒ๋ฆฌ๋ ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ ๋๋ฒ์งธ ์ด๋ฒคํธ์ ํธ๋ค๋ฌ ์ญ์ @TransactionalEventListener์ phase๋ฅผ BEFORE_COMMIT์ผ๋ก ์ค์ ํ ๊ฒฝ์ฐ๋ผ๋ฉด ์ฒ๋ฆฌ๋์ง ์์ต๋๋ค. (before_commit์ ํ๋ฒ๋ง ํธ์ถ๋๋ฏ๋ก, ์ด๋ฏธ ์ฒซ ํธ์ถ์์ ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์ ๋๋ค.))
์ ๋ @TransactionalEventListene๋์ @EventListner๋ฅผ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ์๋๋ฐ, ์ด์ ๋ ์๋์ ๊ฐ์ต๋๋ค.
๋ชจ์ ์์ฑ -> ๋ชจ์ ๊ธฐ๋ณธ ์ญํ ์ ์ฅ -> ๋ชจ์์ ์์ฑํ ํ์์ ํ์ฅ์ผ๋ก ๋ฑ๋ก
์๊ฐํด๋ณด๋ ์ ํ๋ฆ์ ํ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ด๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅด๋ค ์๊ฐํ์ฌ @TransactionalEventListener ๋์ @EventListner๋ฅผ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ์์ต๋๋ค.
@TransactionalEventListener๋ฅผ ๊ณ์ ์ฌ์ฉํ ๊ฒ์ด๋ผ๋ฉด @Transactional(propagation = REQUIRES_NEW)๋ฅผ ์ฌ์ฉํ ์ ์์ง๋ง, ์ด๋๋ ํธ๋์ญ์ ์ด ๋ฌ๋ผ์ ธ ์์์ฑ ์ปจํ ์คํธ๊ฐ ๋ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ ์ด ๋ถ๋ถ์ ์ ์ํด์ฃผ์๋ฉฐ ๊ฐ๋ฐํ์๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
๐ง ์ฐธ๊ณ
https://suhwan.dev/2020/01/16/spring-transaction-common-mistakes/
https://soongjamm.tistory.com/155?category=1217576