본문 바로가기
IT

Spring Boot 3 JPA에서 데이터 수정(Update)하는 가장 깔끔한 방법과 예제

by 굿센스굿 2025. 5. 9.
반응형

 

Spring Boot 3와 JPA를 사용하면서 가장 자주 마주하게 되는 기능 중 하나는 바로 **데이터 수정(Update)**입니다. 특히 save() 메소드 하나로 등록과 수정이 모두 가능하다는 점은 초보자에게는 혼란을, 숙련자에게는 유용함을 제공합니다. 이번 포스팅에서는 Spring Data JPA에서 update 쿼리를 어떻게 수행하며, 이를 어떤 방식으로 처리하는 것이 가장 깔끔하고 실용적인지 자세히 설명하겠습니다.


✅ JPA의 수정(Update)은 어떻게 동작할까?

Spring Data JPA에서 데이터를 수정하는 기본 흐름은 다음과 같습니다.

  1. 수정하고자 하는 데이터를 findById() 등으로 조회합니다.
  2. 조회된 엔티티는 **영속성 컨텍스트(Persistence Context)**에 포함됩니다.
  3. 이 엔티티의 필드 값을 변경하면 JPA는 해당 변경 사항을 감지(Dirty Checking) 합니다.
  4. 트랜잭션이 종료되면서 변경된 내용을 바탕으로 update 쿼리를 자동으로 실행합니다.

따라서 단순히 엔티티 객체의 값을 바꾸는 것만으로도 DB에는 실제 수정이 이루어지는 것입니다.


📦 실습을 위한 엔티티 및 Repository 구성

우선 JPA에서 사용할 엔티티와 리포지토리를 만들어보겠습니다.

// UpdateTestEntity.java
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UpdateTestEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    public void changeName(String name) {
        this.name = name;
    }
}
// UpdateTestRepository.java
import org.springframework.data.jpa.repository.JpaRepository;

public interface UpdateTestRepository extends JpaRepository<UpdateTestEntity, Long> {
}

🛠️ 데이터 수정 방식 #1: save() 또는 saveAndFlush() 사용

🔍 Step 1: 데이터 조회

우선 수정할 데이터를 findById() 등을 이용해 조회합니다.

UpdateTestEntity testEntity = repository.findById(1L).orElse(null);

✏️ Step 2: 데이터 변경

조회된 객체의 상태를 변경합니다.

testEntity.changeName("changed name");

💾 Step 3: save() 또는 saveAndFlush() 호출

repository.save(testEntity);
// 또는 즉시 flush
repository.saveAndFlush(testEntity);

💡 Tip!

  • save()는 트랜잭션 범위 내에서 사용될 경우 트랜잭션 커밋 시점에 flush 됩니다.
  • saveAndFlush()는 즉시 flush가 발생하므로 Hibernate가 생성한 쿼리 로그를 확인할 때 매우 유용합니다.

Hibernate 로그 예시:

select ute1_0.id, ute1_0.name 
from update_test_entity ute1_0 
where ute1_0.id=?

update update_test_entity 
set name=? 
where id=?

🛠️ 데이터 수정 방식 #2: @Transactional 활용하기

실무에서는 save()를 사용하는 방식보다 @Transactional을 통한 Dirty Checking 기반의 자동 수정이 더욱 일반적이며, 효율적입니다.

// UpdateService.java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class UpdateService {

    private final UpdateTestRepository repository;

    @Transactional
    public void updateEntity() {
        UpdateTestEntity entity = repository.findById(1L).orElseThrow();
        entity.changeName("changed name");
        // save() 호출 없이도 자동으로 update 실행됨
    }
}

⚙️ 왜 @Transactional이 더 좋은 선택인가?

  • save()는 내부적으로 select → merge → update 과정이 포함되어 있어 불필요한 리소스를 사용할 수 있습니다.
  • @Transactional은 수정만 감지해서 반영하므로 더 깔끔하고 직관적입니다.
  • 수정 도중 예외가 발생하면 자동으로 롤백 처리됩니다.
  • 여러 엔티티를 하나의 트랜잭션 내에서 수정할 수 있는 확장성이 뛰어납니다.

📋 주요 정리: 각 방식 비교

항목 save() @Transactional

자동 감지 여부 X O
쿼리 실행 시점 명시적으로 save 호출 시 트랜잭션 종료 시 자동
리소스 효율성 상대적으로 낮음 상대적으로 높음
코드 간결성 중간 매우 좋음
롤백 지원 수동 자동 (@Transactional)

🎯 실무에서 가장 많이 쓰이는 방식은?

실제 현업에서는 다음과 같은 흐름으로 업데이트를 처리합니다.

@Transactional
public void updateSomething(Long id, String newName) {
    UpdateTestEntity entity = repository.findById(id)
        .orElseThrow(() -> new IllegalArgumentException("데이터가 존재하지 않습니다."));
    entity.changeName(newName);
}

이 방식이 선호되는 이유는 매우 명확합니다.

  • 명확한 트랜잭션 경계
  • 불필요한 코드 제거
  • 간편한 테스트 가능
  • 예외 발생 시 자동 롤백

🧪 테스트 코드에서 saveAndFlush를 사용하는 이유

단위 테스트 작성 시에는 @Transactional을 적용해도 실제 update 쿼리가 콘솔에 찍히지 않을 수 있습니다. 그럴 때 saveAndFlush()를 사용하면 Hibernate의 쿼리 로그를 바로 확인할 수 있어 디버깅에 유리합니다.

@Test
void testUpdate() {
    UpdateTestEntity entity = repository.findById(1L).orElseThrow();
    entity.changeName("changed name");
    repository.saveAndFlush(entity); // 쿼리 확인 가능
}

🧠 마무리 요약

  • JPA에서는 save()를 통해서도 수정이 가능하지만, @Transactional을 사용하는 방식이 더 정석적이고 효율적입니다.
  • 엔티티는 조회 후 수정만 해도 Dirty Checking에 의해 자동으로 update 쿼리가 실행됩니다.
  • 테스트 코드에서는 쿼리 로그 확인을 위해 saveAndFlush() 사용이 유용할 수 있습니다.
  • save()는 등록/수정 둘 다 처리하지만, 실수로 중복 insert 또는 overwrite가 일어날 가능성이 있어 신중하게 사용해야 합니다.

 

반응형