본문 바로가기
JPA

자바 ORM 표준 JPA 프로그래밍 - 연관관계 매핑

by 임동무 2023. 3. 30.

JPA 를 사용하여 객체간의 연관관계를 설정해주면 테이블간의 연관관계를 매핑할 수 있다. 

이 때, 단방향(한 쪽에서 참조) 과 양방향 (양쪽에서 참조) 이라는 방향성이 생기게 된다.

 

 

 

테이블과 객체는 관계맺은 상대를 탐색하는 방식에 있어서 차이를 보인다.

테이블은 외래키(fk) Join 을 이용하여 연관관계를 맺은 테이블을 탐색하지만

객체는 참조를 이용하여 연관관계를 맺은 객체를 탐색한다.

 

즉, 테이블은 한 쪽에서 외래키를 이용하여 탐색하며 이 외래키 단위 그대로 참조가 가능하기 때문에 양방향이라는 개념 자체가 존재하지 않는다.

따라서 이 패러다임의 차이를 극복하기 위해서 객체와 테이블간의 연관관계를 잘 성정해야 한다.

 


 

예를 들어보자.

Team 이라는 테이블과 Member 라는 테이블이 존재한다.

테이블에서는 각 Member 들이 fk 로 Team의 pk 값을 소지하고 있을 것이다. 

이 때, 이 외래키 하나로 회원과 팀간의 연관관계가 형성이 되며 이를 통해서

Member -> Team

Team -> Member 간의 자유로운 협력이 가능하다. (방향성이 존재하지 않는다.)

 

 하지만 객체입장에서 보면

Team 이라는 객체와 Member 라는 객체가 존재하고 

각 Member 객체는 Team 에 대한 참조를 가지고 있다. 이 때,

Member -> Team 간의 참조는 가능하지만 

Team -> Member 간의 참조는 할 수 있는 방법이 존재하지 않는다.(Team 에 Member에 대한 참조를 넣어주지 않는이상.)

 

이 패러다임의 차이를 해결하기 위해서 연관관계 매핑에 대한 정확한 이해가 필요하다.

 

 

 


- 양방향 매핑하는 법

객체에서 양방향 매핑을 하는 방식은 단방향 매핑을 2개 만드는 것이다.

즉, 위의 예시에서 Team 이 Member 를 참조하고 ( 단방향 1개),

Member 가 Team 을 참조하도록 (단방향 1개)

만드는 것이다. (총 단방향 2개 -> 양방향)

 

@Entity
@Table
public class Team {

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

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

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

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

    @ManyToOne      //해당 테이블을 기준으로
    @JoinColumn(name = "TEAM_ID")   //실제 조인하는 컬럼을 적어준다.
    private Team team;
}

 

위의 코드를 보면 양쪽에서 참조를 하고 있음을 알고 있다. 

하지만 이상한 점이 2가지 존재한다. 

1. 객체 자체를 가지고 있다.

2. 참조하는 방식이 다르다.


먼저 1번을 보자.

테이블은 객체를 가지는 것이 아니라, fk 값을 가진다. 

하지만 이를 기반으로 객체또한 key 값으로 호출을 하게 될 때의 flow는

key 값을 이용하여 객체 호출 -> 호출된 객체에서 외래키 값 호출 -> 해당 외래키를 통해 객체 불러오기 -> 해당 객체 반환

의 형태로 진행된다. 

하지만 id가 아닌 객체 자체를 가지고 있다면

key 값을 이용하여 객체 호출 -> 호출 시에 객체과 관계를 맺는 객체들 호출 -> 객체에서 참조

하는 방식으로 좀 더 객체지향적으로 모델링할 수 있다.

 


그럼 2번에 대해서 보자.

현재 Team 에서는 @OneToMany 형태로 참조를 하고있고

Member 에서는 @ManyToOne 형태로 참조하고 있다.

직관적으로 의미를 파악할 수 있듯이

하나의 Team 은 여러 Member와 , 여러 Member 는 하나의 Team 과 관계를 맺는다는 것이다.

이는 연관관계의 주인을 설정해준 것이다. 

 

먼저 연관관계의 주인이란 외래키의 관리 권한을 갖는다는 것을 의미한다. 즉, 연관관계의 주인은 외래키를 관리하며 수정, 삭제 등의 작업을 할 수 있지만 연관관계의 주인이 아닌(반대편) 에서는 단순 조회만 가능하다.

위에서 @OneToMany 에서 mappedBy 라는 옵션을 확인할 수 있는데 이를 통해 주인을 지정해 줄 수 있다.

위에서는 Member의 Team 이 연관관계의 주인이며 Team 의 members 는 반대편이 되는 것이다. 


이렇게 설정해줘야하는 이유는 앞서 말한 패러다임의 차이 때문이다.

 

Member 에는 Team 의 FK 값이 저장되며 

객체에서는 team 이라는 변수로 존재한다. 이를 연관관계 주인으로 설정하여 반대편에는 가짜매핑(mappedBy) members 존재하며 이는 테이블상에는 존재하지 않는다. 

 

또한 통상 fk 를 가지는 테이블쪽에 연관관계의 주인을 설정한다. 

예를 들어 위에서 Team 쪽에서 연관관계의 주인을 설정한다면 하나의 Team 임에도 Member 가 추가될때마다 Team 데이터가 하나씩 추가되기 때문이다. 

 

그래서 결론은

연관관계를 설정할 때, 연관관계의 주인쪽에서 등록, 수정이 가능하며 반대편에서는 단순 조회만 가능하다. 

Member에 변경이 있을 때, Team 의 Members 를 호출해서 변경하는 것이 아니라, 

Member 를 호출하여 Team 외래키값을 수정해주어야 한다. 

댓글