본문 바로가기
JPA Basic

JPA Basic - 4

by 완두완두콩 2021. 12. 28.

연관관계 매핑


객체와 테이블 연관관계의 차이를 이해하고 , 객체의 참조와 테이블의 외래키를 매핑하는 것에

대해 알아본다.

 

 

 

@Entity
public class Members {

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Teams teams;
@Entity
public class Teams {

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

다음과 같은 두개의 객체 (테이블) 이 존재할 때 , 멤버는 하나의 팀에 속할수 있고 , 하나의 팀은 여러 명의 멤버를 가질 수 있는 N:1 관계이다.

여기서 주의해야 할 점은 , Member 테이블에 ForeignKey(외래키)도 포함해 주었는데(private Teams teams) 앞으로 '객체'를 '테이블'에 맞추어 모델링을 하는 것을 기본 베이스로 깔고 간다.

 

다음과 같은 코드를 작성한다고 하면 , team 을 저장하는 것은 문제 없지만 , 회원을 저장시에 , team.getId()를 통해 아이디를 꺼내오고 , 해당 아이디로 다시 member.setTeamId() 로 저장한다.

이는 member에서 외래키 식별자인 TeamId 를 직접 접근하는 문제가 생기기 때문에 좋지 않은 방법이다.

 

객체를 테이블에 맞추어 데이터 중심으로 모델링하면 , 협력 관계를 만들 수 없다.

테이블은 외래키 조인을 통해 연관된 테이블을 찾는 반면 , 객체는 참조를 사용해서 연관된 객체를 찾는다.

테이블과 객체 간의 이러한 간격을 줄이기 위해 연관 관계 개념을 도입했다.

 

 

단방향 연관 관계


앞서 설명했듯이 , Member 테이블에 Team team (외래키)를 넣어서 매핑한다.

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Teams teams;

멤버 테이블에서 N:1 관계이므로 @ManyToOne 어노테이션을 붙여주고 , Team 테이블에서 Join 하고자 하는 칼럼을 @JoinColumn 어노테이션을 이용해 작성한다.

 

이전의 코드에서 멤버에 팀을 저장하는 경우 , 외래키를(teamId) 통해 직접 설정하였지만 지금의 경우 , 멤버테이블에서 

setTeam 으로 참조하여 저장하게 된다.

 

양방향 연관관계


양방향 연관관계와 단방향 연관관계의 차이점은 Team 테이블에도 List members 라는 변수를 설정해주는 것이다.

@OneToMany(mappedBy = "teams")
private List<Members> membersList = new ArrayList<>();

 Team 테이블에서는 Member 와 1:N 관계이므로 @OneToMany 를 사용하고 mappedBy 를 사용한다.

mappedBy 는 단순히 어떤 것과 매핑되었는지 확인하고 작성해주면 된다.

앞선 Member 테이블에서는 @ManyToOne 으로 Teams teams 와 매핑되었으므로 mappedBy = "teams" 로 작성한다.

 

객체간의 연관관계는

회원 -> 팀 연관관계 1개 ( 단방향 )

팀 -> 회원 연관관계 1개 ( 단방향 )

으로 총 2개이다.    => 이는 곧 양방향 연관관계

 

테이블간의 연관관계는

회원 <-> 팀 연관관계 1개 ( 양방향 ) 이다.

 

객체를 양방향으로 참조하기 위해서는 단방향 연관관계를 2개 만들면 된다.

 

Member 는 team 과 연관관계 매핑이 돼있고 , Team 은 member 와 연관관계 매핑이 돼 있다.

만약 , A 팀에 속하는 member1 을 B 팀으로 바꾸고 싶다고 하는 경우 , Member 테이블에서 수정을 해야할 지 ,

Team 테이블에서 수정을 해야할 지 기준을 정할 수가 없다.

Member 테이블에서 수정하는 경우 , member.setTeam(B);

Team 테이블에서 수정하는 경우 , team.setMembers(member1);

즉 두 가지 방법으로 바꿀 수 있는데 바꾸는 기준은 '연관관계의 주인' 지정을 통해 진행한다.

 

연관관계 주인(Owner)


객체의 두 관계중 , 하나를 연관관계의 주인으로 지정하고 , 연관관계의 주인만이 외래키를 관리한다. (등록 , 수정)

주인이 아닌 쪽은 읽기만 가능하고 , 주인은 mappedBy 속성을 사용하지 않는다.

반대로 주인이 아닌 경우에 mappedBy 속성을 사용한다.

 

주인을 정하는 기준은 바로 외래키가 있는 곳이 기준이다.

1:N 의 경우 , N 에 외래키가 포함되므로 Member 가 연관관계의 주인이 된다.

 

Members members = new Members();
members.setUsername("members1");
em.persist(members);

Teams teams = new Teams();
teams.setName("teamA");
teams.getMembersList().add(members);
em.persist(teams);

다음과 같은 코드를 실행할 경우 , 

위 그림과 같이 TEAM_ID 에 null 값이 들어감을 확인할 수 있다.

이는 위에서 말했던 연관관계의 주인을 잘못 생각했기 때문이다.

연관관계의 주인은 N:1 의 관계 중 , N 쪽인 바로 member 테이블이 바로 주인이다.

연관관계의 주인에서 등록 또는 수정 해야 하는데 , 위 경우는 teams.getMembersList().add 에서 볼 수 있듯이 연관관계의 주인이 아닌 곳에서 add 요청을 했기 때문에 null 값이 들어가는 오류가 발생하는 것이다.

Teams teams = new Teams();
teams.setName("teamA");
em.persist(teams);

Members members = new Members();
members.setUsername("members1");
members.setTeams(teams);
em.persist(members);

- 정상적인 처리 / 연관관계의 주인 테이블 에서 값을 수정한다.

 

※ 주인 테이블에서만 값을 등록 / 수정하는 것으로 배웠지만 flush, claer 를 하지 않는 경우 , DB 가 아닌 1차 캐시에서 값을 바로 가져오기 때문에 JPA 가 값을 못읽어온다거나 , 테스트 케이스 작성 시 원하는 값이 나오지 않는 경우 발생

이를 위해서 매핑된 쪽에도 값을 넣어준다.

teams.getMembersList().add(members);

하지만 위 경우는 깜빡해서 빠뜨리는 경우가 종종 발생하고 이러한 경우를 방지하기 위해

연관관계 편의 메소드를 생성한다.

public void setTeams(Teams teams) {
    this.teams = teams;
    teams.getMembersList().add(this);
}

set을 호출하는경우 자동으로 반대편 테이블에도 add 가 호출돼서 편리하다.

(setTeams 메소드명을 changeTeams 명으로 변경)

 

정리


처음 설계 시에는 단뱡향 매핑만으로 설계한다.

양방향 매핑은 반대 방향으로 조회 기능이 추가된 것 뿐 , 단방향 매핑을 우선시하고 양방향(mappedBy)은 필요할 때

추가하면 된다.

연관관계의 주인을 정하는 기준은 반드시 외래 키의 위치를 기준으로 정한다. (N이 있는 곳)

 

 

 

* 해당 글은 'Infleran'의 김영한 강사님의 자료를 참조하였습니다.

 

 

'JPA Basic' 카테고리의 다른 글

JPA Basic - 6  (0) 2021.12.30
JPA Basic - 5  (0) 2021.12.29
JPA Basic - 3  (0) 2021.11.18
JPA Basic - 2  (0) 2021.11.16
JPA Basic - 1  (0) 2021.11.16

댓글