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

[ Spring ] ํŠธ๋žœ์žญ์…˜.. ๋‹ค๊ฐ€๊ฐˆ์ˆ˜๋ก ๋ฉ€๊ฒŒ ๋А๊ปด์ง€๋Š” ์นœ๊ตฌ

by carrot0911 2025. 1. 5.

ํŠธ๋žœ์žญ์…˜์ด๋ž€ ๊ณผ์—ฐ ๋ฌด์—‡์ผ๊นŒ??

ํ•˜๋‚˜์˜ ์ž‘์—… ๋‹จ์œ„๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ฌถ์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ด์ค€๋‹ค!
์ด๋ฅผ ํ†ตํ•ด์„œ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์˜ ACID ์†์„ฑ์„ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค! (๋ฉด์ ‘์—์„œ ๋‚˜์˜ค๋Š” ํ•„์ˆ˜ ์งˆ๋ฌธ์ด๋‹ค!!)

  • ์›์ž์„ฑ (Atomicity)
    • ์ž‘์—…์ด ๋ชจ๋‘ ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜, ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ์ „์ฒด๋ฅผ ๋กค๋ฐฑํ•œ๋‹ค.
    • ex) ์†ก๊ธˆ ์‹œ, ๋ˆ์ด A ๊ณ„์ขŒ์—์„œ ๋น ์กŒ์ง€๋งŒ B ๊ณ„์ขŒ๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š์œผ๋ฉด ๋กค๋ฐฑํ•œ๋‹ค.
  • ์ผ๊ด€์„ฑ (Consistency)
    • ์ž‘์—… ์ „ํ›„์˜ ๋ฐ์ดํ„ฐ ์ƒํƒœ๊ฐ€ ํ•ญ์ƒ ์œ ํšจํ•˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค.
  • ๊ฒฉ๋ฆฌ์„ฑ (Isolation)
    • ํŠธ๋žœ์žญ์…˜ ๊ฐ„ ์„œ๋กœ ๊ฐ„์„ญํ•˜์ง€ ์•Š๋„๋ก ๊ฒฉ๋ฆฌ ์ˆ˜์ค€์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์˜ต์…˜์— ๋”ฐ๋ผ์„œ ์„ค์ •์„ ์ด๋ฆฌ์ €๋ฆฌ ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ง€์†์„ฑ (Durability)
    • ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ๋ฐ˜์˜๋œ๋‹ค.

โ–ถ MySQL์„ ๋งŒ๋“ค ๋•Œ ์ด ์›์น™์„ ๋ชจ๋‘ ๊ณ ์ˆ˜ํ•ด์„œ ๋งŒ๋“ค์–ด ๋†จ๋‹ค. ์•ž์œผ๋กœ ์“ฐ๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋Š” ์ด 4๊ฐ€์ง€ ์›์น™์„ ๊ณ ์ˆ˜ํ•ด์„œ ๋งŒ๋“ค์–ด ๋†จ์œผ๋‹ˆ "์ด๋Ÿฐ ์นœ๊ตฌ๊ฐ€ ์žˆ๊ตฌ๋‚˜~~" ์ •๋„๋งŒ ์•Œ๊ณ  ๋„˜์–ด๊ฐ€๋ฉด ๋œ๋‹ค.

 

๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ

์ƒํ’ˆ์„ ๊ตฌ๋งคํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ์˜ ๊ณผ์ •

  1. ์žฌ๊ณ  ๊ฐ์†Œ
  2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
  3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ

โ–ถ ๊ฒฐ๊ตญ ํŠธ๋žœ์žญ์…˜์€ 1, 2, 3๊ณผ ๊ฐ™์€ ๊ฐ๊ฐ์˜ ์ž‘์—…์„ ํ•˜๋‚˜์˜ ์ž‘์—… ๋‹จ์œ„๋กœ ๋ฌถ์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

@Service
@RequiredArgsConstructor
public class OrderService {

    private final StockService stockService;
    private final PaymentService paymentService;
    private final OrderRecordService orderRecordService;
    private final TransactionLogService transactionLogService;

    @Transactional  // @Transactional๋กœ ๋ฌถ์–ด์ค€ ๊ฒƒ์ด ํŠธ๋žœ์žญ์…˜์ด๋‹ค.
    public void processOrder(Long productId, int quantity, BigDecimal amount) {
				    // 1. ์žฌ๊ณ  ๊ฐ์†Œ
            stockService.decreaseStock(productId, quantity);

            // 2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
            paymentService.processPayment(productId, amount);

            // 3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
            orderRecordService.createOrderRecord(productId, quantity);
    }
}

 

 

