Spring/SpringDataJPA

새로운 엔티티를 구별하는 방법

느리지만 꾸준하게 2022. 4. 18. 19:17
  • 새로운 엔티티를 판단하는 기본 전략
  • 식별자가 객체일 때 `null`로 판단
  • 식별자가 자바 기본 타입일 때 `0`으로 판단
  • `Persistable` 인터페이스를 구현해서 판단 로직 변경이 가능

 

 

Item Class를 하나 만들고

// Item Class

package study.datajpa.entity;


import lombok.Getter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@Getter
public class Item {

    @Id
    @GeneratedValue
    private Long id;


}

 

ItemRepository Interface도 만들어주고

package study.datajpa.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Item;

public interface ItemRepository extends JpaRepository<Item, Long> {
}


MemberController에서 아래 PostConstruct를 주석처리 해주고

// MemberController Class

//    @PostConstruct
    public void init() {
        for (int i = 0; i < 100; i++) {
            memberRepository.save(new Member("user" + i, i));
        }
    }
}

 

test를 돌리면 영상과 같이 debug결과가 나오지 않는다. 

해결 SimpleJpaRepository에서 suspend point를 찍어줘야 한다.

 

// ItemRepositoryTest


package study.datajpa.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import study.datajpa.entity.Item;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class ItemRepositoryTest {

    @Autowired
    ItemRepository itemRepository;

    @Test
    public void save() {
        Item item = new Item();
        itemRepository.save(item);
    }
}
// MemberTest에서도 아래 3개를 주석처리 해주고

// then
        System.out.println("findMember.createdDate = " + findMember.getCreatedDate());
//        System.out.println("findMember.updatedDate = " + findMember.getLastModifiedDate());
//        System.out.println("findMember.createdBy = " + findMember.getCreatedBy());
//        System.out.println("findMember.updatedBy = " + findMember.getLastModifiedBy());
    }

그러면 debug 결과값에서 id가 null인 것을 확인할 수 있다.

 

persist하고 entity를 return 하면 이제 id 값이 1이 되는 것을 확인 할 수 있다.

 

 

 

 

 

item Class를 작성해주고

// item Class

package study.datajpa.entity;


import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item {

    @Id
    private String id;


// pk의 값이 존재한다.
    public Item(String id) {
        this.id = id;
    }
}

ItemRepositoryTest를 작성해주고 돌리게되면

// ItemRepositoryTest

package study.datajpa.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import study.datajpa.entity.Item;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class ItemRepositoryTest {

    @Autowired
    ItemRepository itemRepository;

    @Test
    public void save() {
    
    // Item쪽으로 가보면 pk의 값이 존재
        Item item = new Item("A");
        itemRepository.save(item);
    }
}

ItemRepositoryTest에서 Test를 돌리고 

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

SimpleRepository에서 suspend point를 찍은 점에서 step over를 한번 누르게 되면 new Entity가 아니므로 id값이 a로 나오게 된다.

db에서 a를 찾고(없으면 새거라 판단하고 넣게된다.) insert문을 넣게 된다.

아무튼 merge는 최대한 쓰지말자

  • 데이터에 대한 변경은 변경감지로
  • 데이터에 대한 저장은 persist를 쓰자.
select item0_.id as id1_0_0_ from item item0_ where item0_.id='A';


insert 
    into
        item
        (id) 
    values
        (?)

 

Persistable이라는 implementation을 써보자.

// Item Class

package study.datajpa.entity;


import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.domain.Persistable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {

    @Id
    private String id;


    // JPA insert persist가 되기전에 호출이 된다.
    @CreatedDate
    private LocalDateTime createdDate;

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

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

    @Override
    public boolean isNew() {
        // 새거에 대한 로직을 직접 짜야한다.
//        return false;
        return createdDate == null;
    }
}

 

return entity할때 createdDate가 들어온다.(Jpa event안에서 동작)

 

정리를 하면

  • JPA 식별자 생성 전략이 @GeneratedValue면 save()호출 시점에 식별자가 없고 새로운 엔티티로 인식해서 정상 동작을 한다.
  • 그런데 JPA 식별자 생선 전략이 @Id만 사용해서 직접할당이 되면
  • 식별자 값이 있는 상태로 save()를 호출
  • 이 경우 merge()가 호출

 

  • merge()는 우선 DB를 호출해서 값을 확인 => DB에 값이 없다 그러면 새로운 엔티티로 인지 => 매우 비효율적
  • Persistable을 사용해서 엔티티 확인 여부를 직접 구현하는게 효과적
  • 등록시간@CreatedDate을 조합해 사용하면 여기 필드로 새로운 엔티티 여부를 편리하게 확인이 가능(@CreatedDate에 값이 없으면 새 엔티티로 판단.)

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 실전! 스프링 데이터 JPA >

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-%EC%8B%A4%EC%A0%84/dashboard

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.

www.inflearn.com