- JPA의 save() 메서드
1. 새로운 엔티티면 저장(persist)
2. 새로운 엔티티가 아니면 병합(merge)
- 새로운 엔티티를 판단하는 기본 전략
1. 식별자가 객체일 때 null 로 판단
2. 식별자가 자바 기본 타입일 때 0 으로 판단
3. Persistable 인터페이스를 구현해서 판단 로직 변경 가능
- Item 엔티티
@Entity
@Getter
public class Item {
@Id @GeneratedValue
private Long id;
}
- Item Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
}
- save() 메서드
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
- save() 메서드가 호출되면 위와 같이 entity가 null, 0인지 확인한다. null이나 0일 경우 persist를 수행한다.
- persist를 호출하게 되면 insert 쿼리 한번만 실행하게 된다.
- Test 코드
@SpringBootTest
class ItemRepositoryTest {
@Autowired ItemRepository itemRepository;
@Test
public void save() {
Item item = new Item();
itemRepository.save(item);
}
}
- 참고
1. JPA 식별자 생성 전략이 @GenerateValue면 save() 호출 시점에 식별자가 없으므로 새로운 엔티티로 인식해서 정상 동작한다.
2. JPA 식별자 생성 전략이 @Id만 사용해서 직접 할당이면 이미 식별자 값이 있는 상태로 save()를 호출한다. 이 경우 merge()가 호출되고 merge()는 DB에서 값을 확인하기 때문에 비효율적이다. 따라서 Persistable을 사용해서 엔티티 확인 여부를 직접 구현하는게 효과적인다.
- @GenerateValue를 사용하지 않고 직접 식별자를 사용한 경우
@Entity
@Getter
//@NoArgsConstructor(access = AccessLevel.PROTECTED) 기본생성자 생성 대신 작성하면 된다.
public class Item {
@Id
private String id;
public Item() {
}
public Item(String id) {
this.id = id;
}
}
- Test 코드
@SpringBootTest
class ItemRepositoryTest {
@Autowired ItemRepository itemRepository;
@Test
public void save() {
Item item = new Item("A");
itemRepository.save(item);
}
}
- entity가 null, 0이 아니기 때문에 merge()가 실행되게 되고 DB에 쿼리를 날려서 해당 데이터가 있는지 확인한다.
- DB에서 데이터를 확인 후 insert 쿼리를 날린다.
- merger()를 사용하게 되면 쿼리가 추가로 발생하기 때문에 사용되는 것이 좋지 않다.
- Persistable 인터페이스 사용
@Entity
@Getter
//@NoArgsConstructor(access = AccessLevel.PROTECTED) 기본생성자 생성 대신 작성하면 된다.
public class Item implements Persistable<String> {
@Id
private String id;
public Item() {
}
public Item(String id) {
this.id = id;
}
@Override
public boolean isNew() {
return false;
}
}
- Persistable<String> 인터페이스를 사용하게 되면
@Override
public boolean isNew() {
return false;
}
- isNew() 구현체를 구현하게 된다.
- 실무에서는 id를 통해 판단하기가 쉽지 않다.
@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;
}
}
- @CreatedDate는 JPA persist가 호출되기 전에 동작한다. 때문에 별도의 쿼리를 날리지 않는다.
- @CreatedDate의 createdDate; 의 값은 persist되기 전에 null이기 때문에 isNew()는 null로 인식하고 save() 메서드를 persist로 실행하게 된다.
출처 : 인프런 - 김영한(실전! 스프링 데이터 JPA)
'Programming > Spring' 카테고리의 다른 글
Spring - 데이터 검증(컨트롤러, 데이터 타입, @ControllerAdvice...) (0) | 2023.08.28 |
---|---|
JPA - 기본 동작 방식, 아키텍처 (0) | 2023.08.01 |
Spring Data JPA - Web 확장 (도메인 클래스 컨버터, 페이징과 정렬) (0) | 2023.07.26 |
Spring Data JPA - Auditing (0) | 2023.07.26 |
Spring Data JPA - 사용자 정의 리포지토리 (0) | 2023.07.26 |