@Transactional ์ •๋ฆฌ

@Transactional ์™„์ „ ์ •๋ณต — ์Šคํ”„๋ง ํŠธ๋žœ์žญ์…˜์˜ ๋™์ž‘ ์›๋ฆฌ(AOP ํ”„๋ก์‹œ)๋ถ€ํ„ฐ 7๊ฐ€์ง€ ์ „ํŒŒ ์˜ต์…˜, ๊ฒฉ๋ฆฌ ์ˆ˜์ค€๋ณ„ ๋™์‹œ์„ฑ ๋ฌธ์ œ, ๋กค๋ฐฑ ๊ทœ์น™๊ณผ readOnly๊นŒ์ง€ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ•œ๋‹ค.

๋“ค์–ด๊ฐ€๋ฉฐ

@Transactional์€ ์Šคํ”„๋ง์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ๋Š” ์• ๋„ˆํ…Œ์ด์…˜ ์ค‘ ํ•˜๋‚˜๋‹ค. ๋ฉ”์„œ๋“œ ์œ„์— ํ•œ ์ค„ ๋ถ™์ด๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์•Œ์•„์„œ ์‹œ์ž‘๋˜๊ณ  ์ปค๋ฐ‹๋˜๋‹ˆ, ๋™์ž‘ ์›๋ฆฌ๋ฅผ ๋ชฐ๋ผ๋„ ๋Œ€๋ถ€๋ถ„์€ ์ž˜ ๋Œ์•„๊ฐ„๋‹ค.

๋ฌธ์ œ๋Š” "๋Œ€๋ถ€๋ถ„"์ด๋‹ค. ์–ด๋А ๋‚  ๋ถ„๋ช… ์˜ˆ์™ธ๊ฐ€ ํ„ฐ์กŒ๋Š”๋ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กค๋ฐฑ๋˜์ง€ ์•Š๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ–ˆ๋”๋‹ˆ ํŠธ๋žœ์žญ์…˜์ด ์˜๋„์™€ ๋‹ค๋ฅด๊ฒŒ ๋ฌถ์ด๊ฑฐ๋‚˜, ๊ฐ™์€ ํด๋ž˜์Šค ์•ˆ์—์„œ ํ˜ธ์ถœํ•œ @Transactional ๋ฉ”์„œ๋“œ๊ฐ€ ์•„์˜ˆ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์ผ์ด ์ƒ๊ธด๋‹ค. ์ด ํ•จ์ •๋“ค์€ ์ „๋ถ€ @Transactional์ด ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€๋ฅผ ๋ชจ๋ฅด๋ฉด ๋””๋ฒ„๊น…์กฐ์ฐจ ์–ด๋ ต๋‹ค.

์ด์ „ N+1 ๊ธ€์ด "์ž˜ ๋Œ์•„๊ฐ€๋Š”๋ฐ ๋А๋ฆฐ" ๋ฌธ์ œ์˜€๋‹ค๋ฉด, ํŠธ๋žœ์žญ์…˜์€ "์ž˜ ๋Œ์•„๊ฐ€๋Š” ์ค„ ์•Œ์•˜๋Š”๋ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ์•ˆ ๋งž๋Š”" ๋ฌธ์ œ๋‹ค. ๋‘˜ ๋‹ค ์šด์˜์—์„œ์•ผ ํ„ฐ์ง„๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๋™์ž‘ ์›๋ฆฌ(ํ”„๋ก์‹œ) → ์ „ํŒŒ(Propagation) → ๊ฒฉ๋ฆฌ ์ˆ˜์ค€(Isolation) → ๋กค๋ฐฑ ๊ทœ์น™ → ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜ ์ˆœ์œผ๋กœ ์ •๋ฆฌํ•œ๋‹ค. ์˜ˆ์ œ๋Š” Spring Boot 4 / Spring Framework 6.x ๊ธฐ์ค€์ด๋‹ค.


1. ํŠธ๋žœ์žญ์…˜๊ณผ ACID