๊ทธ๋ ‡๋‹ค๋ฉด ํŠธ๋žœ์žญ์…˜์€ ์–ธ์ œ ๋™์ž‘ํ•˜๋Š”๊ฑธ๊นŒ??

๋กค๋ฐฑ๋˜๋Š” ์‹œ์ ์ด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

ํŠธ๋žœ์žญ์…˜์€ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ํ•˜๋‚˜์˜ ์ž‘์—…์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค!
๊ทธ๋ž˜์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์˜ˆ์™ธ๋ฅผ ํ•ธ๋“ค๋งํ•˜๋Š” ๊ฒƒ์ด ํŠธ๋žœ์žญ์…˜์˜ ์ตœ์ข… ๋ชฉํ‘œ์ด๋‹ค.

์ค‘๊ฐ„์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ธฐ์กด์— ์ž‘์—…ํ–ˆ๋˜ ๊ฒƒ์„ ์—†๋˜ ๊ฒƒ์œผ๋กœ ๋˜๋Œ๋ ค์•ผ ํ•œ๋‹ค.

์™œ? ๋กค๋ฐฑ์„ ํ•ด์•ผํ• ๊นŒ??

์šฐ๋ฆฌ๊ฐ€ ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๊ณ  ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋‹จ ์—†๋˜ ๊ฒƒ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค!
โ–ถ ์˜ˆ์ธกํ•˜์ง€ ๋ชปํ–ˆ๋˜ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ทธ ์ƒํ™ฉ์„ ํ•ด๊ฒฐ(์ฒ˜๋ฆฌ)ํ•ด์ค˜์•ผ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋‹จ ์—†๋˜ ๊ฒƒ์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ ๋กค๋ฐฑ์„ ํ•œ๋‹ค! ๊ทธ ์ดํ›„์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ฒŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ค ์˜ˆ์™ธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋™์ž‘ํ• ๊นŒ? ๊ทธ ๊ธฐ์ค€์ด ์žˆ์„๊นŒ?? 

๋ฐ”๋กœ RuntimeException ๋˜๋Š” ๊ทธ ํ•˜์œ„ ํด๋ž˜์Šค (ex. NullPointException, IllegalArgumentException)๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ๋กค๋ฐฑ์ด ๋œ๋‹ค! ํ˜น์€ OutOfMemory(OOM)์ด ๋ฐœ์ƒํ•ด๋„ ๋กค๋ฐฑ์ด ๋œ๋‹ค!
์ด๋ฅผ ์ œ์™ธํ•œ Exception์—์„œ๋Š” ๋กค๋ฐฑ์ด ์ง„ํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋Ÿผ ์–ธ์ œ ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š์„๊นŒ?

RuntimeException์— ํ•ด๋‹นํ•˜์ง€ ์•Š์€ ์˜ˆ์™ธ๋“ค์€ ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.
๋”ฐ๋ผ์„œ SQLException, IOException์—์„œ๋Š” ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค!

 

๋กค๋ฐฑ์ด ๋˜๋Š” ์ƒํ™ฉ

@Transactional
public CreateUserResponse createUserV1(CreateUserRequest request) {
    User user = User.createUser(request);  // ์œ ์ € ์ƒ์„ฑ
    userRepository.save(user);  // ์œ ์ € ์ €์žฅ ์ฟผ๋ฆฌ๋ฌธ๋งŒ ๋งŒ๋“ค์–ด๋†“๊ณ  ์•„์ง ์‹คํ–‰์€ ํ•˜์ง€ ์•Š์•˜๋‹ค. -> ํŠธ๋žœ์žญ์…˜์ด ๋๋‚œ ์ดํ›„์— ์ €์žฅ์ด ์™„๋ฃŒ๋˜๊ธฐ ๋–„๋ฌธ์ด๋‹ค.
    throw new RuntimeException();  // ์˜ˆ์™ธ ๋ฐœ์ƒ -> ๋กค๋ฐฑ -> ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์•„๋ฌด๊ฒƒ๋„ ๋‚จ์ง€ ์•Š๋Š”๋‹ค.
}

๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š๋Š” ์ƒํ™ฉ

@Transactional
public CreateUserResponse createUserV2(CreateUserRequest request) throws IOException {
    User user = User.createUser(request);  // ์œ ์ € ์ƒ์„ฑ
    userRepository.save(user);  // ์œ ์ € ์ €์žฅ ์ฟผ๋ฆฌ๋ฌธ๋งŒ ๋งŒ๋“ค์–ด๋†“๊ณ  ์•„์ง ์‹คํ–‰์€ ํ•˜์ง€ ์•Š์•˜๋‹ค.
    throw new IOException();  // ์˜ˆ์™ธ ๋ฐœ์ƒ -> ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ฐ’์ด ๋‚จ๋Š”๋‹ค.
}

๋ฌผ๋ก  ๋””ํดํŠธ ์„ค์ • ๊ฐ’์ด ์ด๋ ‡๊ฒŒ ์ง„ํ–‰๋œ๋‹ค๋Š” ๊ฒƒ์ด๊ณ , RuntimeException ์ด์™ธ์˜ ์˜ˆ์™ธ๋„ ๋กค๋ฐฑ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

@Transactional ์„ค์ • ๊ฐ’ ์ค‘์—์„œ rollbackFor ๋ผ๋Š” ์„ค์ •์ด ์žˆ๊ณ  ์—ฌ๊ธฐ์— ์˜ˆ์™ธ๋ฅผ ๋ช…์‹œํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
์ƒ์†์„ Throwable(์ตœ์ƒ๋‹จ)์„ ๋ฐ›๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•˜์œ„์— ์žˆ๋Š” ๊ฒƒ๋“ค์€ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
ex) @Transactional(rollbackFor = IOException.class)

@Transactional(rollbackFor = IOException.class)  // ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋„ ๋กค๋ฐฑ์„ ํ•ด์ฃผ์„ธ์š”. ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
public CreateUserResponse createUserV3(CreateUserRequest request) throws IOException {
    User user = User.createUser(request);  // ์œ ์ € ์ƒ์„ฑ
    userRepository.save(user);  // ์œ ์ € ์ €์žฅ ์ฟผ๋ฆฌ๋ฌธ๋งŒ ๋งŒ๋“ค์–ด๋†“๊ณ  ์•„์ง ์‹คํ–‰์€ ํ•˜์ง€ ์•Š์•˜๋‹ค.
    throw new IOException();  // ์˜ˆ์™ธ ๋ฐœ์ƒ -> ๋กค๋ฐฑ -> ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์•„๋ฌด๊ฒƒ๋„ ๋‚จ์ง€ ์•Š๋Š”๋‹ค.
}

๊ทธ๋ ‡๋‹ค๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค๋ฉด ๋˜์ง€ ์•Š์•˜์„๊นŒ? ์™œ ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์—ˆ์„๊นŒ??

โ–ถ ์™œ๋ƒํ•˜๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ RuntimeException์ด ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. default ๊ฐ’์œผ๋กœ ๊ทธ๋ ‡๊ฒŒ ์ •ํ•ด๋†จ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๋Ÿฐ ์„ค์ • ๊ฐ’์ด ์—†์œผ๋ฉด RuntimeException์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋„๋ก ๋งŒ๋“ค์–ด๋†จ๋‹ค.

๋ฌผ๋ก  ๋” ์ƒ์œ„ ๊ฐœ๋…๋„ ๊ฐ€๋Šฅํ•˜๋‹ค!

@Transactional(rollbackFor = Exception.class)
public CreateUserResponse createUserV4(CreateUserRequest request) throws SQLException {
    User user = User.createUser(request);
    userRepository.save(user);
    throw new SQLException();
}

 

๋ฐ˜๋Œ€๋กœ ๋กค๋ฐฑํ•˜์ง€ ์•Š๋Š” ์„ค์ •๋„ ์žˆ์ง€ ์•Š์„๊นŒ??

@Transactional ์•ˆ์˜ ์„ค์ • ๊ฐ’์„ ๋ณด๋ฉด noRollbackFor ๋ผ๋Š” ์„ค์ •๊ฐ’์ด ์žˆ๋‹ค!
์˜ˆ์™ธ๋Š” ๋ฐœ์ƒํ–ˆ์ง€๋งŒ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋Š” ๋กค๋ฐฑํ•˜์ง€ ์•Š๊ฒ ๋‹ค๋Š” ์„ค์ •์ด๋‹ค.

