본문 바로가기

JPA

JPA 연관관계 매핑 2. 일대다, 다대일, 일대일, 다대다 연관관계 매핑

연관관계 매핑

연관관계 매핑 시 고려해야 할 3가지가 다중성, 단방향 or 양방향, 연관관계의 주인이라고 저번 게시글에 작성하였다.

 

 

  • 이 게시글에서는 다중성에 대해 알아본다.
  • JPA에서는 연관관계를 맺을 때 다중성을 annotation으로 표기한다.
  • @ManyToMany, @ManyToOne, @OneToOne, @OneToMany으로 이름만 봐도 명확하게 이해가 가능하다.

 


일대일(@OneToOne)

  • 일대일 연관관계 매핑은 저번 게시글에서 다룬 거처럼 외래 키를 원하는 곳에 두면 된다.

다대일(@ManyToOne)

  • 다대일의 반대는 일대다이다. 그러므로 다대일 매핑을 하면 상대편 객체에서 일대다로 매핑을 하는 것과 같다.
  • 실제 테이블에서는 다 쪽이 외래 키를 가지게 된다.
  • 그러므로 다대일 매핑을 하는 게 명확하게 대상 객체에 값을 넣는 것을 인지할 수 있으므로 많이 사용된다.
// Lombok Annotation은 따로 생략했습니다.
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "member")
    private List<Board> boards = new ArrayList<>();
}

@Entity
public class Board {

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

    private String title;

    private String content;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;
}
  • 위의 예제는 Member는 Board를 많이 가질 수 있고 각 Board는 Member를 한 명만 가질 수 있으므로 Board 입장에서는 다대일 관계이다.
  • 그러므로 Board에서 다대일 매핑을 하였다.
  • 실제 테이블에서도 Board가 외래 키를 가지게 되므로 연관관계 주인이 실제 외래키를 가지게 된다.
Hibernate:

create table Board (
   board_id bigint not null,
    content varchar(255),
    title varchar(255),
    member_id bigint,
    primary key (board_id)
)
Hibernate:

create table Member (
   member_id bigint generated by default as identity,
    name varchar(255),
    primary key (member_id)
)
Hibernate:

alter table Board
   add constraint FK3hi0ewxk20gygxyqrp5t5vudr
   foreign key (member_id)
   references Member

 


일대다(@OneToMany)

  • 위의 예제에서 Member를 연관관계의 주인으로 잡으면 일대다 매핑이 된다.
  • 하지만 테이블에서는 1:N 관계에서 N에게 외래 키가 생긴다.
  • 그렇기 때문에 일대다 매핑을 하게 되면 특이한 구조가 된다.
  • 외래 키를 관리하는 테이블이 반대편이므로 추가 Update SQL이 날아간다.
  • 일대다 매핑은 연관관계의 주인인으로 잡지 말고 항상 다대일 매핑으로 하자

다대다(@ManyToMany)

  • 관계형 데이터베이스는 정규화된 테이블 두 개로 다대다 관계를 표현할 수 없다.
  • 그렇기 때문에 따로 테이블을 만들어 풀어낸다.
@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;


    private String name;

    @ManyToMany(mappedBy = "members")
    private List<Team> teams = new ArrayList<>();
}

@Entity
public class Team {

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

    private String teamName;


    @ManyToMany
    @JoinColumn(name = "member_id")
    private List<Member> members = new ArrayList<>();

}

 

Hibrnate:

create table Member (
   member_id bigint generated by default as identity,
    name varchar(255),
    primary key (member_id)
)
Hibernate:

create table Team (
   team_id bigint not null,
    teamName varchar(255),
    primary key (team_id)
)
Hibernate:

create table Team_Member (
   teams_team_id bigint not null,
    members_member_id bigint not null
)
  • Member와 Team을 다대다 관계로 매핑을 하였다.
  • 위에서 설명했듯이 관계형 DB에서는 아래와 같이 따로 테이블을 생성하는 것을 알 수 있다.
  • 객체에서는 두 개지만 실제 DB에서는 3개의 테이블로 관리되면 유지보수가 힘들어진다.
  • 그러므로 다대다 매핑을 하기보다는 중간에 연결할 Entity 하나를 생성하고 일대다 - 중간 Entity - 다대일 이렇게 관계를 맺는 게 좋다.

 

중간 Entity 만들기

  • MemberTeam이라는 중간 Entity를 만들었다.
  • 그리고 거기에서 각 Member, Team을 다대일 관계로 매핑하였다
  • 실제 테이블 생성은 위와 비슷하게 생겨날 것이다.
  • 하지만 명시적으로 MemberTeam 객체가 존재하므로 혼란이 적어진다.
  • 그러므로 다대다 관계보다는 중간 Entity를 만들어 일대다 - 다대일로 관계를 맺자.
@Entity
public class Team {

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

    private String teamName;

    @OneToMany(mappedBy = "team")
    private List<MemberTeam> memberTeams = new ArrayList<>();

}

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;


    private String name;

    @OneToMany(mappedBy = "member")
    private List<MemberTeam> memberTeams = new ArrayList<>();
}

@Entity
public class MemberTeam {

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

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

}

 


결론

  • 일대일 관계는 어느 쪽에 연관관계 주인을 두던지 상관이 없다.
  • 다대일 관계는 연관관계 주인인 다 쪽에서 외래 키를 관리하므로 명확하다.
  • 일대다 관계는 사용하지 말고 반대로 다대일 관계로 풀어서 사용하자
  • 다대다 관계는 사용하지 말고 일대다 - 중간 Entity - 다대일 관계로 풀어서 사용하자.

참고도서