ํŠธ๋žœ์žญ์…˜(Transaction)์€ ๋” ์ด์ƒ ์ชผ๊ฐค ์ˆ˜ ์—†๋Š” ํ•˜๋‚˜์˜ ์ž‘์—… ๋‹จ์œ„๋‹ค. ๊ณ„์ขŒ ์ด์ฒด๋ฅผ ๋– ์˜ฌ๋ฆฌ๋ฉด ์‰ฝ๋‹ค. "A ๊ณ„์ขŒ์—์„œ ์ถœ๊ธˆ"๊ณผ "B ๊ณ„์ขŒ๋กœ ์ž…๊ธˆ"์€ ๋ฐ˜๋“œ์‹œ ํ•จ๊ป˜ ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜ ํ•จ๊ป˜ ์‹คํŒจํ•ด์•ผ ํ•œ๋‹ค. ์ถœ๊ธˆ๋งŒ ๋˜๊ณ  ์ž…๊ธˆ์ด ์•ˆ ๋˜๋ฉด ๋ˆ์ด ์ฆ๋ฐœํ•œ๋‹ค.

์ด ๋ณด์žฅ์„ ํ”ํžˆ ACID๋กœ ์š”์•ฝํ•œ๋‹ค.

์†์„ฑ ์˜๋ฏธ
Atomicity (์›์ž์„ฑ) ์ „๋ถ€ ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜ ์ „๋ถ€ ์‹คํŒจํ•œ๋‹ค. ์ค‘๊ฐ„ ์ƒํƒœ๋Š” ์—†๋‹ค
Consistency (์ผ๊ด€์„ฑ) ํŠธ๋žœ์žญ์…˜ ์ „ํ›„๋กœ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๊ทœ์น™์ด ์œ ์ง€๋œ๋‹ค
Isolation (๊ฒฉ๋ฆฌ์„ฑ) ๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ํŠธ๋žœ์žญ์…˜๋ผ๋ฆฌ ์„œ๋กœ ๊ฐ„์„ญํ•˜์ง€ ์•Š๋Š”๋‹ค
Durability (์ง€์†์„ฑ) ์ปค๋ฐ‹๋œ ๊ฒฐ๊ณผ๋Š” ์žฅ์• ๊ฐ€ ๋‚˜๋„ ๋ณด์กด๋œ๋‹ค

์Šคํ”„๋ง์˜ @Transactional์€ ์ด ์ค‘ ์›์ž์„ฑ๊ณผ ๊ฒฉ๋ฆฌ์„ฑ์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ ์„ ์–ธ์ ์œผ๋กœ ๋‹ค๋ฃจ๊ฒŒ ํ•ด์ค€๋‹ค.

@Service
@RequiredArgsConstructor
public class TransferService {

    private final AccountRepository accountRepository;

    @Transactional
    public void transfer(Long fromId, Long toId, long amount) {
        Account from = accountRepository.findById(fromId).orElseThrow();
        Account to = accountRepository.findById(toId).orElseThrow();

        from.withdraw(amount);   // ์ถœ๊ธˆ
        to.deposit(amount);      // ์ž…๊ธˆ
        // ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์ƒ ์ข…๋ฃŒ๋˜๋ฉด ์ปค๋ฐ‹, ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง€๋ฉด ๋กค๋ฐฑ
    }
}

transfer ์•ˆ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋‚˜๋ฉด ์ถœ๊ธˆ๋„ ์ž…๊ธˆ๋„ ๋ชจ๋‘ ์ทจ์†Œ๋œ๋‹ค. ์ด "์„ ์–ธ๋งŒ ํ•˜๋ฉด ์•Œ์•„์„œ ๋œ๋‹ค"๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒŒ ๋‹ค์Œ์— ๋ณผ ํ”„๋ก์‹œ๋‹ค.


2. @Transactional์€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€ — AOP ํ”„๋ก์‹œ

@Transactional์˜ ๋น„๋ฐ€์€ ์Šคํ”„๋ง AOP ํ”„๋ก์‹œ๋‹ค. ์Šคํ”„๋ง์€ @Transactional์ด ๋ถ™์€ ๋นˆ์„ ๊ทธ๋Œ€๋กœ ์“ฐ์ง€ ์•Š๊ณ , ๊ทธ ๋นˆ์„ ๊ฐ์‹ธ๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋Œ€์‹  ์ฃผ์ž…ํ•œ๋‹ค. ํ˜ธ์ถœ์€ ํ•ญ์ƒ ์ด ํ”„๋ก์‹œ๋ฅผ ๊ฑฐ์นœ๋‹ค.