@Transactional(noRollbackFor = RuntimeException.class)
public CreateUserResponse createUserV5(CreateUserRequest request) {
    User user = User.createUser(request);
    userRepository.save(user);
    throw new RuntimeException();
}

 

 

@Transactional ๋‚ด๋ถ€์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ํ•ด์„œ ๋กค๋ฐฑํ•˜๊ณ  ๋‚˜๋ชฐ๋ผ๋ผ ํ•˜๋ฉด ๋๋‚˜๋Š” ๊ฑด๊ฐ€??

๋‹น์—ฐํžˆ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์ด๋Ÿฐ ๋ถ€๋ถ„๊นŒ์ง€ ์ƒ๊ฐํ•˜๋ฉด์„œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.
์ƒ๊ฐํ•˜๊ณ  ์„ค๊ณ„ํ•  ๋•Œ๋Š” "์‚ฌ์šฉ์ž๊ฐ€ ์ด๊ฒƒ์„ ์–ด๋–ค ์‹์œผ๋กœ ์‚ฌ์šฉํ• ๊นŒ?"๋ผ๊ณ  ์„ค๊ณ„ํ•˜๋ฉด์„œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•œ๋‹ค.

@Service
@RequiredArgsConstructor
public class OrderService {

    private final StockService stockService;
    private final PaymentService paymentService;
    private final OrderRecordService orderRecordService;
    private final TransactionLogService transactionLogService;

    @Transactional
    public void processOrder(Long productId, int quantity, BigDecimal amount) {
            // 1. ์žฌ๊ณ  ๊ฐ์†Œ
            stockService.decreaseStock(productId, quantity);

            // 2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
            paymentService.processPayment(productId, amount);

            // 3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
            orderRecordService.createOrderRecord(productId, quantity);

    }
}


try - catch ๋ฌธ !!! ์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
๋ฌผ๋ก  ๋ชจ๋“  ๋ถ€๋ถ„์— try-catch๋กœ ๊ฐ์‹ธ๋ฉด ์ฝ”๋“œ๊ฐ€ ๋”๋Ÿฌ์›Œ์ง€๊ฒ ์ง€๋งŒ... ์ž์ฃผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋ถ„์—์„œ๋Š”
๋กœ๊ทธ๋“  ์–ด๋– ํ•œ ์ถ”๊ฐ€ ์ž‘์—…์ด๋“  ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

@Service
@RequiredArgsConstructor
public class OrderService {

    private final StockService stockService;
    private final PaymentService paymentService;
    private final OrderRecordService orderRecordService;
    private final TransactionLogService transactionLogService;

    @Transactional
    public void processOrder(Long productId, int quantity, BigDecimal amount) {
        try {
        // 1. ์žฌ๊ณ  ๊ฐ์†Œ
        stockService.decreaseStock(productId, quantity);

        // 2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
        paymentService.processPayment(productId, amount); -> ๋ˆ ์—†์–ด์š”...

        // 3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
        orderRecordService.createOrderRecord(productId, quantity);
        } catch ( Exception e ) {

           log.info("๊ฒฐ์ œ ๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ");
           ํ˜น์€ ์–ด๋–ค ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๋””๋น„์— ์ €์žฅํ•œ๋‹ค. 
           // ํ›„์† ์ž‘์—… ์‹คํ–‰
        }
    }
}

์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์‰ฝ๊ฒŒ ๋๋‚œ๋‹ค. ํ•˜์ง€๋งŒ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๊ฐ€ ๊ณผ์—ฐ ์ •์ƒ์ ์ธ ์ฝ”๋“œ์ผ๊นŒ..

โ–ถ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ƒ์„ ํ–ˆ๊ณ  ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋กค๋ฐฑ์ด ์ง„ํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. (์ด๊ฒƒ์ด ๊ฐ€์žฅ ๋งŽ์ด ํ•˜๋Š” ์‹ค์ˆ˜ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.)

@Transactional ์นœ๊ตฌ๋Š” ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๊ทธ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ๊ธฐ ์œ„ํ•œ ์นœ๊ตฌ์ด๋‹ค!

@Service
@RequiredArgsConstructor
public class OrderService {

    private final StockService stockService;
    private final PaymentService paymentService;
    private final OrderRecordService orderRecordService;
    private final TransactionLogService transactionLogService;

