프로그래밍/Spring

[ JPA ] JPA의 save 시에 여러 케이스 알아보기

Yanoo 2024. 3. 10. 18:43
728x90
반응형

 

Case 1

  • Member Class
@Entity
@Table
@Setter
@Getter
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    
    protected Member() {
    }

    public Member(String name) {
        this.name = name;
    }
    
    
    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    
    /*
    생략
    */
}
  • MemberService
@Transactional
public String test() {
    Member unsavedMember = new Member(UUID.randomUUID().toString());
    System.out.println("unsavedMember.getId() = " + unsavedMember.getId());
    System.out.println("==============================");
    Member member = memberRepository.save(unsavedMember);
    System.out.println("==============================");
    System.out.println("member.getId() = " + member.getId());
    System.out.println("==============================");
    
    return member.getId().toString();
}

다음 Case의 쿼리는 언제 실행될까?

 

결과

일반적으로 JPA 공부를 진행했다면 @Transactional 이 걸려있는 메소드 끝에 쿼리가 실행될 것으로 예상을 할 것입니다.

하지만 결과를 보면

메소드가 끝나는 시점이 아닌

Member member = memberRepository.save(unsavedMember);

호출 시점에 insert 하는 것을 알 수 있습니다.

이유는 Member 클래스의

GeneratedValue 전략인 IDENTITY 속성 때문인데요,

이 전략은 pk 생성을 DB에 의존하는 형식인데 영속성 컨텍스트에 저장하기 위해서 id 값이 필요하기에 호출 시점에 insert를 진행하여 영속성 컨텍스트에 저장하게 됩니다.

그렇다면 IDENTITY 전략을 없애고 직접 부여하면 메소드 끝날 때 insert를 진행할까요?

 

결과를 보면

처음 예상한대로 메소드 끝나는 시점에 insert 쿼리가 실행됨을 확인할 수 있습니다.

 

 

Case 2

IDENTITY 전략을 없애고 ID 값을 1로 고정 시킨 후에 해당 로직을 두 번 호출하면 어떻게 될까요?

 

결과

첫 번째 호출 시

 

두 번째 호출 시

 

영속성 컨텍스트는 식별자(Id 값)를 기준으로 저장하기에 첫 번째 호출 시에 insert를 진행하고, 두 번째 호출 시에는 저장된 값을 가져와서 사용하여 UUID.random 호출을 통해 이름이 바뀌었으므로 update문이 호출 되는 것을 확인할 수 있습니다.

 

Case 3

@Transactional 생략 시

// @Transactional => 생략
public String test() {
    Member unsavedMember = new Member(1L, UUID.randomUUID().toString());
    System.out.println("unsavedMember.getId() = " + unsavedMember.getId());
    System.out.println("==============================");
    Member member = memberRepository.save(unsavedMember);
    System.out.println("==============================");
    System.out.println("member.getId() = " + member.getId());
    System.out.println("==============================");
    
    return member.getId().toString();
}

 

결과

save 호출 시에 저장되는 것을 확인할 수 있습니다. 여기서 save가 호출되는 이유는 jpaRepository save 메소드에 @Transactional 어노테이션이 기본적으로 적용되어 있기 때문입니다.

 

 

Case 4

Id를 primitive type인 long으로 변경 후 로직 호출

@Transactional
    public String test() {
//        Team team = new Team("team1");
//        teamRepository.save(team);
        Member unsavedMember1 = new Member(0L,"이름1");
        Member unsavedMember2 = new Member(1L,"이름2");

        System.out.println("==============================");
        Member member = memberRepository.save(unsavedMember1);
        System.out.println("===============");
        Member member2 = memberRepository.save(unsavedMember2);

        System.out.println("==============================");

        Member unsavedMember3 = new Member(0L,"이름2");
        Member member3 = memberRepository.save(unsavedMember3);
        System.out.println("==============================1");
        Member unsavedMember4 = new Member(1L,"이름3");

        System.out.println("==============================");

        Member member4 = memberRepository.save(unsavedMember4);

        return member.getId().toString();
    }

 

결과

0값으로 저장 시에 예외가 발생함을 알 수 있습니다.

그 이유는 id 값을 primitive 타입으로 결정했을 때 영속성 컨텍스트에 값이 있는지 없는지의 기준은 0이됩니다.(영속성 컨텍스트에 들어가기 위해선 id 값이 필요합니다.)

그렇기에 0값으로 영속성 컨텍스트에 들어가지 못하고 db에도 0값으로 insert를 하지 못하게 됩니다.

좀 더 알아보기 위해서

IDENTITY 전략을 활성화 시킨 후에 호출해봅니다.

이 때는 정상적으로 처리됨을 알 수 있습니다.

이유는 0값이면 없다는 것이 되므로 insert가 진행되어 할당 받은 id값은 1입니다.

 

여기서 추가적으로 저는 insert 쿼리가 3번 나가지 않을까 예상을 했습니다.

Member member = memberRepository.save(unsavedMember1); // 1번

Member member2 = memberRepository.save(unsavedMember2); // 2번

Member member3 = memberRepository.save(unsavedMember3); // 3번

 

하지만 2번 밖에 발생하지 않았는데요, 그 이유는 역시 IDENTITY 전략에 있습니다.

1번 호출 시에 insert를 하게 되면 IDENETY 전략에 의해서 id값을 1번으로 할당 받게 됩니다.

그렇기에 로직상 1L로 save하는 로직은 첫 번째 엔티티에 해당하게 되는 것입니다.

결과적으로 0값으로 save를 수행하는 로직만 insert를 하게 되고

1로 할당된 엔티티는 마지막 save 값인 이름3이라는 name 값을 가지게 됩니다.

 

 

728x90
반응형