ํ”„๋ก์‹œ๋Š” ์‹ค์ œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์ƒ ์ข…๋ฃŒ๋˜๋ฉด ์ปค๋ฐ‹, ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง€๋ฉด ๋กค๋ฐฑํ•œ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ์ฝ”๋“œ์—๋Š” ํŠธ๋žœ์žญ์…˜ ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ํ•œ ์ค„๋„ ์—†์ง€๋งŒ, ํ”„๋ก์‹œ๊ฐ€ ๊ทธ ์•ž๋’ค๋ฅผ ๊ฐ์‹ธ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด "ํ”„๋ก์‹œ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•œ๋‹ค"๋Š” ๊ตฌ์กฐ์—์„œ ๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ํ•จ์ •์ด ๋‚˜์˜จ๋‹ค.

ํ•จ์ • 1 — ๋‚ด๋ถ€ ํ˜ธ์ถœ(self-invocation)

๊ฐ™์€ ํด๋ž˜์Šค ์•ˆ์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฉด ํ”„๋ก์‹œ๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ๊ทธ ๋ฉ”์„œ๋“œ์˜ @Transactional์€ ๋ฌด์‹œ๋œ๋‹ค.

@Service
public class OrderService {

    public void placeOrder(Order order) {
        // โŒ this.saveOrder() ํ˜ธ์ถœ → ํ”„๋ก์‹œ๋ฅผ ์•ˆ ๊ฑฐ์นจ → @Transactional ๋ฌดํšจ
        saveOrder(order);
    }

    @Transactional
    public void saveOrder(Order order) {
        // ํŠธ๋žœ์žญ์…˜์ด ์‹œ์ž‘๋  ๊ฑฐ๋ผ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ, ๋‚ด๋ถ€ ํ˜ธ์ถœ์ด๋ผ ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค
    }
}

placeOrder์—์„œ saveOrder๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด this.saveOrder()๊ฐ€ ๋˜์–ด ํ”„๋ก์‹œ๋ฅผ ์šฐํšŒํ•œ๋‹ค. ํ•ด๊ฒฐ์ฑ…์€ ํŠธ๋žœ์žญ์…˜์ด ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค๋ฅธ ๋นˆ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ฑฐ๋‚˜, ์ง„์ž… ๋ฉ”์„œ๋“œ์ธ placeOrder ์ชฝ์— @Transactional์„ ๋ถ™์ด๋Š” ๊ฒƒ์ด๋‹ค. ์ž๊ธฐ ์ž์‹ ์„ ํ”„๋ก์‹œ๋กœ ์ฃผ์ž…๋ฐ›๋Š” ์šฐํšŒ๋ฒ•๋„ ์žˆ์ง€๋งŒ ๊ถŒ์žฅ๋˜์ง„ ์•Š๋Š”๋‹ค.

ํ•จ์ • 2 — public ๋ฉ”์„œ๋“œ์—๋งŒ ์ ์šฉ

ํ”„๋ก์‹œ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ๊ธฐ๋ณธ์ ์œผ๋กœ public ๋ฉ”์„œ๋“œ์—๋งŒ ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋œ๋‹ค. private, protected ๋ฉ”์„œ๋“œ์— @Transactional์„ ๋ถ™์—ฌ๋„ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค(์กฐ์šฉํžˆ ๋ฌด์‹œ๋œ๋‹ค).


3. ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ (Propagation)

์ „ํŒŒ(Propagation)๋Š” ์ด๋ฏธ ํŠธ๋žœ์žญ์…˜์ด ์ง„ํ–‰ ์ค‘์ผ ๋•Œ, ์ƒˆ ํŠธ๋žœ์žญ์…˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋‚˜๋ฉด ์–ด๋–ป๊ฒŒ ํ• ์ง€๋ฅผ ์ •ํ•˜๋Š” ๊ทœ์น™์ด๋‹ค. ํ•ฉ๋ฅ˜ํ• ์ง€, ์ƒˆ๋กœ ๋งŒ๋“ค์ง€, ๋ฌด์‹œํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. ์Šคํ”„๋ง์€ 7๊ฐ€์ง€ ์˜ต์…˜์„ ์ œ๊ณตํ•œ๋‹ค.

