1. 연관관계 매핑시 고려사항 3가지
1) 다중성
- 다대일: @ManyToOne
- 일대다: @OneToMany
- 일대일: @OneToOne
- 다대다: @ManyToMany
- 다중성은 대칭성이 있다.
- 다대다는 실무에서 사용하면 안된다.
2) 단방향, 양방향
- 테이블
> 외래키 하나로 양쪽 조인 가능하다. 그래서 방향이라는 개념이 없다.
- 객체
> 참조용 필드가 있는 쪽으로만 참조 가능
> 한쪽만 참조하면 단방향이고, 양쪽이 서로 참조하면 양방향
사실 객체 입장에서는 양방향이라는 것이 없음. 각 객체가 한쪽 방향으로 향해있으며, 단방향이 2개가 있는 것
3) 연관관계의 주인
- 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
- 객체 양방향 관계는 A -> B, B -> A 처럼 참조가 2군데가 있음
- 객체 양방향 관계는 참조가 2군데가 있으니, 둘 중 테이블의 외래 키를 관리할 곳을 지정해주어야 한다.
- 연관관계의 주인 : 외래 키를 관리하는 참조
- 주인의 반대편 : 외래 키에 영향을 주지 않으며, 단순히 데이터를 조회하는 기능만 있다.
2. 다대일(N:1)

- 외래 키가 있는 곳에 참조를 걸고 연관관계 매핑을 하면된다.
- 가장 많이 사용하는 연관관계이고 다대일의 반대는 일대다 이다.
- 외래 키가 있는 쪽이 연관관계의 주인이다
- 양쪽을 서로 참조하도록 개발해야한다.
아래 코드는 Member와 Team을 다대일(N:1) 연관관계로 매핑한 코드이다.
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne // Member 입장에서 N
@JoinColumn(name = "TEAM_ID") // 조인하는 컬럼명
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team") // Team의 기준에서는 1:N, 1:N 매핑에서 연관관계를 가지고 있는 것을 표시하는 것. (Member의 team)
private List<Member> members = new ArrayList<>();
}
3. 일대다(1:N)

Team을 중심으로 하는 연관관계이며, Member 객체는 Team을 알고싶지 않은 설계이다.
Team에 List<Member> members가 들어가있고, Team에 있는 Member를 추가하거나 수정하려고 할 때 Member에 있는 TEAM_ID를 변경시켜주어야한다.
아래 코드는 Team과 Member를 일대다(1:N) 연관관계로 매핑하여 예시로 실행시킨 코드이다.
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
}
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
// Main
public class RelationShipMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setName("memberA");
em.persist(member);
Team team = new Team();
team.setName("teamA");
// 해당 코드로 인해 update Member set TEAM_ID WHERE MEMBER_ID 쿼리가 실행되어짐
team.getMembers().add(member);
em.persist(team);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
- 일대다 단방향은 일대다(1:N)에서 1이 연관관계의 주인이다.
- 테이블 일대다 관계는 항상 N 쪽에 외래 키가 있다.
- 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조가 만들어진다.
- @JoinColumn을 꼭 사용해야 한다. 그렇지 않으면 조인 테이블 방식을 사용해야한다.(중간에 테이블 하나 추가되어진다)
- Team Entity에서 @JoinColumn을 주석처리하고 실행을 시키면 아래와 같이 테이블이 하나 생성되는 것을 알 수 있다.


일대다 단방향 매핑의 단점은 1)엔티티가 관리하는 외래 키가 다른 테이블에 있는 부분과 2) 연관관계 관리를 위해 추가로 UPDATE SQL을 실행하는 부분이 있다. 참조를 하나 더 넣더라도 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자!
일대다 양방향이라는 야매(?) 방식이 있는데, 이런 매핑은 공식적으로 존재하지는 않는다고 한다.
@JoinColumn(insertable=false, updatable=false)로 설정해주면, 읽기 전용 필드를 사용해서 양방향 처럼 사용할 수 있다.
4. 일대일(1:1)
- 일대일 관계는 그 반대도 일대일이다.
- 주 테이블이나 대상 테이블 중에 외래 키를 선택해서 넣을 수 있다. 주 테이블에 외래 키를 넣거나 대상 테이블에 외래 키를 넣어도 된다.
- 외래 키에 데이터베이스 유니크 제약조건 추가해야 한다.
1) 주 테이블에 외래 키 단방향
MEMBER가 주 테이블, MEMBER의 LOCKER_ID에 유니크를 넣어도 되고, LOCKER의 LOCKER_ID에 유니크를 넣어도된다.
다대일(@ManyToOne) 단방향 매핑과 유사한 방식이다.

