JPA

[JPA] 더티 체킹(Dirty Checking)? + 동시성 이슈/제어

cornarong 2024. 6. 13. 16:41

1. 더티 체킹 개념

  • 더티 체킹(Dirty Checking): JPA의 기능으로, 영속성 컨텍스트에 담긴 엔티티의 상태 변화를 자동으로 감지하여 트랜잭션 커밋 시점에 변경 사항을 데이터베이스에 반영하는 메커니즘.

 

2. 영속성 컨텍스트(Persistence Context)

  • 정의: 엔티티를 영속성 컨텍스트에 담아 관리하는 일종의 캐시.
  • 역할: 엔티티의 상태를 추적하고, 변경된 엔티티를 데이터베이스에 자동으로 반영.

 

3. 엔티티의 생명주기

  • 비영속 상태(New/Transient): 엔티티가 영속성 컨텍스트에 담기지 않은 상태.
  • 영속 상태(Managed): 엔티티가 영속성 컨텍스트에 담긴 상태. 더티 체킹의 대상.
  • 준영속 상태(Detached): 영속성 컨텍스트에서 분리된 상태.
  • 삭제 상태(Removed): 엔티티가 삭제된 상태.

 

4. 더티 체킹 동작 원리

  • 변경 감지: 영속성 컨텍스트는 트랜잭션이 시작될 때 엔티티의 스냅샷을 저장. 트랜잭션 종료 시 스냅샷과 비교하여 변경 사항을 감지.
  • 트랜잭션 커밋 시점: 감지된 변경 사항을 데이터베이스에 반영.

 

5. 트랜잭션 내에서의 더티 체킹 예제

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void updateUserName(Long userId, String newName) {
        // 1. 엔티티 조회: 영속성 컨텍스트에 담김
        User user = userRepository.findById(userId).orElseThrow(() -> new EntityNotFoundException("User not found"));
        // 2. 엔티티 속성 변경: 더티 체킹 대상
        user.setName(newName);
        // 3. 트랜잭션 커밋: 변경 사항이 자동으로 데이터베이스에 반영
    }
}

 

6. 주요 고려사항

  • 트랜잭션 사용: 더티 체킹은 트랜잭션 내에서 작동.
  • 영속성 컨텍스트: 엔티티가 영속성 컨텍스트에 있어야 더티 체킹이 가능.
  • 명시적 저장: 경우에 따라 명시적으로 save 메서드를 호출하여 변경 사항을 저장할 수도 있음.

 

7. 신규 엔티티와 더티 체킹 

  • 신규 엔티티는 save 호출 시 영속성 컨텍스트에 담기며, 그 이후 변경 사항은 더티 체킹의 대상이 됨.
@Transactional
public void createUser(String name) {
    User user = new User();
    user.setName(name);
    userRepository.save(user);  // 엔티티가 영속성 컨텍스트에 담기고 데이터베이스에 저장됨
}

 

 

8. 동시성  이슈 

* 동시성 문제는 여러 사용자가 동시에 동일한 데이터를 수정할 때 발생할 수 있음. 예로 두 사용자가 동일한 엔티티를 동시에 수정하고 저장하는 경우 데이터 일관성 문제가 발생할 수 있다.

 

9. 해결(제어) 방법

- 여러가지 제어 방법이 있고 락을 사용하는 방법 두가지만 간략하게 정리했습니다. 이런 방식이 있다 정도만 알고 활용하면 될 것 같습니다.

 

1. 낙관적 락(optimistic lock) : 가장 일반적인 해결 방법으로, Optimistic Locking을 활용하여 충돌을 방지한다.
엔티티에 버전 필드(ex. @Version)를 추가하여 버전이 일치하지 않는 경우에는 충돌로 간주하고 예외를 발생시키거나 복구한다.

  • 사용 방법: 낙관적 락은 여러 사용자가 동시에 데이터를 수정할 가능성이 낮을 때 사용된다. 엔티티를 읽을 때 버전 번호나 타임스탬프를 체크하여 충돌을 감지하고, 업데이트시에만 실제로 데이터베이스에서 해당 엔티티의 상태를 확인하고 업데이트한다.
  • 장점: 동시성이 높은 환경에서 성능이 좋다. 일반적인 상황에서 데이터베이스 락을 걸지 않고도 충돌을 피할 수 있다.
  • 적합한 상황: 대부분의 경우에서 사용할 수 있으며, 충돌이 발생할 가능성이 낮은 경우 적합하다.

- optimistic lock 예제)

@Entity
public class Entity {
    @Id
    private Long id;
    
    @Version
    private Long version; // Optimistic Locking을 위한 버전 필드
   
    // getters, setters, other fields...
}

 

 

2. 비관적 락(pessimistic lock) : 특정 엔티티를 읽을 때 잠금을 거는 방식. 이 방법은 특히 긴 트랜잭션 내에서 엔티티의 상태가 변경되지 않도록 보장한다. "LockModeType.PESSIMISTIC_WRITE"를 사용하여 엔티티를 잠근다.

 

  • 사용 방법: 비관적 락은 데이터가 수정될 가능성이 크거나 긴 시간 동안 데이터를 잠금(lock) 해야 할 때 사용된다. 데이터를 읽을 때 바로 데이터베이스에서 잠금을 걸어 다른 사용자가 해당 데이터를 수정할 수 없도록 한다.
  • 장점: 데이터 일관성을 보장할 수 있으며 긴 트랜잭션에서 데이터가 변경되지 않도록 보호할 수 있다.
  • 적합한 상황: 특정 데이터의 동시 수정이 빈번하거나 데이터 일관성을 엄격하게 유지해야 하는 경우에 적합하다.

- pessimistic lock 예제)

public interface EntityRepository extends JpaRepository<Entity, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT e FROM Entity e WHERE e.id = :id")
    MyEntity findByIdWithPessimisticWriteLock(@Param("id") Long id);
}

 


 

9.  더티 체킹(Dirty Checking) 정리

* JPA 더티 체킹은 영속성 컨텍스트에서 엔티티의 상태 변화를 자동으로 감지하여 데이터베이스에 반영하는 핵심 기능.

 

1. 자동 변경 감지: 트랜잭션 내에서 영속성 컨텍스트에 있는 엔티티의 속성 변화를 추적한다

2. 트랜잭션 커밋 시점: 트랜잭션이 커밋될 때, 더티 체킹에 의해 변경된 엔티티의 상태가 데이터베이스에 자동으로 반영된다

3. 영속성 컨텍스트 필요: 엔티티가 영속성 컨텍스트에 관리되어야만 더티 체킹이 작동한다

4. 신규 엔티티의 저장: 신규 생성된 엔티티는 save() 메서드를 통해 데이터베이스에 저장되고, 이후에 발생하는 변경 사항도 더티 체킹을 통해 관리된다

5. 명시적 저장: 경우에 따라 명시적으로 save() 메서드를 호출하여 변경 사항을 데이터베이스에 반영할 수 있다

 

10.  동시성 해결 정리

1. 낙관적 락 : 충돌이 적을 때 사용하며, 성능을 위해 데이터베이스 락을 피하고 업데이트 충돌을 감지한다.

2. 비관적 락 : 충돌이 발생할 가능성이 크거나 데이터 일관성을 강제로 유지해야 할 때 사용하며, 데이터베이스 락을 활용 하여 데이터를 보호한다람쥐.