์ „ํŒŒ ์˜ต์…˜ ์ง„ํ–‰ ์ค‘์ธ ํŠธ๋žœ์žญ์…˜์ด ์žˆ์œผ๋ฉด ์—†์œผ๋ฉด
REQUIRED (๊ธฐ๋ณธ) ๊ทธ ํŠธ๋žœ์žญ์…˜์— ํ•ฉ๋ฅ˜ ์ƒˆ๋กœ ์‹œ์ž‘
REQUIRES_NEW ๊ธฐ์กด ๊ฒƒ์„ ์ž ์‹œ ๋ณด๋ฅ˜ํ•˜๊ณ  ์ƒˆ๋กœ ์‹œ์ž‘ ์ƒˆ๋กœ ์‹œ์ž‘
NESTED ์ค‘์ฒฉ ํŠธ๋žœ์žญ์…˜(savepoint) ์ƒ์„ฑ ์ƒˆ๋กœ ์‹œ์ž‘
SUPPORTS ํ•ฉ๋ฅ˜ ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰
NOT_SUPPORTED ๋ณด๋ฅ˜ํ•˜๊ณ  ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰ ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰
MANDATORY ํ•ฉ๋ฅ˜ ์˜ˆ์™ธ ๋ฐœ์ƒ
NEVER ์˜ˆ์™ธ ๋ฐœ์ƒ ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰

์‹ค๋ฌด์—์„œ ๊ฑฐ์˜ ๋Œ€๋ถ€๋ถ„์€ ๊ธฐ๋ณธ๊ฐ’์ธ REQUIRED๋ฅผ ์“ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ€๋” REQUIRES_NEW๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์ด ๋‘˜์˜ ์ฐจ์ด๊ฐ€ ํ•ต์‹ฌ์ด๋‹ค.

REQUIRED๋Š” ํ˜ธ์ถœ ์ฒด์ธ ์ „์ฒด๋ฅผ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ์ค‘๊ฐ„ ์–ด๋””์„œ๋“  ์˜ˆ์™ธ๊ฐ€ ๋‚˜๋ฉด ์ „์ฒด๊ฐ€ ํ•จ๊ป˜ ๋กค๋ฐฑ๋œ๋‹ค.

REQUIRES_NEW๋Š” ๊ธฐ์กด ํŠธ๋žœ์žญ์…˜์„ ์ž ์‹œ ๋ณด๋ฅ˜์‹œํ‚ค๊ณ  ์™„์ „ํžˆ ๋…๋ฆฝ๋œ ์ƒˆ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค. ์•ˆ์ชฝ ํŠธ๋žœ์žญ์…˜์€ ๋ฐ”๊นฅ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋”ฐ๋กœ ์ปค๋ฐ‹·๋กค๋ฐฑ๋œ๋‹ค. ๋Œ€ํ‘œ์ ์ธ ์‚ฌ์šฉ ์˜ˆ๊ฐ€ "๋ณธ ์ž‘์—…์€ ์‹คํŒจํ•˜๋”๋ผ๋„ ๋กœ๊ทธ·์ด๋ ฅ์€ ๋ฐ˜๋“œ์‹œ ๋‚จ๊ฒจ์•ผ ํ•˜๋Š”" ๊ฒฝ์šฐ๋‹ค.

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final OrderLogService orderLogService;

    @Transactional // REQUIRED
    public void placeOrder(Order order) {
        orderLogService.writeLog("์ฃผ๋ฌธ ์‹œ๋„: " + order.getId()); // ๋…๋ฆฝ ํŠธ๋žœ์žญ์…˜
        orderRepository.save(order);
        validateStock(order); // ์—ฌ๊ธฐ์„œ ์˜ˆ์™ธ๊ฐ€ ๋‚˜๋ฉด ์ฃผ๋ฌธ์€ ๋กค๋ฐฑ
    }
}

@Service
@RequiredArgsConstructor
public class OrderLogService {

    private final OrderLogRepository orderLogRepository;

    // ๋ฐ”๊นฅ ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋ผ๋„ ์ด ๋กœ๊ทธ๋Š” ์‚ด์•„๋‚จ๋Š”๋‹ค
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void writeLog(String message) {
        orderLogRepository.save(new OrderLog(message));
    }
}

์œ„ ์ฝ”๋“œ์—์„œ validateStock์ด ์‹คํŒจํ•ด placeOrder๊ฐ€ ๋กค๋ฐฑ๋ผ๋„, REQUIRES_NEW๋กœ ๋ถ„๋ฆฌ๋œ ๋กœ๊ทธ๋Š” ๋ณ„๋„ ํŠธ๋žœ์žญ์…˜์—์„œ ์ด๋ฏธ ์ปค๋ฐ‹๋˜์–ด ๋‚จ๋Š”๋‹ค. ๋‹จ, REQUIRES_NEW๋Š” ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ณ„๋„ ์ปค๋„ฅ์…˜์„ ์žก์œผ๋ฏ€๋กœ ๋‚จ์šฉํ•˜๋ฉด ์ปค๋„ฅ์…˜ ํ’€์ด ๋น ๋ฅด๊ฒŒ ๊ณ ๊ฐˆ๋œ๋‹ค. ๊ผญ ํ•„์š”ํ•œ ๊ณณ์—๋งŒ ์“ด๋‹ค.

