@Transactional (트랜잭션)


1. 트랜잭션이란?

여러 DB 작업을 하나의 단위로 묶는 것.
중간에 실패하면 전부 롤백, 전부 성공하면 커밋.

게시글 작성 시나리오:
1. community 테이블에 INSERT   ← 성공
2. files 테이블에 INSERT       ← 실패!
→ 트랜잭션 없으면: community만 저장되고 파일은 누락 (데이터 불일치)
→ 트랜잭션 있으면: community INSERT도 롤백 → 깔끔

2. 기본 사용법

@Service
@Transactional(readOnly = true)  // 클래스 기본값: 읽기 전용
public class CommunityService {

    // 읽기 전용 (readOnly = true 상속)
    public Page<CommunityDTO> getCommunityList(...) { ... }

    @Transactional  // 쓰기 필요 → readOnly = false로 오버라이드
    public Long createCommunity(CommunityCreateDTO dto) { ... }

    @Transactional  // 쓰기 필요
    public void updateCommunity(Long id, CommunityUpdateDTO dto) { ... }

    @Transactional  // 쓰기 필요
    public void deleteCommunity(Long id) { ... }
}

readOnly = true 쓰는 이유


3. 트랜잭션 전파 (Propagation)

트랜잭션 안에서 다른 트랜잭션을 호출할 때 어떻게 동작할지 결정.

// 기본값: REQUIRED
@Transactional(propagation = Propagation.REQUIRED)

전파는 빼자..
기본적으로 service1에서 service2 호출.
service2에서 에러나도 service1,2 둘다 롤백 어쨋든 @Trasactional 있는 다른 service호출할 때는 주의 알림메일 발송 기록 (메일 실패 상관없이 실패했다는 내역은 DB에 남기기) 등.

옵션 설명
REQUIRED (기본값) 기존 트랜잭션 있으면 참여, 없으면 새로 생성
REQUIRES_NEW 항상 새 트랜잭션 생성 (기존 트랜잭션 일시 중단)
SUPPORTS 트랜잭션 있으면 참여, 없으면 없이 실행
NEVER 트랜잭션 있으면 예외 발생
// 실무 예시: 로그 저장은 실패해도 메인 로직에 영향 없게
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String action) {
    // 로그 저장 실패해도 외부 트랜잭션은 그대로 진행
}

4. 주의사항 - 같은 클래스 내부 호출

@Service
public class CommunityService {

    @Transactional
    public void createCommunity(...) {
        // ... 게시글 저장
        sendNotification(); // ❌ 트랜잭션 적용 안 됨!
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification() {
        // 같은 클래스 내부 호출은 프록시를 거치지 않아서
        // @Transactional이 무시됨
    }
}

해결: 별도 Service 클래스로 분리 후 주입받아 호출.


5. 롤백 조건

기본적으로 RuntimeExceptionError에서만 롤백.

// Checked Exception은 기본적으로 롤백 안 됨
@Transactional
public void process() throws IOException {
    // IOException 발생해도 롤백 안 됨 (기본값)
}

// rollbackFor로 명시적 지정
@Transactional(rollbackFor = Exception.class)
public void process() throws IOException {
    // 모든 예외에서 롤백
}

6. 이 프로젝트에서의 패턴

@Service
@Transactional(readOnly = true)  // ← 기본: 조회는 readOnly
public class CommunityService {

    // 조회 → readOnly 상속
    public Page<CommunityDTO> getCommunityList(...) { ... }
    public CommunityDTO getCommunityDetail(Long id) { ... }

    // 변경 → @Transactional 오버라이드
    @Transactional
    public Long createCommunity(...)  { ... }

    @Transactional
    public void updateCommunity(...) { ... }

    @Transactional
    public void deleteCommunity(...) { ... }
}