@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에 저장돼 있다고 판단할 수밖에 없다.
'CS 잡지식' 카테고리의 다른 글
Projection 최적화 기능(feat.Spring Data JPA) (0) | 2023.05.14 |
---|---|
Query by Example (0) | 2023.05.14 |
요청 API에서 Paging 정보 넘기기!!! (0) | 2023.05.13 |
[도메인 엔티티]로 API를 개발이 허용되는 유일한 경우! (0) | 2023.05.13 |
@Column(updateable , insertable .... ), @PrePersist, @PreUpdate (0) | 2023.05.13 |