본문 바로가기
Spring

[Spring Data JPA] 직접 PK 값을 설정하면 발생하는 문제 - Persistable

by 개미가되고싶은사람 2025. 1. 15.

문제 상황

@GeneratedValue를 사용하지 않고, 직접 PK 값을 설정해야 하는 경우가 있습니다. 잘못된 PK 값 설정 시, SimpleJpaRepository.save() 메소드에서 entityInformation.isNew(entity)가 false를 반환하게 됩니다. 이 경우, merge() 메소드가 호출되며, 데이터베이스에 해당 데이터가 존재하는지 확인 후, 없으면 새로운 데이터를 삽입합니다. 이로 인해 예상치 못한 자원 사용이 발생할 수 있습니다.

// 문제가 발생하는 엔티티
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item {

    @Id
    private String id;

    public Item(String id) {
        this.id = id;
    }
}

// 문제 상황
@Test
void test() {
	itemRepository.save(new Item(UUID.randomUUID().toString())); // UUID로 간단한 예시
}

 

 

해결 방법

Persistable 인터페이스의 isNew() 메소드를 직접 구현하여 문제를 해결할 수 있습니다. 간단한 방법으로는 엔티티에 생성일 속성을 추가하고, 이 값이 null이면 true, 아니면 false를 반환하도록 설정하는 것입니다.

@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {

    @Id
    private String id;

    @CreatedDate
    private LocalDateTime createdDate;

    public Item(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return createdDate == null; // 생성일이 null이면 새로운 엔티티로 간주
    }
}


SimpleJpaRepository

@Override
@Transactional
public <S extends T> S save(S entity) {
    Assert.notNull(entity, "Entity must not be null");

    if (entityInformation.isNew(entity)) {
        entityManager.persist(entity); // 새로운 엔티티일 경우
        return entity;
    } else {
        return entityManager.merge(entity); // 기존 엔티티일 경우
    }
}

 

@CreatedDate 애노테이션을 사용하면 엔티티가 저장될 때 자동으로 생성일이 설정되므로 처음 save() 메소드를 호출할 때 createdDate가 null이 아니게 되어 isNew()가 false를 반환할 수 있습니다. 이로 인해 isNew() 메소드의 조건이 잘못된 것처럼 보일 수 있지만, save(S entity) 메소드를 보면 entityInformation.isNew(entity)로 구분한 후 해당 값이 true면 entityManager.persist(entity)가 실행되고, 이때 @CreatedDate에 의해 시간이 설정되므로 createdDate가 null인 경우에만 새로운 엔티티로 간주하기 때문에 코드에는 문제가 없습니다.