쌓고 쌓다

연관관계 매핑 @OneToMany, @ManyToOne 본문

프로그래밍/JPA

연관관계 매핑 @OneToMany, @ManyToOne

승민아 2023. 8. 2. 13:11

게시판이 여러 파일들을 업로드하여 관리하는 연관관계 매핑이 잘 안되어서

아주 흥미롭게 책을 펼쳐 정리해봤다.

 

연관관계 매핑시 3가지를 고려한다.

1. 다중성

다대일, 일대다, 일대일, 다대다가 있다.

 

2. 단방향, 양방향

테이블은 외래 키로 어떤 테이블에서든 두 테이블이 조인이 가능하니 양방향이 언제든 가능하다.

객체는 참조 필드를 가지는 객체만 조회가 가능하니, 한쪽만 있으면 단방향 양쪽에 있으면 양방향이다.

 

3. 연관관계 주인

DB는 외래 키 하나로 두 테이블이 연관관계를 맺을 수 있다. 이때 관리 포인트는 외래 키 하나로 한곳이다.

그런데 엔티티는 양방향으로 매핑했다면 관리 포인트가 두곳이 된다. A->B와 B->A.

JPA는 관리 포인트를 한곳을 정해 외래 키를 관리하는데 이 한곳이 연관관계 주인이된다.

 

 

여러 예제를 보기전에 DB 테이블의 구조를 보자.

Member 테이블의 team_id는 외래 키이며 Team 테이블의 id를 참조한다.

다중성에서 왼쪽이 연관관계의 주인이라고 정하고 아래의 예제들을 보자. (다대일은 다쪽이 주인, 일대다는 일이 주인)

 

@ManyToOne 단방향 

Member

public class Member {
    @Id @Column(name="id")
    private String id;

    @Column(name="username")
    private String username;

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

Member가 M(다)쪽으로 연관관계의 주인이다.

@JoinColumn으로 Member.team 필드를 외래 키 "team_id"와 매핑했다.

이제 Member.team 필드로 회원 테이블 "team_id" 외래 키를 관리한다.


Team

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

 

외래 키 저장 방법

Team team1 = new Team("team1", "팀1");
em.persist(team1);

Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);

저장 결과

 

@ManyToOne 양방향

Member

public class Member {

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

    @Column(name="username", nullable = false, length = 10)
    private String username;

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

 

Team

public class Team {
    @Id
    private String id;
    private String name;

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

주인이 아니면 mappedBy로 주인 필드의 이름을 값으로 입력한다.

주인이 아닌곳에서 외래 키 변경은 못하고 읽기만 가능하다.

 

양방향 조회 방법

Team team1 = em.find(Team.class, "team1");
Member member1 = em.find(Member.class, "member1");

List<Member> members = team1.getMembers();
for(Member m : members) {
    System.out.println("m = " + m.getUsername());
}

List<Member> members2 = member1.getTeam().getMembers();
for(Member m : members2) {
    System.out.println("m2 = " + m.getUsername());
}

 

@OneToMany 단방향

Team

public class Team {
    @Id
    private String id;
    private String name;

    @OneToMany
    @JoinColumn(name="team_id") // * MEMBER 테이블의 TEAM_ID (FK)
    private List<Member> members = new ArrayList<>();
}

일대다 단방향은 주의하자.

보통 키의 관리는 다쪽에서 한다.

그러나 Member에는 외래 키를 매핑할 수 있는 참조 필드가 없다.

Team의 members 필드에 외래 키를 매핑할 수 있는 참조 필드가 있다.

그래서 @JoinColumn의 name 속성의 값을 Member 필드의 외래 키로 작성한다.

 

Member

public class Member {
    @Id @Column(name="id")
    private String id;

    @Column(name="username", nullable = false, length = 10)
    private String username;
}

 

일대다 외래키 저장 방법

Member member1 = new Member("member1", "회원1");
em.persist(member1);

Member member2 = new Member("member2", "회원2");
em.persist(member2);

Team team1 = new Team("team1", "팀1");
team1.getMembers().add(member1);
team1.getMembers().add(member2);
em.persist(team1);

Team의 members에 멤버를 추가할때 UPDATE 쿼리가 발생한다.

본인 테이블에 외래 키가 없고 다른 테이블에 있으므로 한번에 INSERT문을 수행하는것이 아니라

UPDATE SQL을 추가로 수행해야한다.

 

왜냐 Member을 저장할땐 Team을 모른다.

그래서 일단 Member를 저장할땐 team_id는 비워져있고

Team을 저장할때 team.members로 회원들의 team_id 외래 키를 업데이트해야한다.

 

일대다 단방향은 성능 문제가 있기에 다대일 양방향 매핑을 사용하자.

 

@OneToMany 양방향

일대다 양방향 매핑은 존재하지 않는다.

@ManyToOne에는 mappedBy 속성이 없으므로

One쪽은 주인이 될 수 없다. (현재 게시글은 왼쪽이 주인으로 가정했음)

왜냐 관계형 DB에서 일대다 다대일 관계에서 외래 키는 항상 다 쪽에 있다.

 

왼쪽이 연관관계의 주인이라고 가정하지 않았을때

일대다 양방향이랑 다대일 양방향은 똑같은 말이다.

Comments