트랜잭션과 더티 체킹

1️⃣ 트랜잭션(Transaction)

  • 정의: 하나의 작업 단위(작업의 묶음)로 간주되어, 모두 성공하거나 모두 실패해야 하는 작업을 의미합니다.

  • 특징 (ACID):

    • 원자성(Atomicity): 작업이 모두 성공하거나, 모두 롤백(실패)해야 함.
    • 일관성(Consistency): 트랜잭션이 완료된 후 데이터가 항상 일관된 상태를 유지.
    • 격리성(Isolation): 동시에 실행되는 트랜잭션은 서로 영향을 주지 않음.
    • 지속성(Durability): 트랜잭션이 성공적으로 완료되면 결과는 영구적으로 저장.

2️⃣ 더티 체킹(Dirty Checking)

  • 정의: JPA에서 엔티티를 관리할 때, 엔티티의 변경 사항을 감지하고 자동으로 업데이트 쿼리를 생성해 데이터베이스에 반영하는 기능.
  • 작동 원리:
    1. 엔티티를 가져오면 JPA는 이를 영속성 컨텍스트에서 관리.
    2. 엔티티의 상태가 변경되면 JPA는 변경 내용을 감지.
    3. @Transactional 이 끝날 때 변경 사항을 데이터베이스에 반영.
  • 더티 체킹은 트랜잭션이 끝날 때 자동으로 실행되기 때문에 개발자가 직접 UPDATE 쿼리를 실행할 필요가 없음.

Spring에서의 트랜잭션과 더티 체킹

1️⃣ Spring의 트랜잭션 관리

Spring에서는 @Transactional 어노테이션을 사용하여 트랜잭션을 선언적으로 관리할 수 있습니다.

@Transactional 사용법

@Service
public class UserService {

    @Transactional // 트랜잭션 시작
    public void updateUser(Long userId, String newName) {
        User user = userRepository.findById(userId).orElseThrow();
        user.setName(newName); // 변경
    } // 트랜잭션 커밋 또는 롤백
}
  • @Transactional 어노테이션이 붙은 메서드에서:
    • 메서드 실행 전에 트랜잭션 시작.
    • 예외 발생 시 롤백.
    • 정상 실행 시 커밋.

롤백(Rollback) :

트랜잭션에서 문제가 발생하거나, 작업이 완료되지 않았을 때 이전 상태로 되돌리는 작업을 말합니다.

Spring에서의 롤백 처리:
Spring의 @Transactional은 기본적으로 RuntimeException이 발생하면 롤백을 실행합니다.

@Transactional
public void updateUser(Long userId, String newName) {
    User user = userRepository.findById(userId).orElseThrow();
    user.setName(newName);
    if (newName.isEmpty()) {
        throw new RuntimeException("이름은 비어 있을 수 없습니다."); // 롤백 발생
    }
}
  • CheckedException 처리:
    CheckedException은 기본적으로 롤백되지 않으므로, 명시적으로 롤백 설정이 필요합니다.
@Transactional(rollbackFor = Exception.class)
public void updateUserWithCheckedException(Long userId, String newName) throws Exception {
    User user = userRepository.findById(userId).orElseThrow();
    user.setName(newName);
    throw new Exception("테스트 예외"); // 롤백 발생
}
  • 롤백 시 주의사항:
    • 롤백은 트랜잭션의 변경 사항만 되돌리며, 외부 시스템(API 호출, 파일 쓰기 등)은 롤백되지 않음.
    • 트랜잭션이 포함되지 않은 작업은 롤백 시 영향을 받지 않음.

2️⃣ Spring의 더티 체킹 사용

Spring Data JPA는 JPA의 더티 체킹을 그대로 활용합니다.

더티 체킹 동작 예시

@Transactional
public void updateUserEmail(Long userId, String newEmail) {
    User user = userRepository.findById(userId).orElseThrow();
    user.setEmail(newEmail); // 엔티티 상태 변경
} // 트랜잭션 종료 시점에 자동으로 UPDATE 쿼리 실행
  • 엔티티를 수동으로 저장(save) 하지 않아도, 변경 사항이 자동으로 반영됩니다.

    구분설명관련 SQL
    PERSIST(중요)새로운 엔티티를 영속성 컨텍스트에 추가INSERT
    MERGE준영속/비영속 상태의 엔티티를 영속 상태로 변경-
    REMOVE(중요)엔티티를 영속성 컨텍스트와 DB에서 삭제DELETE
    REFRESHDB의 데이터로 엔티티를 다시 조회하여 갱신-
    DETACH영속성 컨텍스트에서 엔티티를 분리(준영속 상태로 전환)-

