ํธ๋์ญ์ ์ด๋ ๊ณผ์ฐ ๋ฌด์์ผ๊น??
ํ๋์ ์์
๋จ์๋ฅผ ๋
ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ด์ฃผ๋ ์ญํ ์ ํด์ค๋ค!
์ด๋ฅผ ํตํด์ ๋ฐ์ดํฐ ๋ฒ ์ด์ค์ ACID ์์ฑ์ ์งํฌ ์ ์๋๋ก ํ๋ค! (๋ฉด์ ์์ ๋์ค๋ ํ์ ์ง๋ฌธ์ด๋ค!!)
- ์์์ฑ (Atomicity)
- ์์ ์ด ๋ชจ๋ ์ฑ๊ณตํ๊ฑฐ๋, ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ์ฒด๋ฅผ ๋กค๋ฐฑํ๋ค.
- ex) ์ก๊ธ ์, ๋์ด A ๊ณ์ข์์ ๋น ์ก์ง๋ง B ๊ณ์ข๋ก ๋ค์ด๊ฐ์ง ์์ผ๋ฉด ๋กค๋ฐฑํ๋ค.
- ์ผ๊ด์ฑ (Consistency)
- ์์ ์ ํ์ ๋ฐ์ดํฐ ์ํ๊ฐ ํญ์ ์ ํจํ๋๋ก ๋ณด์ฅํ๋ค.
- ๊ฒฉ๋ฆฌ์ฑ (Isolation)
- ํธ๋์ญ์ ๊ฐ ์๋ก ๊ฐ์ญํ์ง ์๋๋ก ๊ฒฉ๋ฆฌ ์์ค์ ์ค์ ํ ์ ์๋ค.
- ์ต์ ์ ๋ฐ๋ผ์ ์ค์ ์ ์ด๋ฆฌ์ ๋ฆฌ ์ ํ ์ ์๋ค.
- ์ง์์ฑ (Durability)
- ํธ๋์ญ์ ์ด ์ฑ๊ณตํ๋ฉด ๊ฒฐ๊ณผ๊ฐ ์๊ตฌ์ ์ผ๋ก ๋ฐ์๋๋ค.
โถ MySQL์ ๋ง๋ค ๋ ์ด ์์น์ ๋ชจ๋ ๊ณ ์ํด์ ๋ง๋ค์ด ๋จ๋ค. ์์ผ๋ก ์ฐ๋ ๋ชจ๋ ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ ์ด 4๊ฐ์ง ์์น์ ๊ณ ์ํด์ ๋ง๋ค์ด ๋จ์ผ๋ "์ด๋ฐ ์น๊ตฌ๊ฐ ์๊ตฌ๋~~" ์ ๋๋ง ์๊ณ ๋์ด๊ฐ๋ฉด ๋๋ค.
๊ตฌ์ฒด์ ์ธ ์์
์ํ์ ๊ตฌ๋งคํ๋ค๊ณ ๊ฐ์ ํ์ ๋์ ๊ณผ์
- ์ฌ๊ณ ๊ฐ์
- ๊ฒฐ์ ์ฒ๋ฆฌ
- ์ฃผ๋ฌธ ๊ธฐ๋ก ์์ฑ
โถ ๊ฒฐ๊ตญ ํธ๋์ญ์ ์ 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; // -> ์ด๊ฒ!!!! ์์ธ๋ฅผ ๋์ ์ผ์ง๋ง ๋กค๋ฐฑ์ด ๋๋ค!!!
}
}
}
์์ฑํ ์ฝ๋์ ๊ฐ์ด ์์ธ๋ฅผ ๋ค์ ๋์ ธ์ฃผ๋ฉด ๋กค๋ฐฑ์ด ์งํ๋๋ค!
๋ง์ฝ ํธ๋์ญ์ ์ด ํ ๊ฐ๊ฐ ์๋๋ผ ์ฌ๋ฌ ๊ฐ์ ์์ ๋ฌถ์์ด ๋ถ์ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น??
์ํ์ ๊ตฌ๋งคํ๋ค๊ณ ๊ฐ์ ํ์ ๋์ ๊ณผ์
- ์ฌ๊ณ ๊ฐ์
- ๊ธ์ก ์ฐจ๊ฐ
- ๋ง์ผ๋ฆฌ์ง
- ์ฟ ํฐ
- ํ๊ธ
- ์ฃผ๋ฌธ ๊ธฐ๋ก
ํธ๋์ญ์ ์ ์ ํ
ํธ๋์ญ์ ์ ์๋์ ๊ฐ์ ์ต์ ์ ๊ฐ์ง๊ณ ์๋ค.
์ ํ ์ต์ | ํ์ฌ ํธ๋์ญ์ ์กด์ฌ ์ | ํ์ฌ ํธ๋์ญ์ ์์ ์ |
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์ ์ฃผ์ ์ฒ๋ฆฌํ๊ณ ์คํํ๋ฉด ํธ๋์ญ์ ์ด ์์ ๊ฒฝ์ฐ๋ฅผ ํ์ธํ ์ ์๋ค.
์ด๋ค ๊ฒฝ์ฐ์ ๊ฐ๊ฐ ์ฌ์ฉํด์ผ ํ ๊น??
์ฐ๋ฆฌ๊ฐ ์ํ์ ๊ตฌ๋งคํ๋ ๋ก์ง์ ๋ค์ ์๊ฐํด๋ณผ ํ์๊ฐ ์๋ค.
- ์ฌ๊ณ ๊ฐ์
- ๊ฒฐ์ ์ฒ๋ฆฌ
- ์ฃผ๋ฌธ ๊ธฐ๋ก ์์ฑ
- ์ฑ๊ณต ์คํจ ์ฌ๋ถ ๊ธฐ๋ก
@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๋ฅผ ์์ฑํ ์ ์๋ค.