전부 성공하거나 전부 취소
public void transfer(int from, int to, int amount) {
accountMapper.withdraw(from, amount); // ① 출금 성공
// 여기서 시스템 다운!
accountMapper.deposit(to, amount); // ② 입금 못 됨
}
출금만 되고 입금 안 됨 = 돈이 사라짐. 시스템 사고.
여러 DB 작업을 「나눌 수 없는 한 단위」 로 묶는 약속. 하나라도 실패하면 모두 롤백.
@Service
public class TransferService {
@Autowired private AccountMapper mapper;
@Transactional
public void transfer(int from, int to, int amount) {
mapper.withdraw(from, amount);
mapper.deposit(to, amount);
mapper.recordHistory(from, to, amount);
}
}
👉 메서드 시작 = 트랜잭션 시작 / 메서드 정상 종료 = 커밋 / 예외 발생 = 자동 롤백.
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
👉 위 두 줄로 @Transactional 활성화. tx:annotation-driven 누락 시 — 어노테이션 무시됨.
| 속성 | 의미 |
|---|---|
| Atomicity (원자성) | 전부 성공 또는 전부 취소 |
| Consistency (일관성) | 제약 조건(NOT NULL 등) 항상 만족 |
| Isolation (고립성) | 다른 트랜잭션과 격리 |
| Durability (지속성) | 커밋 후 영구 보존 (시스템 다운돼도) |
👉 본 과정은 A 만 깊이. C·I·D 는 후속 과정.
@Transactional 의 기본 롤백 정책:
@Transactional(rollbackFor = Exception.class) // 모든 예외에서 롤백
public void method() throws IOException { ... }
👉 본 과정은 RuntimeException 만 던지므로 기본 동작으로 충분.
@Transactional 은 일반적으로 Service 메서드에 붙임:
| 메서드 | 이유 |
|---|---|
| 회원가입 | 회원 INSERT 후 권한 INSERT 등 (확장 시) |
| 게시글 작성 | 글 INSERT + 첨부파일 저장 |
| 조회수 + 통계 | 여러 테이블 동시 갱신 |
👉 단일 SELECT 같은 단순 메서드엔 굳이 안 붙여도 됨 (성능 미세 차이만).
@Service
public class MemberService {
public void register(Member m) {
save(m); // ⚠️ @Transactional 적용 안 됨!
}
@Transactional
public void save(Member m) { ... }
}
Spring 의 AOP 프록시 메커니즘 때문. 우회 — 다른 빈에서 호출 또는 클래스 분리.
@Transactional
public void transferBuggy(int from, int to, int amount) {
mapper.withdraw(from, amount); // ① 출금
if (amount > 1000) {
throw new RuntimeException("금액 너무 큼"); // ② 강제 예외
}
mapper.deposit(to, amount);
}
// 호출
service.transferBuggy(1, 2, 5000);
// 결과 — DB 에서 확인
SELECT * FROM account WHERE id = 1;
// 잔액 그대로 (롤백됨)
JDBC 트랜잭션 활성 시 콘솔:
==> Preparing: UPDATE account SET balance = ... WHERE id = ?
==> Parameters: 5000(Integer), 1(Integer)
<== Updates: 1
(예외 발생)
(자동 롤백 — 위 UPDATE 가 취소됨)
@Transactional(readOnly = true)
public List<Board> findAll() {
return mapper.findAll();
}
👉 단순 SELECT 만 하는 메서드에 — DB 성능 미세 향상.
트랜잭션 단어를 들어본 적은 있지만 코드에 어떻게 적용하는지 막연.
@Transactional 한 줄의 효과를 안다. Service 메서드에 적절히 붙일 수 있다.