포스트

2022-05-24-TIL

2022-05-24-TIL

Today I Learned

JPA 연관관계

JPA 연관관계 매핑은 객체의 참조와 테이블의 외래 키를 연결하는 작업이다. 객체는 양쪽에서 서로를 참조할 수 있지만 테이블에서는 외래 키 하나로 관계가 표현되므로, 양방향 관계에서는 둘 중 어느 쪽이 외래 키를 관리할지 정해야 한다. 외래 키를 가진 쪽이 연관관계의 주인이고, 반대쪽은 mappedBy로 주인을 가리키며 읽기만 한다. 주인이 아닌 쪽 컬렉션에만 값을 넣으면 DB에 반영되지 않는다는 점이 자주 하는 실수다.

  • https://cjw-awdsd.tistory.com/47

JPA 복합키 매핑: @EmbeddedId vs @IdClass

테이블의 기본 키가 여러 컬럼으로 이루어져 있으면 JPA에서는 식별자 클래스를 따로 만들어 매핑한다. 방법은 두 가지다.

  • @IdClass: 엔티티에 @Id 필드를 여러 개 두고, 같은 이름의 필드를 가진 식별자 클래스를 @IdClass로 지정한다. JPQL에서 e.orderId처럼 필드를 바로 쓸 수 있다.
  • @EmbeddedId: @Embeddable 식별자 클래스를 엔티티의 필드 하나로 둔다. JPQL에서는 e.id.orderId처럼 한 단계를 거친다. 식별자를 하나의 값 객체로 다룬다는 점이 더 객체지향적이다.

어느 쪽이든 식별자 클래스는 Serializable을 구현하고, 기본 생성자를 가지며, equals()와 hashCode()를 구현해야 한다. 영속성 컨텍스트가 식별자로 엔티티를 찾기 때문에 동등성 비교가 올바르지 않으면 같은 행을 다른 엔티티로 취급한다. 제목의 @Embedded는 값 타입 필드에 쓰는 애너테이션이고, 복합 키에는 @EmbeddedId를 쓴다.

  • https://www.baeldung.com/spring-jpa-embedded-method-parameters
  • https://velog.io/@cham/JPA-%EB%B3%B5%ED%95%A9-%ED%82%A4-%EB%A7%A4%ED%95%91%ED%95%98%EB%8A%94-%EB%B2%95
  • https://www.baeldung.com/jpa-composite-primary-keys

JPA FK 중복에러

같은 물리 컬럼을 두 개 이상의 매핑이 쓰려고 하면 Hibernate가 매핑 단계에서 에러를 낸다. 예를 들어 team_id 컬럼을 @Column으로 매핑한 필드와 @JoinColumn(name = "team_id")로 매핑한 연관관계 필드가 동시에 있으면 어느 쪽이 값을 쓸지 정할 수 없다. 둘 다 필요하다면 한쪽에 insertable = false, updatable = false를 주어 읽기 전용으로 만든다.

  • https://parksrazor.tistory.com/218

JPA Join Table

JPA에서 조인 테이블을 통해서 연관관계를 매핑하려고 할 때, id만 갖고있는 테이블이면 @JoinTable로 매핑하고 별도의 엔티티는 정의하지 않아도 된다. 하지만 연관관계 테이블(조인 테이블)에 여러가지 필드를 포함하고 있는 경우에는 엔티티로 선언을 할 필요가 있는데, 이 때 @JoinTable로 매핑해두면 중복 키 에러가 발생하여 실행이 안된다.

1
2
3
4
5
6
@JoinTable(
    name = "post_comment_ref",
    joinColumns = @JoinColumn(name = "post_id"),
    inverseJoinColumns = @JoinColumn(name = "post_comment_id")
)
private List<PostComment> comments = new ArrayList<>();

@JoinTable은 조인 테이블을 JPA가 직접 관리하는 숨은 테이블로 취급한다. 그런데 같은 테이블을 엔티티로도 선언하면, 한 테이블의 컬럼을 두 매핑이 각각 소유하게 되어 앞의 FK 중복 에러와 같은 상황이 된다. Hibernate의 DuplicateMappingException(“physical column name referred to by multiple logical column names”)이 이런 경우에 나타나는 에러 중 하나다.

정리하면 선택지는 둘이다. 조인 테이블에 키 외의 컬럼이 없으면 @ManyToMany와 @JoinTable만으로 충분하다. 생성일, 순서, 상태 같은 컬럼이 있으면 조인 테이블을 엔티티(PostCommentRef)로 승격하고, 양쪽에서 @OneToMany(mappedBy = ...)로 그 엔티티를 참조한다. 이 경우 @JoinTable 매핑은 지운다.

  • https://parkhyeokjin.github.io/jpa/2019/10/28/JPA-chap6.html
  • https://stackoverflow.com/questions/5478328/in-which-case-do-you-use-the-jpa-jointable-annotation
  • https://velog.io/@sa1341/JPA-%EC%A1%B0%EC%9D%B8-%EC%BB%AC%EB%9F%BC-%EB%B0%8F-%EC%A1%B0%EC%9D%B8-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
  • https://stackoverflow.com/questions/61510176/duplicatemappingexception-table-contains-physical-column-name-referred-to
  • https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#bnbqf
  • https://www.baeldung.com/hibernate-many-to-many

One to One

@OneToOne은 보통 한쪽 테이블에 외래 키를 두고 @JoinColumn으로 매핑한다. 외래 키를 어느 테이블에 둘지는 조회 패턴으로 정한다. 주 테이블에 두면 주 엔티티만 조회해도 대상 존재 여부를 알 수 있고, 대상 테이블에 두면 나중에 관계가 1:N으로 바뀌어도 테이블 구조를 유지할 수 있다.

조인 테이블로 1:1을 표현할 수도 있다. 이때는 @OneToOne에 @JoinTable을 붙이고, 조인 테이블의 두 컬럼에 각각 유니크 제약이 있어야 실제로 1:1이 보장된다. 링크 중 하나는 이 매핑이 기대대로 동작하지 않는 사례를 다룬다.

또 하나 알아 둘 점은, 외래 키를 갖지 않은 쪽(mappedBy 쪽)의 @OneToOne은 지연 로딩을 지정해도 즉시 로딩될 수 있다는 것이다. 프록시를 만들려면 대상이 존재하는지 알아야 하는데, 외래 키가 없는 쪽에서는 그것을 알기 위해 결국 조회가 필요하기 때문이다.

  • https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/one-to-one-join-table.html
  • https://stackoverflow.com/questions/32317162/why-does-my-onetoone-mapping-through-a-jointable-not-work
  • http://www.java2s.com/Tutorials/Java/JPA/0720__JPA_OneToOne_Join_Column.htm
  • https://www.baeldung.com/jpa-one-to-one
  • https://stackoverflow.com/questions/39033485/one-to-one-broken-column-mapping-for-id-in-jpa
참고한 자료외부 출처 1

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다