문제점 코드 (Before)
@Transactional // ← 트랜잭션 시작
public int updateStocksFromMst(MultipartFile file) {
List<StockMstDto> mstList = parseMstFile(file);
int updatedCount = 0;
for (StockMstDto mst : mstList) {
try {
// ... DB 작업 (INSERT/UPDATE)
stockMapper.insertStock(newStock); // ← 여기서 SQL 에러!
updatedCount++;
} catch (Exception e) {
log.error("Failed...", e); // ← 예외를 잡아서 삼킴
}
}
return updatedCount; // ← 메서드는 정상 종료처럼 보임
}
핵심 문제:
1. 예외를 catch해서 삼켜버림
catch (Exception e) {
log.error(...); // 로그만 찍고 끝
}
// 예외가 메서드 밖으로 나가지 않음!
2. Spring @Transactional의 롤백 조건
- Spring은 unchecked exception(RuntimeException)이 메서드 밖으로 throw되어야 롤백합니다
- 예외를 catch하면 Spring은 "정상 종료"로 판단합니다
- 하지만 커밋하지도 않고 트랜잭션이 hanging 상태로 남습니다
3. 좀비 트랜잭션 생성
시작: @Transactional 메서드 진입 → DB 커넥션 획득, 트랜잭션 시작 중간: INSERT 실패 → Exception 발생 → catch로 잡힘 → Spring은 예외를 못 봄 끝: return updatedCount → 메서드는 "정상 종료" 결과: 커밋도 롤백도 안됨 → 트랜잭션 hanging → DB 락 유지 → HikariCP 커넥션 반환 안됨
4. 실제 발생한 상황
13:48:38 - MST 업로드 시도 → stock_id AUTO_INCREMENT 누락으로 INSERT 실패 → 예외를 catch로 삼킴 → 트랜잭션이 10분 이상 RUNNING 상태로 남음 → users 테이블 락 유지 (왜? 같은 커넥션 세션 내에서 여러 테이블 접근) 14:03:14 - 로그인 시도 → UPDATE users SET last_login_at... 실행 → 기존 트랜잭션(ID 1051)이 락을 잡고 있어서 블로킹 → 30초 타임아웃 발생
수정된 코드 (After)
@Transactional // ← 트랜잭션 시작
public int updateStocksFromMst(MultipartFile file) {
List<StockMstDto> mstList = parseMstFile(file);
int updatedCount = 0;
// try-catch 제거! ← 핵심 변경
for (StockMstDto mst : mstList) {
Stock existingStock = stockMapper.selectStockByCode(mst.getStockCode());
if (existingStock != null) {
stockMapper.updateStockBasicInfo(updateStock);
} else {
stockMapper.insertStock(newStock); // ← 에러 발생 시 즉시 throw
}
updatedCount++;
}
return updatedCount; // ← 정상 종료 시에만 도달
}
수정 핵심:
1. try-catch 완전 제거
// Before: catch로 예외 삼킴
try {
stockMapper.insertStock(newStock);
} catch (Exception e) {
log.error(...); // ← 예외 소멸
}
// After: 예외를 자연스럽게 propagate
stockMapper.insertStock(newStock); // ← 에러 시 즉시 throw
2. Spring의 자동 롤백 활성화
시나리오 1 (성공): → 모든 INSERT/UPDATE 성공 → 메서드 정상 종료 → Spring이 COMMIT 실행 → 커넥션 반환
시나리오 2 (실패): → INSERT에서 SQLException 발생 → unchecked exception이 메서드 밖으로 throw → Spring이 예외 감지 → 자동으로 ROLLBACK 실행 ← 핵심! → 커넥션 반환 → 트랜잭션 정상 종료
3. All-or-Nothing 보장
- 100개 종목 중 99번째에서 에러 → 전체 롤백
- 데이터 일관성 보장
- 부분 성공으로 인한 데이터 불일치 방지
4. 추가 개선사항
- 수천 개 종목 업로드 시 로그 과다 방지
- 디버그 시에만 상세 로그 활성화
동작 비교
Before (문제 코드)
- 트랜잭션 시작
- INSERT 실패 → Exception
- catch로 잡음 → 예외 삼킴
- 계속 진행 (다음 종목)
- return updatedCount
- Spring: "정상 종료네? 근데 커밋할 게 없네?"
- 트랜잭션 hanging → 락 유지 → 타임아웃 유발
After (수정 코드)
- 트랜잭션 시작
- INSERT 실패 → Exception
- 예외가 메서드 밖으로 throw
- Spring: "예외 감지! ROLLBACK!"
- 트랜잭션 즉시 종료 → 락 해제 → 커넥션 반환
- 컨트롤러로 예외 전파 → 500 에러 응답
Spring @Transactional의 롤백 규칙
@Transactional
public void method() {
// Case 1: RuntimeException → 자동 롤백 ✅
throw new RuntimeException();
// Case 2: Checked Exception → 커밋됨 ⚠️
throw new IOException(); // 롤백 안됨!
// Case 3: 예외 catch → 커밋됨 ⚠️
try {
throw new RuntimeException();
} catch (Exception e) {
// 삼켜버림 → Spring은 정상 종료로 판단
}
}
우리 코드는 Case 3에 해당했고, 이를 Case 1로 변경했습니다.
'Archive(완료된 내용) > 포트폴리오 강화' 카테고리의 다른 글
| [stock101] dart연동 및 ksi 연동 그리고 리팩토링 -4일차 (1) | 2026.01.22 |
|---|---|
| [stock101] KIS 코스피 코스닥 종목 정보 매일 최신화하기 - 3일차 (1) | 2026.01.21 |
| [stock101] KIS API연동 웹소켓 - 2일차 (1) | 2026.01.20 |
| [stock101] pdf파일 업로드 및 AI 셋팅 - 1일차 (0) | 2026.01.19 |
| [임베딩] (0) | 2026.01.17 |