    @Transactional
    public void processOrder(Long productId, int quantity, BigDecimal amount) {
       try {
        // 1. ์žฌ๊ณ  ๊ฐ์†Œ
        stockService.decreaseStock(productId, quantity);

        // 2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
        paymentService.processPayment(productId, amount);

        // 3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
        orderRecordService.createOrderRecord(productId, quantity);
       } catch ( Exception e ) {

           log.info("๊ฒฐ์ œ ๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ")
           throw e;  // -> ์ด๊ฒƒ!!!! ์˜ˆ์™ธ๋ฅผ ๋˜์ €์•ผ์ง€๋งŒ ๋กค๋ฐฑ์ด ๋œ๋‹ค!!!
       }
    }
}

์ž‘์„ฑํ•œ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋˜์ ธ์ฃผ๋ฉด ๋กค๋ฐฑ์ด ์ง„ํ–‰๋œ๋‹ค!

 

๋งŒ์•ฝ ํŠธ๋žœ์žญ์…˜์ด ํ•œ ๊ฐœ๊ฐ€ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž‘์—… ๋ฌถ์Œ์ด ๋ถ™์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ??

์ƒํ’ˆ์„ ๊ตฌ๋งคํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ์˜ ๊ณผ์ •

  1. ์žฌ๊ณ  ๊ฐ์†Œ
  2. ๊ธˆ์•ก ์ฐจ๊ฐ
    1. ๋งˆ์ผ๋ฆฌ์ง€
    2. ์ฟ ํฐ
    3. ํ˜„๊ธˆ
  3. ์ฃผ๋ฌธ ๊ธฐ๋ก

 

ํŠธ๋žœ์žญ์…˜์˜ ์ „ํŒŒ

ํŠธ๋žœ์žญ์…˜์€ ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ต์…˜์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

์ „ํŒŒ ์˜ต์…˜ ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜ ์กด์žฌ ์‹œ ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜ ์—†์„ ์‹œ
REQUIRED ์ฐธ์—ฌ ์ƒˆ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ
REQUIRES_NEW ์ƒˆ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ ์ƒˆ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ
SUPPORTS ์ฐธ์—ฌ ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰
NOT_SUPPORTED ํŠธ๋žœ์žญ์…˜ ์ค‘๋‹จ ํ›„ ์‹คํ–‰ ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰
MANDATORY ์ฐธ์—ฌ ์˜ˆ์™ธ ๋ฐœ์ƒ
NEVER ์˜ˆ์™ธ ๋ฐœ์ƒ ํŠธ๋žœ์žญ์…˜ ์—†์ด ์‹คํ–‰
NESTED ์ค‘์ฒฉ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ ์ƒˆ ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ

99%๋Š” REQUIRED๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
์ง„์งœ ์จ์•ผํ•œ๋‹ค ํ•ด๋„ REQUIRES_NEW๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
์ „๋ถ€ ์™ธ์šธ ํ•„์š”๋Š” ์—†๊ณ  ํ•„์š”ํ•  ๊ฒฝ์šฐ ๊ฒ€์ƒ‰ํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

OutService

@Transactional(propagation = Propagation.REQUIRED)
public void outerRequired() {
    logTransaction("outerRequired");
    innerService.innerRequired();  // REQUIRED
    innerService.innerRequiresNew();  // REQUIRES_NEW
    innerService.innerSupports();  // SUPPORTS
    innerService.innerNotSupported();  // NOT_SUPPORTED
    innerService.innerMandatory();  // MANDATORY
    innerService.innerNever();  // NEVER
    innerService.innerNested();  // NESTED
}

InnerService

@Transactional(propagation = Propagation.REQUIRED)
public void innerRequired() {
    logTransaction("innerRequired");
}

@Transactional(propagation = Propagation.REQUIRES_NEW) // ์ด ์นœ๊ตฌ๋„ ๊ฑฐ์˜ ์“ธ ์ผ์ด ์—†๋‹ค.
public void innerRequiresNew() {
    logTransaction("innerRequiresNew");
}