NESTED๋Š” ๋ถ€๋ชจ ํŠธ๋žœ์žญ์…˜ ์•ˆ์— ์„ธ์ด๋ธŒํฌ์ธํŠธ(savepoint)๋ฅผ ๋งŒ๋“ ๋‹ค. ์ž์‹์ด ๋กค๋ฐฑ๋˜๋ฉด ์„ธ์ด๋ธŒํฌ์ธํŠธ๊นŒ์ง€๋งŒ ๋˜๋Œ๋ฆฌ๊ณ  ๋ถ€๋ชจ๋Š” ๊ณ„์† ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹จ, JPA/Hibernate ์กฐํ•ฉ์—์„œ๋Š” ์ง€์›์ด ์ œํ•œ์ ์ด๋ผ ์‹ค๋ฌด ์‚ฌ์šฉ ๋นˆ๋„๋Š” ๋‚ฎ๋‹ค.


4. ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ (Isolation Level)

๊ฒฉ๋ฆฌ ์ˆ˜์ค€์€ ๋™์‹œ์— ์‹คํ–‰๋˜๋Š” ํŠธ๋žœ์žญ์…˜๋“ค์ด ์„œ๋กœ์˜ ์ž‘์—…์„ ์–ผ๋งˆ๋‚˜ ๋ณผ ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ •ํ•œ๋‹ค. ๊ฒฉ๋ฆฌ๋ฅผ ๋А์Šจํ•˜๊ฒŒ ํ•˜๋ฉด ์„ฑ๋Šฅ(๋™์‹œ์„ฑ)์€ ์ข‹์•„์ง€์ง€๋งŒ ์ด์ƒ ํ˜„์ƒ์ด ์ƒ๊ธฐ๊ณ , ๊ฐ•ํ•˜๊ฒŒ ํ•˜๋ฉด ์•ˆ์ „ํ•˜์ง€๋งŒ ๋™์‹œ์„ฑ์ด ๋–จ์–ด์ง„๋‹ค. ์ „ํ˜•์ ์ธ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋‹ค.

๊ฒฉ๋ฆฌ๊ฐ€ ์•ฝํ•  ๋•Œ ์ƒ๊ธฐ๋Š” ๋Œ€ํ‘œ์  ์ด์ƒ ํ˜„์ƒ ์„ธ ๊ฐ€์ง€๋ถ€ํ„ฐ ๋ณด์ž.

  • Dirty Read — ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์•„์ง ์ปค๋ฐ‹ํ•˜์ง€ ์•Š์€ ๋ณ€๊ฒฝ์„ ์ฝ๋Š”๋‹ค. ๊ทธ ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜๋ฉด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์€ ์…ˆ์ด ๋œ๋‹ค.
  • Non-Repeatable Read — ํ•œ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ๊ฐ™์€ ํ–‰์„ ๋‘ ๋ฒˆ ์ฝ์—ˆ๋Š”๋ฐ, ๊ทธ ์‚ฌ์ด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ˆ˜์ •·์ปค๋ฐ‹ํ•ด์„œ ๊ฐ’์ด ๋‹ฌ๋ผ์ง„๋‹ค.
  • Phantom Read — ๊ฐ™์€ ์กฐ๊ฑด์œผ๋กœ ๋‘ ๋ฒˆ ์กฐํšŒํ–ˆ๋Š”๋ฐ, ๊ทธ ์‚ฌ์ด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ํ–‰์„ ์ถ”๊ฐ€·์‚ญ์ œํ•ด์„œ ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์˜ ํ–‰ ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค.

์ด ์„ธ ํ˜„์ƒ์„ ์–ด๋””๊นŒ์ง€ ๋ง‰๋А๋ƒ๋กœ ํ‘œ์ค€ SQL์€ 4๋‹จ๊ณ„ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ์ •์˜ํ•œ๋‹ค.

ํ‘œ์—์„œ ๋ณด๋“ฏ ์•„๋ž˜๋กœ ๊ฐˆ์ˆ˜๋ก ๋” ๋งŽ์€ ์ด์ƒ ํ˜„์ƒ์„ ๋ง‰์ง€๋งŒ, ๊ทธ๋งŒํผ ๋ฝ์ด ๊ฐ•ํ•ด์ ธ ๋™์‹œ์„ฑ์€ ๋–จ์–ด์ง„๋‹ค. ์Šคํ”„๋ง์—์„œ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ์ง€์ •ํ•œ๋‹ค.

