[JPA] 연관 관계 매핑 정리

옹심이·2024년 12월 19일
0
post-thumbnail

다대일

단방향 매핑

@Entity
public class Member {
		@Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ...
}

외래 키가 있는 MEMBER 테이블을 기준으로 하여 객체 연관관계에서 같은 레벨인 Member 객체에 @JoinColumn을 통해 참조할 외래키의 이름을 지정한다.

단방향 매핑은 주인인 Member 개체에서 team에 대한 조회, 추가, 수정, 삭제가 가능하지만 Team 에서는 member에 대해 아무런 접근을 할 수 없다.

양방향 매핑

public class Team {
    @Id @GeneratedValue
    private Long id;
    
    private String name;
 
    @OneToMany(mappedBy = "team")
    private List<Member> member;
    ...
}

mappedBy 속성 값으로 연관 관계의 주인을 지정해주면, 가짜 매핑으로써 member에 대한 조회만 가능해진다.

일대다

단방향 매핑

public class Team {
    @Id @GeneratedValue
    private Long id;
    
    private String name;
 
    @OneToMany(mappedBy = "team")
    @JoinColumn(name = "TEAM_ID")
    private List<Member> member;
    ...
}

이는 앞서 살펴본 다대일 연관 관계와 반대로 Team을 중심으로 연관 관계를 설정하는 방법이다.

@JoinColumn을 사용하지 않으면 TEAM과 MEMBER의 조인 테이블이 생성되니 꼭 어노테이션을 지정해주도록 하자.

권장하지 않는 이유

DB 관점에서는 무조건 다 쪽에 외래 키가 존재한다. 그렇기 때문에 Team 객체의 members 값을 수정하면 DB상에서 MEMBER에 수정이 반영된다.

TEAM과 MEMBER 테이블에 INSERT된 후에 연관 관계 관리를 위해 MEMBER 테이블의 외래 키가 업데이트 된다. 엔터프라이즈 운영 환경에서는 테이블의 개수가 아주 많기 때문에 이렇게 불필요한 쿼리의 생성은 혼란을 야기할 수 있다.

그러므로 외래 키가 존재하는 테이블을 중심으로 하여 다대일 매핑을 사용하는 것이 좋다.

양방향 매핑

만약 역방향에서도 필드 값에 대한 조회가 필요하다면 양방향 매핑을 사용할 수 있다.

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(insertable = false, updatable = false)
    private Team team;
    ...
}

@JoinColumn에 속성이 추가되었다. 만약에 name 속성을 사용하여 조인 할 칼럼의 이름을 설정해주면 Member 또한 연관 관계의 주인이 되기 때문에 다른 속성을 사용하여 이를 방지해야 한다.

이를 위해 속성 값으로 읽기 전용 필드로 만드는 것이다.

일대일 관계

일대일은 반대도 일대일이기 때문에 외래 키를 주 테이블과 대상 테이블 중어떤 테이블에 둘지에 대해서는 상황을 고려하여 선택하면 된다.

단방향 매핑(주 테이블 기준)

주 테이블에 외래키를 두는 경우는 Member 개체가 Locker의 참조를가지는 것과 유사하게 주 테이블에 외래 키를 두고 대상 테이블을 탐색하는 것이다.

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(insertable = false, updatable = false)
    private Team team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    ...
}

MEMBER와 LOCKER 간에 일대일 연관 관계 매핑을 하려면 이렇게 할 수 있다.

양방향 매핑(주 테이블 기준)

@Entity
public class Locker {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToOne(mappedBy = "locker")
    private Locker locker;
}

이전과 똑같이 mappedBy로 역방향에서도 locker 필드 값을 조회할 수 있도록 설정해주면 된다.

일대일 매핑(대상 테이블 기준)

대상 테이블에 외래키를 두는 경우는 대상 테이블에 주 테이블에 대한 외래키가 존재하는 것이다.

대상 테이블 기준 객체 연관 관계는 JPA에서 일대일 매핑을 지원하지 않는다.

양방향 매핑(대상 테이블 기준)


g)

엔티티에 있는 필드는 테이블에서도 직접 관리되어야 한다.

그렇기 때문에 Locker의 member를 연관 관계의 주인으로 정하면 구현이 가능하지만 권장되는 방법은 아니다.

외래 키 부여할 테이블 트레이드 오프

주 테이블은 주로 많이 접근하는 테이블인 MEMBER로 설정한다

외래 키가 주 테이블에 있는게 좋을까 반대로 대상 테이블에 있는게 좋을까?

미래의 테이블 변화를 고려하지 않을 수 없다. 테이블 수정을 고려할 때 주 테이블에 외래 키를 부여하는 경우 대상 테이블에 외래 키를 부여하는 경우 둘 다 장단점이 있다.

주 테이블에 외래 키를 두는 경우

  • 주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾는다.
  • 장점 : 주 테이블만 조회해도 대상 테이블의 데이터에 데이터가 있는지 확인 가능하다.
  • 단점 : 값이 없으면 외래 키에 null을 허용하게 된다.

대상 테이블에 외래 키를 두는 경우

  • 대상 테이블에 외래 키가 존재한다.
  • 장점 : 비즈니스 로직 변경에 의해 주 테이블과 대상 테이블을 일대일 관계에서 일대다 관계로 수정해도 테이블 구조에는 수정이 일어나지 않는다.
  • 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 즉시 로딩된다.

다대다

다대다 연관 관계는 실사용이 권장되지 않는다.

관계형 데이터베이스는 정규화된 테이블 2개로 다대다를 표현할 수 없기 때문에 연결 테이블을 하나 더 둬서 일대다, 다대일 관계를 추가해 구현한다.

객체는 컬렉션을 사용하면 객체 두 개로 다대다 관계 구현이 가능하다.

단방향 매핑

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(insertable = false, updatable = false)
    private Team team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();
    ...
}

@ManyToMany와 @JoinTable로 다대다 단방향 매핑을 지정할 수 있다.

양방향 매핑

@Entity
public class Product {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();
    ...
}

다음 코드를 추가해주면 양방향 매핑으로 수정할 수 있다

권장하지 않는 이유

연결 테이블이 단순히 연결만 하고 끝나지 않는다.

매핑되는 MEMBER_ID, PRODUCT_ID만 사용할 수 있으며 그 이외에 필요한 주문 시간 ,수량 같은 데이터는 사용할 수 없기 때문이다.

권장하는 방법

이는 연결 테이블을 하나더 생성하고 일대다와 다대일로 나눠 구현하면 가능하다. 따라서 연결 테이블에 매핑되는 객체도 추가해서 구현해야한다.

추가로 연결 테이블을 추가할 때 PK는 MEMBER_ID와 PRODUCT_ID와 관련 없는 UUID등을 사용하는 것이 좋다.

@Entity
public class MemberProduct {
    @Id @GeneratedValue
    private Long id;
 
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
 
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
	...
}
@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "MEMBER_ID")
    private Long id;

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

    @ManyToOne
    @JoinColumn(insertable = false, updatable = false)
    private Team team;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

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

    @Id @GeneratedValue
    private Long id;

    private String name;

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

0개의 댓글