Lined Notebook

[Spring Data JPA] 14. JPA 일대다 연관관계

by ymkim

✔ 일대다 매핑을 이해하기 위한 다대일 양방향 매핑

  • 위 사진을 보면 팀과 맴버가 다대일 양방향 관계이다.
  • 현재는 Team도 members를 가지고 있고, Member 역시 team을 가지고 있다.
  • 하지만
    • 위와 같은 이유로 인해 아래 사진과 같은 연관관계 설계가 나올 수 있다.
    • 일대다 단방향 매핑 관계
    • 객체 입장에서 생각하면 충분히 나올 수 있는 설계다.
  • Member 입장에서는 Team을 참조하지 않아도 되는 설계가 나올 수 있다.

✔ 일대다 단방향 매핑

위에서 본 관계와 비교하여 화살표 방향이 Team –> Member로 반대가 된것을 잘 봐야한다.

  • 일대다 관계에서는 (1)이 연관관계의 주인이다.
  • 일(1) 쪽에서 외래키를 관리하겠다는 의미가 된다.
  • 일대다 관계는 JPA 표준스펙에서는 지원을 하지만 실무에서 해당 모델은 권장하지 않는다.
  • 일대다 단방향 연관관계는 반드시 @JoinColumn을 사용해야 한다.
    • 사용하지 않는 경우 JoinTable 방식을 사용한다.
    • Team_Member라는 중간 테이블이 생성된다.

💡 일대다 단방향 매핑의 문제점 01

  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래키가 있다.
  • 객체와 테이블의 패러다임 차이 때문에 객체의 반대편 테이블에 외래키를 관리하는 특이한 구조.
  • 현재 TEAM과 MEMBER 테이블 중 외래키는 MEMBER 테이블에 존재하는 상황이다.
  • 아래 예제를 한번 살펴보자.

Member 엔티티

@Entity
public class Member {

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

    @Column(name = "USERNAME")
    private String username; //Member에는 일대다 단방향 관계기에 별다른 제스처가 없다

}

Team 엔티티

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

    @OneToMany
    @JoinColumn(name = "TEAM_ID") //@JoinColumn을 통해 TEAM_ID를 연관관계의 주인으로 설정
    private List<MemberSample> members = new ArrayList<>();
}

JPAMain 클래스

try {
    Member member = new Member();
    member.setUsername("member1");

    em.persist(member); // 영속성 컨텍스트 -> 맴버 등록

    System.out.println("====맴버 등록===="); // 팀 등록 전

    Team team = new Team();
    team.setName("teamA");

    team.getMembers().add(member); // 여기가 문제가 된다

    em.persist(team);

    System.out.println("====팀 등록====");

    tx.commit();
} catch (Exception e) {
    tx.rollback();
} finally {
    em.close();
}
  • 현재 Team 객체의 member를 업데이트하고 있는데, 위에서 말했다시피 외래키는 MEMEBER 테이블에 존재한다.

💡 일대다 단방향 매핑의 문제점 02

JPAMain 클래스 구동 결과

Hibernate:
    /* insert hello.jpa.Member
        */
        insert
        into
            Member
            (id, age, createdDate, description, lastModifiedDate, roleType, name)
        values
            (null, ?, ?, ?, ?, ?, ?)
-----멤버 저장
Hibernate:
    /* insert hello.jpa.Team
        */
        insert
        into
            Team
            (id, name)
        values
            (null, ?)
-----팀 저장
Hibernate:
    /* create one-to-many row hello.jpa.Team.members */
    update
        Member
    set
        TEAM_ID=?
    where
        id=?
  • 팀 엔티티를 수정하였는데 맴버 테이블에 업데이트 쿼리가 나가는 현상 발생.
  • 실무에서는 테이블이 수십개가 엮여서 돌아가는 상황, 위와 같은 상황은 운영을 힘들게 한다.

💡 해결 방안

  • 객체지향적으로 조금 손해를 볼 수 있지만, 다대일 단방향, 양방향을 사용한다.
  • 즉, 먼저 학습한 , 필요한 경우 양방향 매핑을 통해 해결한다.
  • 다대일 단방향 관계로 매핑하고
  • 객체 입장에서 보면, 반대방향으로 참조할 필요가 없는데 관계를 하나 만드는 것이지만, DB의 입장으로 설계의 방향을 조금 더 맞춰서 운영상 유지보수하기 쉬운 쪽으로 선택할 수 있다.

✔ 일대다 양방향

  • JPA 표준 스펙이 제공하는것은 아니지만, 일대다 양방향 매핑 역시 존재한다.
//수정 전: 이런식으로 매핑을 하면 연관관계의 주인이 2개가 된다.
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

//수정 후: 읽기 전용으로 변경하여, 양방향 매핑을 한 효과를 만들어낸다.
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private Team team;

🚀 결론

  • 일대다 단방향 매핑의 단점
    • 엔티티가 관리하는 외래키가 다른 테이블에 있음
    • 연관관계 관리를 위해 추가로 UPDATE SQL 실행. (성능 문제)
  • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.
    • 객체간의 참조를 하나 더 넣는 한이 있더라도 다대일 사용.

참고 자료

블로그의 정보

기록하고, 복기하고

ymkim

활동하기