@Transactional(propagation = Propagation.SUPPORTS)
public void innerSupports() {
    logTransaction("innerSupports");
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerNotSupported() {
    logTransaction("innerNotSupported");
}

@Transactional(propagation = Propagation.MANDATORY)
public void innerMandatory() {
    logTransaction("innerMandatory");
}

@Transactional(propagation = Propagation.NEVER)
public void innerNever() {
    logTransaction("innerNever");
}

@Transactional(propagation = Propagation.NESTED)
public void innerNested() {
    logTransaction("innerNested");
}

 

@Transactional annotation์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์‹คํ–‰ ๊ฒฐ๊ณผ

[outerRequired] ํŠธ๋žœ์žญ์…˜ ํ™œ์„ฑํ™”๋จ. ์ด๋ฆ„: org.example.jwtfilter.user.service.OuterService.outerRequired
[innerRequired] ํŠธ๋žœ์žญ์…˜ ํ™œ์„ฑํ™”๋จ. ์ด๋ฆ„: org.example.jwtfilter.user.service.OuterService.outerRequired
[innerRequiresNew] ํŠธ๋žœ์žญ์…˜ ํ™œ์„ฑํ™”๋จ. ์ด๋ฆ„: org.example.jwtfilter.user.service.InnerService.innerRequiresNew
[innerSupports] ํŠธ๋žœ์žญ์…˜ ํ™œ์„ฑํ™”๋จ. ์ด๋ฆ„: org.example.jwtfilter.user.service.OuterService.outerRequired
[innerNotSupported] ํŠธ๋žœ์žญ์…˜ ์—†์Œ.
[innerMandatory] ํŠธ๋žœ์žญ์…˜ ํ™œ์„ฑํ™”๋จ. ์ด๋ฆ„: org.exmaple.hwtfilter.user.service.OuterService.outerRequired

@Transactional annotation์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹คํ–‰ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์—†์„ ๊ฒฝ์šฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์–ด๋–ค ๊ฒฝ์šฐ์— ๊ฐ๊ฐ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ??

์šฐ๋ฆฌ๊ฐ€ ์ƒํ’ˆ์„ ๊ตฌ๋งคํ•˜๋Š” ๋กœ์ง์„ ๋‹ค์‹œ ์ƒ๊ฐํ•ด๋ณผ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

  1. ์žฌ๊ณ  ๊ฐ์†Œ
  2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
  3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
  4. ์„ฑ๊ณต ์‹คํŒจ ์—ฌ๋ถ€ ๊ธฐ๋ก
@Service
@RequiredArgsConstructor
public class OrderService {

    private final StockService stockService;
    private final PaymentService paymentService;
    private final OrderRecordService orderRecordService;
    private final TransactionLogService transactionLogService;

    @Transactional
    public void processOrder(Long productId, int quantity, BigDecimal amount) {
        // 1. ์žฌ๊ณ  ๊ฐ์†Œ
        stockService.decreaseStock(productId, quantity);

        // 2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
        paymentService.processPayment(productId, amount);

        // 3. ์ฃผ๋ฌธ ๊ธฐ๋ก ์ƒ์„ฑ
        orderRecordService.createOrderRecord(productId, quantity);

        // 4. ์„ฑ๊ณต ์—ฌ๋ถ€ ๊ธฐ๋ก 
        transactionLogService.logTransactionResult(productId, true, "Order successful");
    }
}

์„ฑ๊ณต ์—ฌ๋ถ€ ๊ธฐ๋ก ๊ณผ์ •์—์„œ ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ ์ „์ฒด๋ฅผ ๋กค๋ฐฑํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์‹œ์Šคํ…œ ์ƒ ๋ฌธ์ œ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋กค๋ฐฑํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
REQUIRED_NEW๋กœ ๋”ฐ๋กœ ๋ฌถ์–ด์„œ ์ด ์นœ๊ตฌ๋งŒ ๋กค๋ฐฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ์‚ฌ์šฉ์ด ๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ํ”ํ•˜์ง€ ์•Š๋‹ค.

 

+ ํŠธ๋žœ์žญ์…˜ ์นœ๊ตฌ๋Š” ์–ด๋–ป๊ฒŒ ์ด๋ ‡๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฑธ๊นŒ??

๋”๋ณด๊ธฐ

ํŠธ๋žœ์žญ์…˜์„ ์•Œ๊ธฐ ์œ„ํ•ด์„œ๋Š” AOP๋ผ๋Š” ๊ฐœ๋…์„ ์•Œ์•„์•ผ ํ•œ๋‹ค.

 

AOP๋Š” ์‰ฝ๊ฒŒ ๋งํ•ด์„œ ์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ์—์„œ ์›ํ•˜๋Š” ์‹œ์ ์—์„œ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

๋งŒ์•ฝ @Transactional์ด ์—†๋‹ค๋ฉด?

public CreateUserResponse createUserV4(CreateUserRequest request) throws SQLException {
    
    try {
		    User user = User.createUser(request);
        userRepository.save(user);
	  } catch (Exception e) {
	      rollback();
	  }
}

์ด๋Ÿฐ ์‹์œผ๋กœ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด ๋„ˆ๋ฌด ๊ท€์ฐฎ๊ธฐ ๋•Œ๋ฌธ์—

@Transactional(rollbackFor = Exception.class)
public CreateUserResponse createUserV4(CreateUserRequest request) throws SQLException {
    User user = User.createUser(request);
    userRepository.save(user);
    throw new SQLException();
}

์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ๊ฒƒ์ด๋‹ค!

๊ฒฐ๋ก ์ ์œผ๋กœ @Transactional์€ Spring์˜ AOP๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๋‹ค!

Proxy๋Š” ํด๋ž˜์Šค ๋‚ด์—์„œ 1๊ฐœ๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

annotation(REQUIRED)์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ํ˜ธ์ถœํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ annotation(REQUIRES_NEW)์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ ธ์•ผ ํ•  ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋˜์ง€๋งŒ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•ด๋ณด๋ฉด ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ๋‹ค.?
๊ฐ€์žฅ ์ƒ์œ„์˜ ํŠธ๋žœ์žญ์…˜์„ ๋”ฐ๋ผ๊ฐ„๋‹ค.

@Transactional(propagation = Propagation.REQUIRED)
public void innerRequired2() {
    logTransaction("innerRequired");
    innerRequiresNew();
    innerSupports();
    innerNotSupported();
    innerMandatory();
    innerNever();
}

AOP ๋™์ž‘์„ ํ•ด์„œ ์‹ค์ œ๋กœ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— Proxy ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด์„œ ์–ด๋–ค ๊ฒƒ์„ ์‹คํ–‰์‹œํ‚ฌ ๊ฒƒ์ธ์ง€ ๋ฏธ๋ฆฌ ์ •์˜ํ•œ๋‹ค.
๊ทธ๋Ÿผ ๊ฐ๊ฐ์˜ ํด๋ž˜์Šค์—์„œ๋Š” Proxy ๊ฐ์ฒด๋ฅผ 1๊ฐœ๋งŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
Proxy ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด์„œ @Transactional(propagation = Propagation.REQUIRED) ์ด๊ฑธ ์“ฐ๊ฒ ๋‹ค๊ณ  ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์ด ์™€๋„ ์ ์šฉ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.

 

OutService

// Proxy ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  @Transactional(propagation = Propagation.REQUIRED)๋ฅผ ์„ ์–ธํ•œ๋‹ค.
// Proxy ๊ฐ์ฒด ๋‚ด๋ถ€์— 
// innerService.innerRequired();
// innerService.innerRequiresNew();
// ๊ฐ๊ฐ ๋“ค์–ด๊ฐ„๋‹ค.
@Transactional(propagation = Propagation.REQUIRED)
public void outerRequired() {
    logTransaction("outerRequired");
    innerService.innerRequired();  // REQUIRED
    innerService.innerRequiresNew();  // REQUIRES_NEW
    innerService.innerSupports();  // SUPPORTS
    innerService.innerNotSupported();  // NOT_SUPPORTED
    innerService.innerMandatory();  // MANDATORY
    //innerService.innerNever();  // NEVER
    //innerService.innerNested();  // NESTED
}
@GetMapping("/get")
public String getUserInfo(HttpServletRequest request) {
    outerService.outerRequired();  // @Transactional(propagation = Propagation.REQUIRED)
    outerService.temp();  // @Transactional(propagation = Propagation.REQUIRES_NEW)
    return "์ผ๋ฐ˜ ํŽ˜์ด์ง€ ๋ฆฌ์†Œ์Šค๊ฐ€ ํ—ˆ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";
}

์ด๋Ÿฐ ์‹์œผ๋กœ ๋‹ค๋ฅธ ํด๋ž˜์Šค์—์„œ ๊ฐ„์ ‘ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด ๊ฐ๊ฐ์˜ Proxy๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.