CS 잡지식

Spring Data JPA가 제공하는 SAVE()의 비밀(매우매우 중요)

JIN_YOUNG _KIM 2023. 5. 13. 22:39
@Transactional
@Override
public <S extends T> S save(S entity) { // Spring Data JPA가 제공하는 [저장] 메서드

   Assert.notNull(entity, "Entity must not be null");

   if (entityInformation.isNew(entity)) { // 새로운 엔티티 O -> PERSIST()호출
      em.persist(entity);
      return entity;
   } 
   else { // 새로운 엔티티 X -> merge()호출
      return em.merge(entity); // 절대 Spring Data JPA가 [저장]을 할 때, MERGE()를 사용하지 
      							//않도록 해야 한다.(구체적인 설명은 아래에서...)
   }
}

새로운 엔티티 : DB에 저장돼 있지 않은 엔티티

새로운 엔티티 x : DB에 이미 저장돼 있는 엔티티

merge의 치명적인 단점 : DB에 1번은 꼭 쿼리가 나간다는 것(아래에서 자세히 설명돼 있음)

(merge()를 사용해야 할 때는 보통 [저장]이 아닌, [준영속 엔티티]에 대한 [수정]일 때 사용해야 하는 것이 가장 좋다)

(그러나 merge()는 [저장]이든 [수정]이든 사용할 일이 거의 없고, 또한 사용 권장을 절대 하지 않는다)

([수정]이면 Dirty Checking을 사용하고, [저장]이면 꼭 persist()를 사용하도록 해야 한다)

 

Q. 그럼, 이 엔티티가 새로운 엔티티인지 아닌지를 어떻게 구별을 할까?

A. 

IF. 식별자(id)가 [객체]일 때 null 로 판단

Entity
@Getter
public class Item {

    @Id@GeneratedValue
    Long id;

}

 

public interface ItemRepository extends JpaRepository<Item,Long> {


}

 

@Test
public void save(){

    Item item = new Item(); // 이 시점의 Item 엔티티의 식별자(id)는 [null] -> [새로운 엔티티]
    itemRepository.save(item); // em.persist(item)로 저장됨.

[객체]의 식별자(id)가 null이므로, DB에는 저장돼 있지 않은 엔티티라고 판단을 해서 아래의 코드에도 나와 있지만

persist()로 [저장]을 한다. 

 

[객체]가 새로운 엔티티가 아닌 경우!

@Entity
@Getter
public class Item {

   /* @Id@GeneratedValue
    Long id;*/

    @Id
    String id;

}

 

 

@Test
public void save(){
    
  
    // 새로운 엔티티 x
    Item item = new Item("AAA"); // 이미 식별자(id)가 존재(null이 아님) -> 이미 DB에 저장돼 있는 엔티티라고 판단.
    itemRepository.save(item); // Spring Data Jpa는 em.[merge(ite)]으로 저장(이때 치명적인 단점이, 
                               // DB에 이미 저장돼 있는 새로운 엔티티가 아닌 엔티티이므로, SELECT쿼리를 1번 날린후,
                                // 트랜잭션이 종료되면, INSERT문이 날라 간다.(SELECT문이 1번 날라간다는 치명적인 단점!)


}

실행을 해보면 알겠지만, 이미 DB에 저장돼 있는 엔티티(새로운 엔티티X)라고 판단을 해 버리기에

DB로 부터 조회를 해오기 위해서 SELECT쿼리문을 1번 날려 버린다.

(persist()면 select문 없이 insert문 한 번만 나간다.)

 

Q.그럼 만약 불가피하게, @GeneratedValue를 사용하지 못하여 개발자가 직접 클라이언트 코드에서 id값을 넣어 줘야 하

는 경우에는 어떻게 persist()를 호출하게 만들 수가 있을까??

(id값이 이미 들어가 있다고 꼭 이미 DB에 저장된 객체라고 판단하는 것에는 조금의 무리가 있다.)

A. Persisable 인터페이스를 오버라이딩을 하면 된다. 

public class Item implements Persistable<String> {

    @Id
    private String id;
    
    
    @CreatedDate // persist()가 호출되기 직전에, cratedDate값이 들어감. 
    private LocalDateTime createdDate;
    
    
    public Item(String id) {
        this.id = id;
    }
    
    
    @Override
    public String getId() {
        return id;
    }
    
    
    @Override // 이 부분을 오버라이딩하여 판단 로직을 개발자가 적절히 바꿔 주면 된다. 
    public boolean isNew() {
        return createdDate == null; // createdDate가 없다면, 아직 한 번도 등록된 것이 아닌 것으므로
        							// DB에 없는 새로운 엔티티라고 판다. 
                                    //@CreatedDate는 해당 객체가 등록(저장)될 때, 딱 1번 값이 삽이 된다.
    }
}

SELECT문이 안 날라감!!!

 

 

IF. 식별자( id)가 [자바 기본 타입]일 때 0 으로 판단

식별자가 이미 존재하므로, DB에 저장돼 있다고 판단할 수밖에 없다.