2) 주 테이블에 외래 키 양방향
다대일 양방향 매핑처럼 외래 키가 있는 곳이 연관관계의 주인이 되고, 반대편은 mappedBy를 적용해주면된다.

3) 대상 테이블에 외래 키 단방향

이런 관계는 지원하지 않음. 양방향 관계는 지원한다.
4) 대상 테이블에 외래 키 양방향

Locker에 있는 Member를 연관관계 주인으로 두고 매핑을 하면된다.
사실 이 부분은 주 테이블에 외래키 양방향의 그림을 뒤집은 것과 같다. (매핑 방법 동일)
일대일 매핑관계 정리
1) 주 테이블에 외래 키
- 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾는다.
- 객체지향 개발자 선호한다.
- JPA 매핑이 편리하다
- 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능하다.
- 단점 : 값이 없으면 외래 키에 null을 허용해야 한다.
2) 대상 테이블에 외래 키
- 대상 테이블에 외래 키가 존재한다.
- 전통적인 데이터베이스 개발자가 선호한다.
- 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조를 유지할 수 있다.
- 단점 : 프록시 기능과 한계로 지연 로딩으로 설정해도 항상 즉시 로딩된다.
Member의 Locker가 있는지 확인하려면 Locker 테이블에 접근해서 확인을 해야함. 직접 확인을 해야하기 때문에 프록시 객체가 의미가 없다.
5. 다대다(N:N)
이 연관관계는 실무에서 잘 사용하지 않는다.

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야한다.
예를 들어 한 명의 회원이 여러 개의 상품을 주문할 수 있고, 하나의 상품이 여러 회원을 가질 수 있다면 두개의 테이블로는 연관관계를 맺을 수 없다. 그래서 중간에 연결 테이블을 추가해야한다.
아래 그림은 객체를 표현한 것이다. 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능하다.

@ManyToMany 사용하고, @JoinTable로 연결 테이블을 지정한다.
다대다 매핑 : 단방향, 양방향 가능
아래와 같이 Member 엔티티에 @ManyToMany와 @JoinTable 어노테이션을 사용하여 다대다 매핑을 만들 수 있다.
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
}
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
}
위와 같이 엔티티를 만들고, 실행해보면 MEMBER_PRODUCT 테이블을 생성하고 외래키 제약조건을 만들어주는 것을 확인할 수 있다.


Product에는 아래와 같이 해주면 매핑할 수 있다.
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
사실 다대다 매핑은 편리해 보이지만 실무에서 사용할 수 없다.
연결 테이블이 단순히 연결만 하고 끝나지 않고, 주문시간, 수량 같은 데이터가 들어올 수 있다.
다대다 한계 극복

다대다 한계를 극복하는 방법으로는 중간 연결 테이블을 엔티티로 승격하는 것인데
@ManyToMany -> @OneToMany, @ManyToOne 변경해서 사용하면 된다.
아래 예시를 살펴보자!
중간 연결 테이블을 엔티티로 승격시켜주고~
package hellojpa.relationship;
import jakarta.persistence.*;
@Entity
public class MemberProduct {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
}
Member와 Product를 아래와 같이 @OneToMany로 MemberProduct를 리스트로 변경해주면 된다.
// Member
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
// Product
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
아니면 아래 그림과 같이 DB 설계를 할 때 ORDER_ID 처럼 새로운 값을 PK로 두고 MEMBER_ID와 PRODUCT_ID를 외래키로 내리는 방법도 있다. 모든 테이블에 일관성 있게 GeneratedValue를 주는 것이다.
ID가 어디에 종속되어 있는 식으로 걸리면 시스템을 유연성있게 갈아치우는 것이 쉽지 않다.
의미없는 값을 PK로 두고 가져가는 것이 애플리케이션 개발을 할 때 훨씬 더 유연하다.

'TIL > JPA' 카테고리의 다른 글
[TIL/JPA] 기본개념 : 프록시와 연관관계 관리[즉시(EAGER)로딩, 지연(LAZY)로딩] (0) | 2024.08.27 |
---|---|
[TIL/JPA] 기본개념 : 고급 매핑(상속관계 매핑, @MappedSuperclass) (0) | 2024.08.24 |
[TIL/JPA] 기본 개념 : 연관관계 매핑 기초 (0) | 2024.08.17 |
[TIL/JPA] 기본 개념 : 엔티티 매핑 (0) | 2024.08.12 |
[TIL/JPA] 기본 개념 : 영속성 관리 - 내부 동작 방식 (0) | 2024.08.08 |