Spring Data Jpa Update에 대해

조윤상·2024년 6월 17일

Spring Data Jpa를 사용한 CRUD 방법 구현

  1. 저장(CREATE)
    save를 사용

  2. 조회(RETRIEVE)
    find나 get을 사용

  3. 삭제(DELETE)
    delete를 사용

엔티티를 수정하려면(UPDATE)?


Spring Data Jpa에서 Update하는 방식

Spring Data Jpa에서는 sql의 insert와 update가 모두 save함수로 수행된다. 먼저 Save함수가 어떻게 동작하는지 살펴보자

@Transactional
@Override
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

save함수를 확인해보면 if문으로 분기가 되는데 isNew함수를 실행해
1. 엔티티가 새로운 엔티티면 persist 함수를 실행하고
2. 엔티티가 이미 존재하면 merge함수를 실행한다.

여기서 엔티티를 분기하는 isNew함수는 @Id로 마킹된 엔티티의 ID가 (int, long, char등 인 경우) 0인지 아닌지 확인하거나 (String, Long등 인 경우) null인지 아닌지 확인한다.

이 내용을 살펴본 결과 save함수의 동작 과정은 아래와 같이 정리할 수 있다.

  • id가 없는(0 이거나 null) 경우 persist 수행
  • id를 전달할 경우 해당 id에 대한 데이터가 있다면 merge 수행
  • id를 전달할 경우 해당 id에 대한 데이터가 없다면 persist 수행

이제 merge가 어떤 식으로 update를 진행시키는지 알아보도록 하자

먼저 영속상태와 준영속 상태에 관해 간단히 말하자면

영속 상태 : 영속성 컨텍스트가 자동으로 해당 엔티티를 영속화해주는 것
준영속 상태 : 영속성 컨텍스트의 관리 밖에 있는 상태

다른 내용 없이 정말 간단하게 설명하면 준영속 상태이면 값을 바꾸든 뭘 하든 영속성 컨텍스트가 관리하지 않으므로 실제로 적용이 되지 않는 상태라고 생각하면 편하다.

이 때 준영속 상태를 -> 영속 상태로 변경하는 두 가지 방법 중 하나가 바로 merge()이다.


merge() 방식으로 update 수행

예시 코드

@PutMapping("/user/{id}")
public void update(@PathVariable Long id, @RequestBody User requestUser) {
    User user = userRepository.findById(id).orElseThrow(() -> {
        // IllegalArgumentException 예외 처리
        throw new IllegalArgumentException("해당하는 아이디가 없습니다 id : " + id);
    });

    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());

    userRepository.save(user);		//엔티티를 수정한 후 명시적으로 save함수 실행
}

merge의 동작 방식은 다음과 같다.

  • 1차캐시 엔티티에서 해당 엔티티를 찾는다.
  • 여기서 찾지 못하면 DB에서 엔티티를 검색한다.
  • DB에서 가져온 엔티티의 값에 준영속 상태의 값들을 하나씩 채워넣는다.
  • 이렇게 채워넣은 엔티티를 return한다.
  • 정확히 말하자면, 준영속화 되어 있는 엔티티를 영속화 해주는게 아니라, 영속화 된 새로운 엔티티에 준영속화 상태의 값을 끼워넣어 주는 것이다.

이 방식에는 한 가지 큰 문제점이 있다.
바로 준영속 상태의 값들 중 null인 값이 있다면 기존의 데이터에서도 그게 null이 된다는 것!!

예를 들어

이름 나이 키
류찬 26 178
으로 이미 저장되어 있고, 이 사람이 키가 커서 183이 되었다고 가정했을 때

키만 183으로 설정한 엔티티를 merge하면

이름 나이 키
null null 183
이 될 수도 있다는 것이다!!


Dirty Checking 방식으로 update 수행

예시 코드

@Transactional		//명시적인 save함수 호출 대신 @Transcational을 통해 entity가 수정되면 dirty checking으로 update.
@PutMapping("/user/{id}")
public void update(@PathVariable Long id, @RequestBody User requestUser) {
    User user = userRepository.findById(id).orElseThrow(() -> {
        // IllegalArgumentException 예외 처리
        throw new IllegalArgumentException("해당하는 아이디가 없습니다 id : " + id);
    });

    user.setPassword(requestUser.getPassword());
    user.setEmail(requestUser.getEmail());

}

위의 Merge 방식과는 다르게 save함수를 사용하지 않고 대신 함수에 @Transactional어노테이션을 붙여주었다.

함수나 클래스에 @Transactional 어노테이션을 붙여주면 해당 함수나 클래스가 Transaction이 되는데 Transaction을 간단히 설명하면

  • 데이터베이스의 상태를 변경하는 작업 또는 한번에 수행되어야하는 연산들을 의미한다.
  • begin으로 시작해 commit으로 끝나 db에 반영된다.
  • begin과 commit 사이에서 예외 발생시 rollback 처리된다.
  • ACID의 4가지 속성을 가진다.

JPA는 엔티티를 조회할때 해당 엔티티의 조회 상태 그대로 스냅샷을 만들어 놓고 Transaction이 끝나는 시점에는 이 스냅샷과 비교해 다른점이 있으면 Update 쿼리를 실행한다.

0개의 댓글