JPA

[JPA] 연관관계 매핑

리승자이 2022. 8. 4. 22:14
728x90

깃허브 바로가기
엔티티들은 대부분 다른 엔티티와 연관이 있다.

연관관계 매핑 핵심 키워드

  • 방향 : 객체관계에서만 존재하고 실제 테이블 관계는 항상 양방향
    • 단방향 : 두 엔티티중 하나만 참조하는 것이
    • 양방향 : 두 엔티티가 서로 참조하는 것
  • 다중성 : 다대일(N:1), 일대일(1:1), 일대다(1:N), 다대다(N:M)
  • 연관관계의 주인 : 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.

단방향 연관관계

Member와 Team이 있다고 가정할 때, 팀은 멤버를 여러명 가질 수 있지만, 멤버는 하나의 팀만 가질 수 있는 구조이다. 이것이 다대일관계이다.

public class Member {
    private String id;
    private String name;
    private Team team;
}

public class Team {
    private String id;
    private String name;
}

이런 관계로 있다고 한다면 멤버 객체와 팀 객체는 단방향 관계이다.
멤버는 Member.team 필드를 통해 팀을 알 수 있는데에 반해, 팀은 회원을 알 수가 없는 구조이다.

다시말해,
Get메소드를 추가한다면 member.getTeam() 으로 가능한데 반대는 불가하다는 것이다.

참조를 통한 연관관계는 언제나 단방향 구조이다. 이 객체간의 연관관계를 양방향으로 하고 싶다면 반대에도 필드를 추가해줘서 참조를 보관해야 한다.

그렇게 하려면 위 구조에서 Team을 아래와 같이 바꿔야 한다.

public class Team {
    private String id;
    private String name;
    private Member member;
}

객체는 양방향을 만드려면 서로 참조하게끔 단방향 연관관계를 2개 만들어야 하고, 테이블은 JOIN을 사용하기 때문에 외래 키를 사용하는 테이블의 연관관계는 양방향이다.

JPA매핑

멤버와 팀 객체들을 JPA를 사용하여 매핑해보면 아래와 같다.

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private String id;

    private String name;

    //연관관계 매핑
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

@Entity
public class Team {
    @Id
    @Column(name = "TEAM_ID")
    private String id;

    private String name;
}
  • 객체 연관관계 : 멤버 객체의 Member.team 필드 사용

  • 테이블 연관관계 : 멤버 테이블의 MEMBER.TEAM_ID 외래키 컬럼을 사용

  • @ManyToOne

    • 이름처럼 다대일 관계라는 매핑 정보들 함축하고 있다. 멤버와 팀은 다대일 관계이다.
      연관관계를 매핑할때 다중성을 나타내는 어노테이션을 필수로 사용해야 한다.
  • @JoinColumn(name = "TEAM_ID") - 외래키를 매핑할 때 사용

    • name속성에는 매핑할 외래 키 이름을 지정. 멤버와 팀 테이블은 TEAM_ID 외래 키로 연관관계를 맺으므로 이 값을 지정하여 맞춰주면 된다. 근데 이 어노테이션은 생략 가능하다.

@JoinColumn의 속성

속성 기능 기본값
name 매핑할 외래 키 이름 필드명 + _ + 참조하는 테이블의 기본키 컬럼명
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본키 컬럼명
foreignKey(DDL) 외래 키 제약조건을 직접 지정할 수 있다.
이 속성은 테이블을 생성할 때만 사용한다.
unique
nullable
insertable
updatable
columnDefinition
table
@Column의 속성과 같다.

@ManyToOne의 속성

속성 기능 기본값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다. true
fetch 글로벌 페치 전략을 설정한다. • @ManyToOne = FetchType.EAGER
• @OneToMany = FetchType.LAZY
cascade 영속성 전이 기능을 사용한다.
targetEntity 연관된 엔티티의 타입 정보를 설정한다.
이 기능은 거의 사용하지 않는다.
컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.

연관된 엔티티 삭제

연관된 엔티티를 삭제하려면 기존에 있던 연관관계들을 먼저 제거하고 삭제해야 한다.
그렇지 않으면 외래키 제약 조건으로 DB에서 오류가 발생한다.
팀을 삭제하려면 팀에 소속된 멤버 연관관계를 먼저 전부 제거해야한다.
그런 뒤에 em.remove(team)을 해줘야 한다.

양방향 연관관계

아까 말했듯 팀은 여러 멤버를 가질 수 있다. 그렇기 때문에 컬렉션을 사용해야한다.
JPA는 List를 포함해서 Collection, Set, Map 같은 다양한 컬렉션을 지원한다.

  • 멤버 → 팀 (Member.team)
  • 팀 → 멤버 (Team.members)

DB 테이블은 외래 키 하나로 양방향 조회가 가능하기 때문에 따로 설정해줄 것은 없다.

@Entity
public class Team {
    @Id
    @Column(name = "TEAM_ID")
    private String id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
}

이렇게 설계하여도 테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.

엔티티를 양방향의 연관관계로 설정하면 객체 참조는 둘인데 외래키는 하나이다.
그래서 둘 사이에 차이가 발생
이러한 이유로 JPA에서는 연관관계중 하나를 정해 테이블의 외래키를 관리해야 한다.
이것을 연관관계의 주인이라고 한다.

  • 주인은 mappedBy 속성을 사용하지 않는다.
  • 주인이 아니라면 mappedBy 속성을 사용하여 속성의 값으로 연관관계의 주인을 지정해야 한다.

그래서 테이블에 외래 키가 존재하는 테이블에 연관관계 관리자 역할을 주어야한다.

주의점

양방향 연관관계는 연관관계의 주인이 외래 키를 관리하기 때문에 주인이 아닌 방향은 값을 설정하지 않아도 DB에 외래 키 값이 정상 입력된다.

그렇기 때문에 주인이 아닌곳에서만 값을 할당한다면 null을 반환하게 되는 결과를 초래한다.
이 부분을 굉장히 주의깊게 사용하여야 할 것이라고 생각한다.

객체까지 고려하여 주인이 아닌 곳에도 값을 할당해줘야 추후에 테스트 코드를 작성했을때 size 0인것을 반환하는 오류를 막을 수 있다.

결론 : 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자

정리

양방향 매핑은 단방향 매핑에 비해 복잡하다. 복잡한 것에 비해 양방향의 장점은 반대방향으로 객체 그래프 탐색 기능이 추가된 것뿐이다.

  • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
  • 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐색 기능이 추가된다.
  • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.

그리고 양방향 매핑 시에는 무한루프에 빠지지 않게 조심해야 한다. 서로가 서로를 무한으로 호출하는지 생각하면서 사용하자.

728x90