스프링데이터 + JPA/스프링 데이터 JPA

25. 새로운 엔티티 구별방법, isNew

sdafdq 2023. 11. 26. 14:23
@Transactional
@Override
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);
    }
}

이 부분에서 isNew(T) 부분에 대해서 말할거임.

 

기본 전략은 식별자로

식별자가 객체면 null인지로 파악

식별자가 자바 기본타입이면 0인지로 파악

 

Persistable 인터페이스를 구현하여 판단 로직 변경이 가능함.

 

 

 

@Entity
@Getter
public class Item {
    @Id
    @GeneratedValue
    private Long id;
}

 

@Test
public void save(){
    Item item = new Item();
    itemRepository.save(item);
}

여기 이렇게 해 보면 

쟤는 일단 Long은 객체니 null이 들어갈 거임.

그래서 새 엔티티임.

 

Long 말고 long이면 원시타입이니 0인지 아닌지로 검사할거임.

원시타입은 null이 되면 참조했을 때 NullException이 나므로.

 

근데 문제가 있을 수 있는 경우가,

@Entity
@Getter
public class Item {
    @Id
    private String id;
}

이렇게 id를 무언가 직접 넣어줄 때.

 

이러면 String은 일단 객체고, 

@SpringBootTest
class ItemRepositoryTest {
    @Autowired ItemRepository itemRepository;
    @Test
    public void save(){
        Item item = new Item("A");
        itemRepository.save(item);
    }
}

이렇게 했을 때,

 

@Transactional
@Override
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);
    }
}

여기서 전략에 따라 객체인데 null은 아니니 merge로 감.

 

merge는 먼저 DB에 select 쿼리를 날려 가져오고, 거기에 덮어 씌움.

근데 가져와 봤는데 없어서, 다시 또 아 얘가 없는거구나 인지하고 그후 insert 쿼리를 날림.

    select
        i1_0.id 
    from
        item i1_0 
    where
        i1_0.id=?


    insert 
    into
        item
        (id) 
    values
        (?)

 

비효율적임.

 

merge라는게  DB에 값이 있다고 생각하고 찾아온 다음 그걸 변경감지를 통해 값을 바꿔주는 것이다.

근데 지금같은 경우는 아예 없음을 인지하여 새롭게 값을 insert한다.

 

우리가 merge를 직접 쓰는 경우는 없을 것이다.

merge는 데이터를 통으로 다 갈아끼우는 것 이기 때문에..

예를 들어 우리는 일부분만 변경하면 되서 변경이 필요한 부분만 새 객체를 만들어서 바꿔줬는데, 나머지가 null인 상태면 그 곳들은 null로 데이터가 뒤덮혀 지는 것이다.

merge를 쓸 일은 엔티티가 영속성 컨텍스트에서 detach되어 떨어졌다가 다시 붙이고 싶을때 merge를 쓰는 둥 하는데, 그럴 일은 거의 없다.

 

기본적으로 merge를 직접 쓴다고 생각하지 마라.

 

그럼 이럴 때는 어떻게 하냐?

 

isNew를 Override 해 주면 된다.

 

어떻게 해 주냐면,

Persistable 이거를 상속받으면

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

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

    @CreatedDate
    private LocalDateTime createdDate;

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

    @Override
    public boolean isNew() {
        return createdDate == null;
    }
}

isNew라는 메소드와 getId라는 메소드를 가지고 있다.

@Override 하여 식별 메소드를 작성해 주자.

getId는 그냥 getId 하면 된다.

isNew는 왜 우리가 보통 어떤 테이블이든 createdDate는 넣으니까,

https://qwefdg3.tistory.com/893

 

저거에 값이 있으면 새 값이 아닌거임. 

 

여튼간에 이거는,

@Transactional
@Override
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가 null이었다가,

persist() 순간에 persist 하기 전에 미리 createdDate에 @CreatedDate와 @EntityEventListener(AuditingEntityListener.class)에서 이벤트 순간을 들어서 현재 LocalDataTime을 넣어준다.

여튼간에, 저건 persist를 하기 전에 가로채는 거고, isNew 검사하는 동안은 우리가 정의해뒀던 isNew대로 createdDate가 null이다. 그래서 그냥 바로 persist를 수행한다.

 

저걸 안하고 String에 id를 임으로 줬다고 String은 객체니 null이 아니니 merge로 넘어가서 조회해오고 조회해보니 null이어서 인지하고 insert하고 그런 과정없이 딱 깔끔하게 @Override해서 만든 식별로직으로 판단해서 isNew인걸 인지하고 바로 insert한다.

 

이거는 실무에서 많이 씀.

식별자를 뭔가 임의로? 뭐 uuid나 그런걸로? 부여해야 할 때?