개발인생/Backend

[Spring] @Transactional

forri 2025. 3. 19. 17:17

@Transactional이란?

@Transactional은 Spring에서 트랜잭션(Transaction)을 관리하는 어노테이션
- 데이터베이스 작업(Insert, Update, Delete 등)을 하나의 단위로 묶어서 처리
- 오류가 발생하면 자동으로 롤백(rollback)하여 데이터의 일관성을 유지
- 트랜잭션이 끝날 때 자동으로 commit 또는 rollback 수행

 


효과

1️⃣ 트랜잭션 시작 → 성공하면 Commit, 실패하면 Rollback

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void updateUser(Long id, String newName) { //메서드 실행시 트랜잭션 시작
        User user = userRepository.findById(id).orElseThrow();
        user.setName(newName);
        // ✅ 변경 사항 자동 반영 (JPA가 자동으로 flush)
        // 오류없이 끝나면 자동으로 commit, 예외발생하면 rollback
    }
}

2️⃣ 예외 발생 시 자동으로 rollback (실패 시 데이터 변경 없음)

@Transactional
public void updateUsers(Long id1, Long id2, String newName) {
    User user1 = userRepository.findById(id1).orElseThrow();
    user1.setName(newName);

    if (true) throw new RuntimeException("예외 발생!"); // 강제 오류

    User user2 = userRepository.findById(id2).orElseThrow();
    user2.setName(newName);
}
  • user1의 이름이 변경되었지만 중간에 예외 발생
  • 트랜잭션이 롤백되면서 user1의 변경도 취소됨
  • 예외가 발생하면 데이터가 원래 상태로 복구됨

3️⃣ @Transactional을 안 쓰면 데이터가 중간에 변경될 수도 있음

public void updateUsersWithoutTransaction(Long id1, Long id2, String newName) {
    User user1 = userRepository.findById(id1).orElseThrow();
    user1.setName(newName);
    userRepository.save(user1); // ✅ 여기서 변경사항이 DB에 반영됨 (Commit)

    if (true) throw new RuntimeException("예외 발생!"); // 강제 오류

    User user2 = userRepository.findById(id2).orElseThrow();
    user2.setName(newName);
    userRepository.save(user2);
}
  • 예외 발생 후에도 user1의 데이터가 변경된 상태로 남아 있음 (Rollback 안 됨)
  • @Transactional을 사용하면 모든 작업이 하나의 단위로 처리되므로, 중간에 실패하면 이전 작업도 롤백됨.
  • @Transactional을 안 쓰면 중간에 예외가 발생해도 이미 변경된 데이터가 저장될 수 있음

 

위치에 따라 동작 방식이 달라짐

1️⃣ 클래스 전체에 @Transactional 적용

@Service
@Transactional
public class UserService { //클래스에 @Transactional을 적용하면 모든 메서드에 자동으로 트랜잭션이 적용
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void updateUser(Long id, String newName) { //트랜잭션 내에서 실행
        User user = userRepository.findById(id).orElseThrow();
        user.setName(newName);
    }

    public void deleteUser(Long id) { //트랜잭션 내에서 실행
        userRepository.deleteById(id);
    }
}

 

2️⃣ 특정 메서드에만 @Transactional 적용

@Transactional
public void updateUser(Long id, String newName) { //이 메서드에서만 트랙잭션 적용
    User user = userRepository.findById(id).orElseThrow();
    user.setName(newName);
}

주의할 점

1️⃣ @Transactional은 public 메서드에서만 동작

@Transactional
private void updateUser(Long id, String newName) { ... } // ❌ 적용 안 됨!
  • @Transactional은 public 메서드에서만 동작
  • private, protected, default 메서드에서는 적용되지 않음.

2️⃣ 같은 클래스 내부에서 @Transactional 메서드 호출 시 적용되지 않음

@Service
public class UserService {
    @Transactional
    public void updateUser(Long id, String newName) {
        User user = userRepository.findById(id).orElseThrow();
        user.setName(newName);
    }

    public void updateAndNotify(Long id, String newName) {
        updateUser(id, newName); // ❌ 트랜잭션 적용 안 됨!
    }
}

 AOP(Aspect-Oriented Programming)의 관계

1️⃣ @Transactional은 내부적으로 AOP를 사용한다

Spring에서 @Transactional을 사용하면 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍) 를 통해 트랜잭션이 자동으로 관리됨.

  • @Transactional을 메서드에 붙이면 Spring AOP가 해당 메서드를 감싸서 트랜잭션을 시작하고 종료함
  • 예외가 발생하면 AOP가 감지해서 rollback을 수행
  • 예외가 없으면 AOP가 자동으로 commit을 수행

👉 @Transactional이 동작하는 원리는 AOP 기반의 프록시(Proxy) 패턴을 이용해서 트랜잭션을 관리하는 것!

 

2️⃣ @Transactional이 실행되는 과정

Spring이 @Transactional을 어떻게 AOP로 관리하는지 흐름을 살펴보자.

 

1. 클라이언트가 updateUser() 메서드를 호출
2. Spring AOP 프록시가 @Transactional을 감지하여 트랜잭션을 시작
3. updateUser()가 실행됨
4. 예외가 발생하면 → rollback, 예외가 없으면 → commit
5. Spring AOP 프록시가 트랜잭션을 종료

 

📌 코드 예제

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional //Spring AOP가 자동으로 트랜잭션을 관리함(내부적으로 AOP 프록시 객체가 동작하여 트랜잭션 처리)
    public void updateUser(Long id, String newName) {
        User user = userRepository.findById(id).orElseThrow();
        user.setName(newName);
    }
}

 

3️⃣ AOP 프록시(Proxy)가 트랜잭션을 관리하는 이유

Spring에서는 @Transactional을 적용하면 AOP 기반의 프록시 객체(Proxy Object)가 생성

 

📌 트랜잭션 프록시 동작 방식

클라이언트 → (프록시) → updateUser() 실행 → (프록시) → commit or rollback
  • AOP를 통해 메서드 실행 전후에 트랜잭션 시작/종료 기능을 추가할 수 있음
개념 설명
AOP(Aspect-Oriented Programming) @Transactional을 감싸는 프록시 역할
프록시(Proxy) 패턴 트랜잭션을 자동으로 시작하고 종료
Spring AOP 동작 방식 메서드 호출 시 프록시를 통해 트랜잭션 관리
트랜잭션 적용 시 주의점 같은 클래스 내부 호출 & private 메서드는 적용되지 않음
트랜잭션 전파(Propagation) 다른 트랜잭션과 충돌을 방지하기 위해 설정 가능