@Transactional(isolation = Isolation.REPEATABLE_READ)
public Report buildReport(Long accountId) {
    // ๋ฆฌํฌํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋™์•ˆ ์กฐํšŒ ๊ฒฐ๊ณผ๊ฐ€ ์ค‘๊ฐ„์— ๋ฐ”๋€Œ์ง€ ์•Š๋„๋ก ๋ณด์žฅ
    ...
}

๋‹ค๋งŒ ๊ธฐ์–ตํ•  ์ ์ด ์žˆ๋‹ค. Isolation.DEFAULT(๊ธฐ๋ณธ๊ฐ’)๋Š” DB์˜ ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅธ๋‹ค. ๊ทธ๋ฆฌ๊ณ  DB๋งˆ๋‹ค ๊ธฐ๋ณธ๊ฐ’์ด ๋‹ค๋ฅด๋‹ค. MySQL(InnoDB)์€ REPEATABLE READ, PostgreSQL๊ณผ Oracle์€ READ COMMITTED๊ฐ€ ๊ธฐ๋ณธ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด DB์— ๋”ฐ๋ผ ๋™์ž‘์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ์›น ์„œ๋น„์Šค๋Š” READ COMMITTED๋กœ ์ถฉ๋ถ„ํ•˜๋ฉฐ, ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ๋†’์ด๊ธฐ ์ „์— ์ •๋ง ํ•„์š”ํ•œ์ง€ ๋”ฐ์ ธ๋ด์•ผ ํ•œ๋‹ค.


5. ๋กค๋ฐฑ ๊ทœ์น™ — ๊ฐ€์žฅ ํ”ํ•œ ํ•จ์ •

์—ฌ๊ธฐ์„œ ๋งŽ์€ ์‚ฌ๋žŒ์ด ํ•œ ๋ฒˆ์”ฉ ๋ฐ์ธ๋‹ค. ์Šคํ”„๋ง์˜ ๊ธฐ๋ณธ ๋กค๋ฐฑ ๊ทœ์น™์€ ์ด๋ ‡๋‹ค.

์–ธ์ฒดํฌ ์˜ˆ์™ธ(RuntimeException)์™€ Error๋Š” ๋กค๋ฐฑํ•œ๋‹ค. ์ฒดํฌ ์˜ˆ์™ธ(checked exception)๋Š” ๋กค๋ฐฑํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ฆ‰, IOException์ด๋‚˜ ์ง์ ‘ ๋งŒ๋“  ์ฒดํฌ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋œ๋‹ค. ์˜๋„์™€ ์ •๋ฐ˜๋Œ€์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

@Transactional
public void process() throws Exception {
    repository.save(entity);
    throw new Exception("์ฒดํฌ ์˜ˆ์™ธ!"); // โŒ ๋กค๋ฐฑ ์•ˆ ๋จ → entity๊ฐ€ ์ปค๋ฐ‹๋œ๋‹ค
}

์ฒดํฌ ์˜ˆ์™ธ์—๋„ ๋กค๋ฐฑ์„ ์ ์šฉํ•˜๋ ค๋ฉด rollbackFor๋กœ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค.

@Transactional(rollbackFor = Exception.class)
public void process() throws Exception {
    repository.save(entity);
    throw new IOException("์ด์ œ๋Š” ๋กค๋ฐฑ๋œ๋‹ค"); // โœ… rollbackFor ๋•๋ถ„์— ๋กค๋ฐฑ
}

๋ฐ˜๋Œ€๋กœ ํŠน์ • ์˜ˆ์™ธ๋Š” ๋กค๋ฐฑ์—์„œ ์ œ์™ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด noRollbackFor๋ฅผ ์“ด๋‹ค. ์‹ค๋ฌด์—์„œ๋Š” "๋น„์ฆˆ๋‹ˆ์Šค ์˜ˆ์™ธ๋Š” ์ „๋ถ€ ๋กค๋ฐฑ"์„ ์›ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„ rollbackFor = Exception.class๋ฅผ ์ž์ฃผ ๋ณธ๋‹ค.

