인프런 커뮤니티 질문&답변

단무지님의 프로필 이미지
단무지

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

식별관계 매핑 오류 질문 드립니다!

해결된 질문

작성

·

2.3K

1

안녕하세요. 강사님

선생님 책이랑 강의 영상들을 보고 응용 실습하였는데, 오류를 이틀째 검색해도 해결을 못하였느데 혹시나 아시나 해서 글 남겨봅니다! ㅠㅠ

@Entity
@Data
public class Posts {

  @Id
  private Long id;

  private String title;
}
@Entity
@Data
public class PostsDetail {

  @Id
  private Long id;

  @MapsId
  @OneToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "id")
  private Posts posts;

  private String description;
}
  @Test
  void t1() {
    Posts posts = new Posts();
    posts.setId(1L);
    postsRepository.save(posts);

    PostsDetail postsDetail = new PostsDetail();
    postsDetail.setPosts(posts);
    postsDetailRepository.save(postsDetail);
  }


오류 메세지: A different object with the same identifier value was already associated with the session Posts
입니다.


추가) 여러가지 실험중에

postsRepository.save(posts);를 하고 바로

entityManaget.clear()를 해주면 값이 들어값니다..
entityManaget.flush()를 하면 안 들어가고요. 오히려 flush를 해줘야 값이 들어가야하는 게 아닌가요? ㅠㅠ

답변 1

5

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 참치캔님

결론을 말씀드리자면 코드를 다음과 같이 수정해야 합니다. save(posts)의 응답값을 사용하는 부분을 주목해주세요.

    @Test

    @Commit

    void t1_resolve() {

        Posts posts = new Posts();

        posts.setId(1L);

        Posts mergedPosts = postsRepository.save(posts);

        PostsDetail postsDetail = new PostsDetail();

        postsDetail.setPosts(mergedPosts);

        postsDetailRepository.save(postsDetail);

    }

이 문제는 사실 스프링 데이터 JPA를 사용했기 때문에 발생했습니다. 스프링 데이터 JPA를 사용하지 않고 EntityManager를 직접 사용하셨으면 발생하지 않았을거에요.

스프링 데이터 JPA의 save()는 식별자가 null이면 em.persist()를 호출하고, 식별자가 null이 아니면 em.merge()를 호출합니다.

그런데 @Id를 직접 할당하는 전략을 사용하고 있기 때문에 다음 로직을 호출하는 시점에 이미 식별자가 존재합니다.

postsRepository.save(posts);

따라서 em.persist()가 아니라 em.merge()가 호출됩니다.

em.persist()를 호출하면 해당 객체가 바로 영속화가 됩니다. 그런데 em.merge()는 파라미터로 넘긴 값은 영속화가 되지 않습니다. 대신에 반환 값이 영속화 되어 있습니다.

이 코드에서

Posts mergedPosts = em.merge(posts);

mergedPosts는 영속화 되어 있지만, posts는 단순히 파라미터로 사용되고, 영속되 되지 않습니다.

따라서 merge를 호출한 경우 영속화 되어 있는 응답값을 사용해야 합니다.

postsDetail.setPosts(mergedPosts); 부분에서 영속화된 상태의 mergedPosts를 넣어주면 정상 동작하게 됩니다. 이전에는 결과적으로 영속화 되지 않은 객체를 넣어주었던 것이지요.

@Id를 직접 할당하는 전략을 사용한다면 스프링 데이터 JPA가 제공하는 Persistable 인터페이스를 구현해서 최초에 생성된 객체인지 아닌지 스프링 데이터 JPA에 알려주어야 합니다. 이 부분은 Persistable로 검색해보시면 방법을 찾으실 수 있을거에요.

그런데 단순하게 Posts의 식별자 전략을 @Id @GeneratedValue로 적용하시면 이런 고민이 필요없이, 기존 코드에서도 모든 문제가 깔끔하게 해결됩니다.

도움이 되셨길 바래요.

단무지님의 프로필 이미지
단무지
질문자

정말 감사합니다 도움이 정말 많이 됐습니다 감사합니다!!! 

단무지님의 프로필 이미지
단무지

작성한 질문수

질문하기