@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;
}
/*
생략
*/
}
@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 쿼리가 실행됨을 확인할 수 있습니다.
IDENTITY 전략을 없애고 ID 값을 1로 고정 시킨 후에 해당 로직을 두 번 호출하면 어떻게 될까요?
첫 번째 호출 시
두 번째 호출 시
영속성 컨텍스트는 식별자(Id 값)를 기준으로 저장하기에 첫 번째 호출 시에 insert를 진행하고, 두 번째 호출 시에는 저장된 값을 가져와서 사용하여 UUID.random 호출을 통해 이름이 바뀌었으므로 update문이 호출 되는 것을 확인할 수 있습니다.
@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 어노테이션이 기본적으로 적용되어 있기 때문입니다.
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 값을 가지게 됩니다.
[ Spring ] Swagger 의 basic path 변경 (0) | 2023.08.25 |
---|---|
JPQL과 Querydsl 차이 (0) | 2022.04.20 |
[ 스프링 데이터 JPA ] 네이티브 쿼리 사용하기 (0) | 2022.04.14 |
상속 관계에서 @Builder 적용 (0) | 2022.03.24 |
@AllArgsConstructor 유의사항 (0) | 2022.03.12 |