๋˜ ํ•˜๋‚˜ ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜๋Š” ์˜ˆ์™ธ๋ฅผ try-catch๋กœ ์‚ผํ‚ค๋Š” ๊ฒƒ์ด๋‹ค.

@Transactional
public void process() {
    try {
        repository.save(entity);
        riskyOperation(); // ์—ฌ๊ธฐ์„œ RuntimeException ๋ฐœ์ƒ
    } catch (Exception e) {
        log.error("์—๋Ÿฌ ๋ฐœ์ƒ", e); // โŒ ์˜ˆ์™ธ๋ฅผ ์žก์•„์„œ ๋๋ƒ„ → ํ”„๋ก์‹œ๊ฐ€ ๋ชจ๋ฆ„ → ์ปค๋ฐ‹๋œ๋‹ค
    }
}

ํ”„๋ก์‹œ๋Š” ๋ฉ”์„œ๋“œ ๋ฐ–์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ ธ์•ผ ๋กค๋ฐฑํ•œ๋‹ค. ์•ˆ์—์„œ ์žก์•„๋ฒ„๋ฆฌ๋ฉด ํ”„๋ก์‹œ ์ž…์žฅ์—์„  ์ •์ƒ ์ข…๋ฃŒ๋กœ ๋ณด์—ฌ ์ปค๋ฐ‹ํ•œ๋‹ค. ์žก์•„์„œ ์ฒ˜๋ฆฌํ•˜๋˜ ๋กค๋ฐฑ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋˜์ง€๊ฑฐ๋‚˜ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()๋กœ ๋ช…์‹œ์ ์œผ๋กœ ๋กค๋ฐฑ์„ ํ‘œ์‹œํ•ด์•ผ ํ•œ๋‹ค.


6. readOnly์™€ ์‹ค์ˆ˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์กฐํšŒ ์ „์šฉ ๋ฉ”์„œ๋“œ์—๋Š” readOnly = true๋ฅผ ๋ถ™์ด๋Š” ๊ฒŒ ์ข‹๋‹ค.

@Transactional(readOnly = true)
public List<Member> findMembers() {
    return memberRepository.findAll();
}

readOnly = true๋Š” JPA์—์„œ ๋”ํ‹ฐ ์ฒดํ‚น(๋ณ€๊ฒฝ ๊ฐ์ง€)์„ ์œ„ํ•œ ์Šค๋ƒ…์ƒท์„ ๋งŒ๋“ค์ง€ ์•Š์•„ ๋ฉ”๋ชจ๋ฆฌ·์„ฑ๋Šฅ ์ด์ ์ด ์žˆ๊ณ , ์ผ๋ถ€ DB·๋“œ๋ผ์ด๋ฒ„์—์„œ๋Š” ์ฝ๊ธฐ ์ „์šฉ ์ตœ์ ํ™” ํžŒํŠธ๋กœ๋„ ์ž‘๋™ํ•œ๋‹ค. ๋˜ํ•œ "์ด ๋ฉ”์„œ๋“œ๋Š” ์“ฐ๊ธฐ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค"๋Š” ์˜๋„๋ฅผ ์ฝ”๋“œ๋กœ ๋“œ๋Ÿฌ๋‚ด๋Š” ๋ฌธ์„œ ์—ญํ• ๋„ ํ•œ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹ค๋ฌด์—์„œ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋‚˜์˜ค๋Š” ์‹ค์ˆ˜๋ฅผ ์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๋‹ค.

  • ๋‚ด๋ถ€ ํ˜ธ์ถœ(self-invocation) — ๊ฐ™์€ ํด๋ž˜์Šค ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์€ ํ”„๋ก์‹œ๋ฅผ ์•ˆ ๊ฑฐ์ณ @Transactional์ด ๋ฌด์‹œ๋œ๋‹ค. ๋นˆ์„ ๋ถ„๋ฆฌํ•˜๋ผ.
  • private ๋ฉ”์„œ๋“œ์— ์ ์šฉ — public ๋ฉ”์„œ๋“œ์—๋งŒ ์ ์šฉ๋œ๋‹ค.
  • ์˜ˆ์™ธ๋ฅผ try-catch๋กœ ์‚ผํ‚ด — ์˜ˆ์™ธ๊ฐ€ ๋ฐ–์œผ๋กœ ๋˜์ ธ์ง€์ง€ ์•Š์œผ๋ฉด ๋กค๋ฐฑ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • ์ฒดํฌ ์˜ˆ์™ธ ๋กค๋ฐฑ ๊ธฐ๋Œ€ — ๊ธฐ๋ณธ์€ ์–ธ์ฒดํฌ ์˜ˆ์™ธ๋งŒ ๋กค๋ฐฑ. ํ•„์š”ํ•˜๋ฉด rollbackFor.
  • ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๋ฅผ ๋„ˆ๋ฌด ๋„“๊ฒŒ — ์™ธ๋ถ€ API ํ˜ธ์ถœ์ด๋‚˜ ๊ธด ์ž‘์—…์„ ํŠธ๋žœ์žญ์…˜ ์•ˆ์— ๋„ฃ์œผ๋ฉด ์ปค๋„ฅ์…˜์„ ์˜ค๋ž˜ ์ ์œ ํ•ด ํ’€์ด ๊ณ ๊ฐˆ๋œ๋‹ค. ํŠธ๋žœ์žญ์…˜์€ DB ์ž‘์—… ๊ตฌ๊ฐ„๋งŒ ์งง๊ฒŒ ๊ฐ์‹ธ๋ผ.
  • REQUIRES_NEW ๋‚จ์šฉ — ๋ณ„๋„ ์ปค๋„ฅ์…˜์„ ์žก์œผ๋ฏ€๋กœ ์ปค๋„ฅ์…˜ ํ’€ ๊ณ ๊ฐˆ ์œ„ํ—˜. ๊ผญ ํ•„์š”ํ•œ ๊ณณ์—๋งŒ.

