JPA - save() 톺아보기, new Entity 판정법

·2024년 5월 7일
0

Spring/JPA

목록 보기
14/15

인프런 김영한 강사님 강의를 듣고 정리한 내용입니다.

스프링 데이터 JPA의 save()

스프링 데이터 JPA 구현체의 save() 메서드는 new 엔티티라면 persist()를 하고(저장), 그렇지 않으면 merge() 하도록(병합) 구현되어 있다.

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

merge와 persist 차이

다음 코드로 merge와 persist의 차이를 알아보자.

// persist 코드
	@Test
    void mergeTest(){
        Member member = new Member("member");
        em.persist(member);
        System.out.println("member = " + member);
    }
    
  // 출력 
  // member = Member(id=1, username=member, age=0)

// merge 코드
 	@Test
    void mergeTest(){
        Member member = new Member("member");
        Member merged = em.merge(member);
        
      	// member != merged
        // member는 준영속 엔티티, merged는 영속 엔티티
        
        System.out.println("member = " + member);
        System.out.println("merged = " + merged);
    }
    
    // 출력
    // member = Member(id=null, username=member, age=0)
    // merged = Member(id=1, username=member, age=0)

persist에서는 member 자체가 영속된다. 하지만 merge를 할 때는 member는 준영속 상태로 남고, merge 결과 반환되는 merged가 영속 엔티티이다.

출력 결과를 보면 merge에서 member의 id는 여전히 null로 남아있다.

조건에 따라 merge, persist 하는 이유

조건에 따라서 persist 하거나 merge를 하게 된다.
merge는 병합 뿐만 아니라 저장할 때 사용해도 된다고 생각하고 무조건적으로 merge를 사용하게되면 다음과 같은 문제가 발생한다.

  1. merge 하는 엔티티 자체는 준영속 상태로 남음
    앞서 말한 것과 같이 반환되는 값만이 영속 엔티티이다.
  2. merge는 실행 전 엔티티를 조회한다.
    1차 캐시에 엔티티가 없으면 DB 조회가 이루어진다. 새로운 엔티티를 저장하는데 DB 조회를 하는 것은 상당히 비효율적이다.

그렇다면 조건을 만들어줘야한다. 바로 새로운 엔티티인지 확인하는 것이다.

조건 - 새로운 엔티티인가?

  • 식별자가 객체일 때는 null인지 판단한다.
  • 식별자가 자바 기본타입일 때는 0인지 판단한다.

식별자는 기본적으로 pk로 확인하지만, generatedValue가 아닌 경우에는 Persistable을 사용해 다른 필드를 식별자로 하여 확인할 수도 있다.

식별자가 pk

기본적으로 새로운 엔티티인지 확인하는 방법은 pk를 확인하는 것이다.
pk를 통해 확인하는 방법에서는 반드시 pk가 GeneratedValue여야 한다.

이제 save에서 이 조건을 통해 pk가 null이면 persist()를, 아니라면 merge()를 호출하게 된다.

식별자가 pk가 아님 - Persistable과 Auditing 사용

pk가 아닌 다른 필드로 새로운 엔티티인지 확인하는 조건을 설정할 수 있다.

엔티티가 Persistable를 구현하도록 해야한다. Persistable의 파라미터로 pk의 타입을 넣는다.

그리고 BaseEntity에서 사용하는 것처럼 Auditing을 사용한다.
@EntityListeners(AuditingEntityListener.class) 를 붙여서 Auditing을 사용할 수 있도록 한다.

어플리케이션에서 Auditor를 인식하기 위해 빈을 등록한다.

	@Bean
	public AuditorAware<String> auditorProvider(){
		return () -> Optional.of(UUID.randomUUID().toString());
	}

생성 일자를 조건으로 사용하기 위해 createdTime 필드를 만들고 그 위에 @CreatedDate 어노테이션을 붙인다. 이를 통해 새로운 엔티티를 persist할 때 자동으로 createdTime을 채울 수 있다.

Persistable 인터페이스를 상속하면 오버라이드된 getter와 isNew 메서드를 구현해야한다. createdDate를 조건으로 사용하기 위해 isNew 메서드가 createdDate가 null이면 참을 반환하도록 한다.

// Persistable, Auditing
@EntityListeners(AuditingEntityListener.class)
@Entity
@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 null;
    }

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

이제 save에서 이 조건을 통해 createdDate가 null이면 persist()를, 아니라면 merge()를 호출하게 된다.

이처럼 DB에서 조회하지 않고 식별자가 null(기본타입이면 0)인지 확인하여 새로운 엔티티인지 판단할 수 있었다.
그래서 효율적으로 save() 메서드를 수행할 수가 있다.

profile
티스토리로 블로그 이전합니다. 최신 글들은 suhsein.tistory.com 에서 확인 가능합니다.

0개의 댓글