[JPA] 왜 외래키가 대상 테이블에 있으면 일대일 단방향 매핑이 안 될까?

joyful·2025년 6월 7일
0

JPA

목록 보기
19/19

서론

일대일 관계는 양쪽 엔티티가 서로 하나의 엔티티만을 참조하는 관계를 의미 한다.

일반적인 다대일(N:1) 관계에서는 외래 키가 주로 다(N)쪽 테이블에 위치하지만, 일대일(1:1) 관계에서는 주 테이블과 대상 테이블 어느쪽이든 상관 없이 외래 키를 가질 수 있다.

따라서 주 테이블과 대상 테이블 중 어느 쪽에 외래 키를 둘지 선택해야 한다.

위 그림을 살펴보면, 1명의 회원은 1개의 사물함만 사용 가능하다. 반대로 1개의 사물함은 1명의 회원만 소유할 수 있다. 이것이 일대일 관계인 것이다.

여기서는 사물함보다는 회원에 접근하는 빈도수가 높은 것으로 간주하여 주 테이블을 회원(MEMBER), 대상 테이블을 사물함(LOCKER)으로 설정했다.

먼저, 주 테이블에 외래 키가 있는 경우를 살펴보자.


🔎 주 테이블에 외래 키가 있는 경우

그림에서 볼 수 있듯, 연관관계의 주인인 객체의 테이블과 외래키가 위치한 테이블이 같다. 이 경우 연관관계 매핑은 어렵지 않으며, 다대일(@ManyToOne) 단방향 매핑과 유사하다.

@Entity
public class Member {
	@Id
    @Column(name = "member_id")
    private Long id;
    
    private String name;
    
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}

Locker 참조 객체를 저장할 수 있는 필드를 하나 생성해주고, @OneToOne 어노테이션을 설정해주어 일대일 연관관계임을 JPA에게 알려주었다. 또한 @JoinColumn 어노테이션으로 locker_id라는 외래키로 조인할 것임을 명시했다.

주 테이블에 외래 키가 있는 경우는 그렇게 어렵지 않다. 연관관계를 관리하는 주체가 테이블과 객체가 동일하기 때문이다. 너무나 자연스럽고 당연하여 헷갈리지 않는다.

그렇다면 상대 테이블에 외래키가 있는 경우는 어떨까?


🔎 상대 테이블에 외래 키가 있는 경우

그림에서 볼 수 있듯, 연관관계의 주인인 객체의 테이블과 외래키가 위치한 테이블이 같지 않다.

먼저, DB 관점에서 살펴보자. 두 테이블을 조인하기 위해서는 외래 키로 조인을 해야 한다.

SELCT *
FROM MEMBER m
JOIN LOCKER l ON l.MEMBER_ID = m.MEMBER_ID

위 쿼리문을 실행하면 문제 없이 잘 실행된다. 우리도 직관적으로 생각하기에, LOCKER 테이블에 존재하는 외래 키와 MEMBER 테이블의 기본 키를 조인하는 방식이 맞다고 판단하게 된다.

@Entity
public class Member {
	@Id
    @Column(name = "member_id")
    private Long id;
    
    private String name;
    
    // 잘못된 연관관계 설정
    @OneToOne
    @JoinColumn(name = "member_id")
    private Locker locker;
}

그래서 이어서 엔티티를 설정해본다. "조인할 컬럼명을 MEMBER 테이블의 PK랑 동일하게 설정하면, LOCKER 테이블에 있는 member_id가 MEMBER 테이블의 PK와 조인되겠지?" 라는 기대와 함께 말이다.

하지만 안타깝게도, 그렇게는 우리가 원하는 방식으로 연관관계를 설정할 수 없다. 왜 그럴까?

기본적으로 JPA는 @JoinColumn으로 명시한 컬럼을 기준으로 조인 조건을 내 테이블의 FK = 상대 테이블의 PK로 만들어 낸다. 다시 말하지만, "내 테이블의 FK"를 가지고 조인을 한다는 소리다. 이 말인즉, 내 테이블에 외래 키가 존재해야 한단 소리다.

근데 현재 외래 키가 어디에 존재하냐? 바로 상대 테이블이다. 즉, 내 테이블에는 외래 키가 없단 소리다. 그러니 JPA가 있지도 않은 내 테이블의 외래 키를 찾을 수 있을리가 만무하다.

즉, 위 소스코드에서 설정한 연관관계를 JPA 입장에서 해석하자면 다음과 같다.

"MEMBER 테이블에 member_id라는 외래키 컬럼이 있나 보네? 이걸로 LOCKER 테이블의 locker_id랑 조인하면 되겠네~"

그래서 JPA는 다음과 같은 SQL을 생성하려고 시도한다.

SELECT *
FROM MEMBER m
JOIN LOCKER l ON m.member_id = l.locker_id;

하지만 실제로는 MEMBER 테이블에 member_id와 같은 외래 키 컬럼이 존재하지 않는다. 결국 JPA는 잘못된 조인 조건을 바탕으로 쿼리를 생성하게 되고, 이는 실행 오류로 이어지게 된다.

그래서 상대 테이블에 외래 키가 있는 경우는 일대일 단방향 매핑을 할 수 없는 것이다.

그렇다면 이제는 다음과 같은 의문이 생길 수도 있다.


🤔 근데.. 일대다 단방향 매핑은 가능하잖아?

일대다 단방향 연관관계 매핑을 공부했다면, 연관관계의 주인이 되는 테이블에 외래 키가 없음에도 불구하고 단방향 연관관계 매핑이 가능하단 사실을 알 것이다. 어떻게 가능한 것일까?

하나의 팀에 여러명의 회원이 속할 수 있다는 요구사항을 전제로, 여기서는 다(1)쪽인 Team을 연관관계의 주인으로 설정했다.

@Entity
public class Team {
	@Id
    private Long id;
    
    private String name;
    
    @OneToMany
    @JoinColumn(name = "team_id")
    private List<Member> members = new ArrayList<>();
}

앞서 언급했듯이, @JoinColumn은 기본적으로 "내 테이블에 있는 외래 키"를 기준으로 조인을 수행한다. 하지만 @OneToMany@JoinColumn을 함께 사용하는 경우에는 조금 다르다. 이 조합을 사용하면 JPA는 대상 테이블(N)에 @JoinColumn에 명시된 이름의 외래 키 컬럼을 생성하려고 한다.

즉, 주 테이블(1)에서 외래 키의 위치를 상대 테이블에 만들도록 강제로 지정하는 셈이다.
이는 JPA에서 @OneToMany 관계에서만 예외적으로 외래 키를 상대 테이블에 생성할 수 있도록 허용한 경우이기 때문에 가능한 것이다.


✍️ 마무리하며..

JPA에서 단방향 매핑을 할 때, 외래 키내 테이블에 있어야 한다는 점을 꼭 기억해야 한다.

만약 외래 키가 상대 테이블에 있다면? JPA 입장에서는 조인 조건을 만들 수가 없어서 단방향 매핑을 할 수 없다. 뭐랄까.. "너한테 외래 키가 없는데 어떻게 대상 테이블과 조인하란 소리임?" 이런 느낌?

다만 @OneToMany는 JPA가 특별히 예외를 둔 케이스라서, 상대 테이블에 외래 키를 만들어주는 방식으로 처리해준다. 그래서 단방향 매핑이 가능했던 거고.

결국 외래 키가 실제로 어디에 있는지, 그리고 누가 연관관계 주인이 누구인지를 제대로 이해하고 있어야 헷갈리지 않는 것이다.

profile
기쁘게 코딩하고 싶은 백엔드 개발자

0개의 댓글