๋งˆ์น˜๋ฉฐ

@Transactional์€ ํ•œ ์ค„๋กœ ๊ฐ•๋ ฅํ•œ ์ผ์„ ํ•ด์ฃผ์ง€๋งŒ, ๊ทธ ํ•œ ์ค„ ๋’ค์—๋Š” ํ”„๋ก์‹œ๋ผ๋Š” ๋™์ž‘ ์›๋ฆฌ๊ฐ€ ์ˆจ์–ด ์žˆ๋‹ค. ์ด ์›๋ฆฌ๋งŒ ์ดํ•ดํ•˜๋ฉด self-invocation, ๋กค๋ฐฑ ๋ˆ„๋ฝ, ์˜ˆ์™ธ ์‚ผํ‚ด ๊ฐ™์€ ํ•จ์ •์ด ์™œ ์ƒ๊ธฐ๋Š”์ง€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์„ค๋ช…๋œ๋‹ค.

์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๋‹ค. ์ „ํŒŒ(Propagation)๋Š” ํŠธ๋žœ์žญ์…˜๋ผ๋ฆฌ ์–ด๋–ป๊ฒŒ ๋ฌถ์„์ง€๋ฅผ, ๊ฒฉ๋ฆฌ ์ˆ˜์ค€(Isolation)์€ ๋™์‹œ ํŠธ๋žœ์žญ์…˜์ด ์„œ๋กœ๋ฅผ ์–ผ๋งˆ๋‚˜ ๋ณผ์ง€๋ฅผ ์ •ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋กค๋ฐฑ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์–ธ์ฒดํฌ ์˜ˆ์™ธ์—๋งŒ ๋™์ž‘ํ•˜๋ฉฐ, ์˜ˆ์™ธ๊ฐ€ ํ”„๋ก์‹œ ๋ฐ–์œผ๋กœ ๋˜์ ธ์ ธ์•ผ ํ•œ๋‹ค๋Š” ์ „์ œ๊ฐ€ ๊น”๋ ค ์žˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’(REQUIRED + DB ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€)์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค. ์ค‘์š”ํ•œ ๊ฑด "๊ธฐ๋ณธ๊ฐ’์ด ๋ฌด์—‡์ด๊ณ  ์–ธ์ œ ๋ฒ—์–ด๋‚˜์•ผ ํ•˜๋Š”์ง€"๋ฅผ ์•„๋Š” ๊ฒƒ์ด๋‹ค. ํŠธ๋žœ์žญ์…˜์€ ์ž˜ ๋Œ์•„๊ฐˆ ๋• ๋ณด์ด์ง€ ์•Š๋‹ค๊ฐ€, ์–ด๊ธ‹๋‚˜๋Š” ์ˆœ๊ฐ„ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ๋ฌด๋„ˆ๋œจ๋ฆฐ๋‹ค. ์›๋ฆฌ๋ฅผ ์•Œ์•„์•ผ ๊ทธ ์ˆœ๊ฐ„์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.


์ฐธ๊ณ  ์ถœ์ฒ˜