@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 ๊ธฐ๋ณธ ๊ฒฉ๋ฆฌ ์์ค)์ผ๋ก ์ถฉ๋ถํ๋ค. ์ค์ํ ๊ฑด "๊ธฐ๋ณธ๊ฐ์ด ๋ฌด์์ด๊ณ ์ธ์ ๋ฒ์ด๋์ผ ํ๋์ง"๋ฅผ ์๋ ๊ฒ์ด๋ค. ํธ๋์ญ์ ์ ์ ๋์๊ฐ ๋ ๋ณด์ด์ง ์๋ค๊ฐ, ์ด๊ธ๋๋ ์๊ฐ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ๋ฌด๋๋จ๋ฆฐ๋ค. ์๋ฆฌ๋ฅผ ์์์ผ ๊ทธ ์๊ฐ์ ๋ง์ ์ ์๋ค.
์ฐธ๊ณ ์ถ์ฒ
- Spring Framework Docs — Transaction Management — https://docs.spring.io/spring-framework/reference/data-access/transaction.html
- Spring Framework Docs — Declarative transaction (@Transactional) — https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative.html
- PostgreSQL Docs — Transaction Isolation — https://www.postgresql.org/docs/current/transaction-iso.html
- MySQL Docs — InnoDB Transaction Isolation Levels — https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
'๐ Tech Stack > Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ํ์ ์์ ์์ฃผ ์ฐ๋ ๋ฐฑ์๋ ์คํฌ๋ค (with JAVA) (3) | 2026.07.10 |
|---|---|
| ๋ฐฐ์น ์ฒ๋ฆฌ vs ์คํธ๋ฆผ ์ฒ๋ฆฌ (1) | 2026.06.18 |
| HTTP ์ํ ์ฝ๋(Status Code) ์ ๋ฆฌ (1) | 2026.06.15 |
| ์น ์ธ์ฆ ๋ฐฉ์ ๋น๊ต (์ฟ ํค, ์ธ์ , ํ ํฐ) (1) | 2026.06.09 |
| JPA N+1 ์ฌํ๋ถํฐ Fetch Join, Batch Size๊น์ง (0) | 2026.06.03 |