성능상 사용하면 좋은 상황

  1. 트랜잭션 사용
  • 여러 작업(INSERT, UPDATE, DELETE)을 하나의 작업 단위로 묶어서 처리해야 할 때.
  • 예: 은행 송금 시스템(출금과 입금이 동시에 이루어져야 함).
  1. 더티 체킹 사용
  • 엔티티의 변경이 자주 발생하며, 이를 자동으로 반영해야 할 때.
  • 예: 사용자 정보 수정, 주문 상태 변경 등.

주의해야 할 점

  1. 트랜잭션 주의사항
  • 트랜잭션은 서비스 계층에서 관리하는 것이 좋습니다. (DAO 계층에서 사용하지 않음)

  • 트랜잭션 범위가 넓으면 성능에 영향을 미칠 수 있으므로, 최소한으로 유지.

  • @Transactional프록시 방식으로 동작하므로, 동일 클래스 내 메서드 호출 시 트랜잭션이 적용되지 않을 수 있음.

    프록시(Proxy) :

    JPA에서 지연 로딩(Lazy Loading)을 위해 사용하는 기술로, 실제 엔티티 대신 프록시 객체를 반환하여 필요할 때 데이터를 로드합니다.

    • 지연 로딩과 즉시 로딩:
      • 지연 로딩(Lazy Loading): 엔티티를 조회할 때 연관 데이터를 바로 로드하지 않고, 실제 접근 시점에 로드.
      • 즉시 로딩(Eager Loading): 엔티티를 조회할 때 연관 데이터를 즉시 로드.
    • 프록시 객체 동작:
      @Entity
      class User {
         @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
         private List<Order> orders;
      }
      User user = entityManager.find(User.class, 1L); // User는 로드되지만 Orders는 로드되지 않음
      List<Order> orders = user.getOrders(); // 이 시점에  Orders 쿼리가 실행
    • 주의사항:
      • 프록시 객체를 사용할 때, 영속성 컨텍스트가 닫히면 LazyInitializationException이 발생할 수 있음.
      • 지연 로딩을 무조건 사용하는 것이 아니라, 실제 데이터 사용 패턴에 맞게 설정해야 성능 최적화 가능.
  1. 더티 체킹 주의사항
  • 변경 감지를 위해 엔티티를 영속성 컨텍스트에서 관리해야 합니다.

  • 영속성 컨텍스트에 너무 많은 엔티티가 있으면 성능이 저하될 수 있음.

  • 불필요한 변경이 발생하지 않도록 엔티티 상태를 신중히 관리.

    영속성 컨텍스트 :

    JPA에서 하나의 트랜잭션 내에서 조회된 엔티티 객체를 보관하는 공간으로, 데이터베이스와의 연결을 관리하며 엔티티 상태를 추적합니다. 트랜잭션이 종료되면 영속성 컨텍스트도 사라지며, 영속성 컨텍스트는 트랜잭션 당 하나입니다.
    이를 통해 더티 체킹과 1차 캐시, 지연 로딩 등의 기능을 제공할 수 있습니다.

    • 동작 방식:
      1. 엔티티 조회 시 1차 캐시 사용:
        이미 조회된 엔티티는 영속성 컨텍스트에서 관리되므로, 다시 조회 시 데이터베이스 쿼리를 실행하지 않습니다.
        User user1 = entityManager.find(User.class, 1L); // DB 조회
        User user2 = entityManager.find(User.class, 1L); // 캐시에서 조회
      2. 엔티티 변경 감지:
        영속성 컨텍스트는 엔티티 상태를 추적하며, 트랜잭션 종료 시 변경된 데이터를 자동으로 반영합니다.
    • 장점:
      • 1차 캐시로 반복 조회 시 성능 최적화.
      • 변경 사항 추적 및 더티 체킹.
      • 데이터 일관성 보장.

🌟결론

  • 트랜잭션: 데이터의 일관성을 보장.
  • 더티 체킹: 변경 사항을 자동으로 감지 및 반영.
  • 영속성 컨텍스트는 JPA의 핵심으로, 엔티티 관리와 변경 감지를 담당하여 성능을 최적화.
  • 프록시는 연관 데이터를 지연 로딩할 때 사용되며, 필요 시점에 데이터베이스와 연결.
  • 롤백은 데이터의 일관성을 유지하는 데 필수적인 메커니즘으로, 예외 상황에서 트랜잭션을 원상 복구.

profile
다 먹어버릴거야!

0개의 댓글