연관 관계 - 정리

신상현·2021년 2월 13일
1

Spring Boot와 JPA

목록 보기
12/16
post-thumbnail

1. 연관 관계 매핑 시 고려사항

(1) 다중성

연관 관계가 있는 두 엔티티가 일대일, 일대다, 다대다 관계인지 파악해야 한다.
보통 다대일, 일대다를 가장 많이 사용하고, 다대다 관계는 거의 사용하지 않는다.

(2) 단방향, 역방향

테이블은 외래 키를 사용해서 양방향 참조가 가능하다.
하지만, 객체는 참조를 통해서만 다른 객체를 조회할 수 있다.
따라서, 객체의 참조는 방향성을 갖게된다.

(3) 연관 관계의 주인

양방향 관계의 경우 연관 관계의 주인을 설정해야 한다.
JPA는 연관 관계의 두 객체 중 하나를 정해 외래 키를 관리한다.
즉, 외래 키의 주인이 아닌 객체는 조회만 가능하다.
외래 키를 가진 테이블과 매핑한 엔티티가 외래 키를 관리하는게 효율적이기 때문에 연관 관계의 주인으로 설정한다.
주인이 아닌 엔티티 객체는 mappedBy 속성을 사용해 주인 필드의 이름을 value로 입력해준다.


2. 다대일 - 단방향, 양방향

  • DB의 일대다 관계에서 외래 키는 항상 "다" 쪽에 있다.
    따라서, 객체의 양방향 관계에서 연관 관계의 주인은 항상 "다" 쪽이다.

  • 양방향 연관 관계에서는 객체 간 항상 서로를 참조해야 한다. 실수하지 않기 위해서는 편의메소드를 작성해주는 것이 좋다.
    이 때, 양 쪽에 다 작성해주면 무한 루프에 빠지기 때문에 주의해야한다!

(1) 단방향

Member

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

Team

@Entity
public class Team {

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

(2) 양방향

Member

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

Team

@Entity
public class Team {
    
    @Id
    @GeneratedValue
    @Column(name="TEAM_ID")
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
    
    ...
    
    
}

3. 일대다 - 단방향, 양방향

  • 일대다 관계는 컬렉션을 활용한다.

  • DB 1:N 관계에서 외래 키는 항상 N쪽에 위치하기 때문에, 일대다 단방향 관계에선 매핑한 객체가 필드에서 관리하는 외래 키가 반대편 테이블에 존재한다.

  • 연관 관계 처리를 위해서 INSERT SQL 이외에 추가적으로 UPDATE SQL을 추가적으로 실행해야 한다.

Member member1 = new Member("1");
Member member2 = new Member("2");

Team team1 = new Team("1");
team1.getMembers().add(member1);
team1.getMembers().add(member2);

em.persist(member1);	// INSERT
em.persist(member2);	// INSERT
em.persist(team1);	// INSERT, UPDATE member1.외래키, UPDATE member2.외래키

tx.commit();
  • 반대편 테이블에서 외래 키를 관리하기 때문에 member1, member2가 저장될 때는 Team을 모른다. 따라서 외래 키 값에 null이 들어가게된다.

  • Team1이 저장될 때, member 객체들의 외래 키 값이 UPDATE 된다.

따라서 일대다 단방향 매핑보다는 외래 키가 자신 테이블에 있는 다대일 양방향 매핑을 활용하는 것을 권장한다.

4. 일대일 - 단방향, 양방향

  • DB의 1:1 관계에서 둘 중 어느 테이블에서도 외래 키를 가질 수가 있다.
    따라서, 두 엔티티 중 누가 외래 키를 가져야하는지 선택해야 한다.
  • 보통 Access 가 많은 엔티티를 주 테이블로 설정한다.
  • @OneToOne 어노테이션을 외래 키로 매핑할 필드에 추가한다.
  • DB에서 주 테이블에 외래 키 필드에 유니크 제약 조건을 설정한다.
  • 양방향 관계인 경우 주인이 아닌 쪽에 mappedBy 속성을 작성한다.

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

  • 외래 키를 객체 참조와 비슷하게 사용할 수 있다.
  • 주테이블만 확인해도 대상 테이블과 연관 관계가 있는지 알 수 있다.
  • 단방향
  • 양방향

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

  • DB 개발자들이 선호
  • 테이블 구조를 변경하지 않아도, 일대일 => 일대다 로 변경이 가능하다.

다대다 - 단방향, 양방향

DB와 객체에서 다대다 관계

RDBMS는 테이블 2 개로 다대다 관계를 표현할 수 없다.
그래서 보통 일대다 + 다대일 관계로 풀어내는 연결 테이블을 사용한다.

객체는 테이블과는 다르게 연결 테이블 객체를 만들지 않고도 2개의 객체가 컬렉션을 활용해 다대다 관계를 표현할 수 있다.

@ManyToMany

  • @ManyToMany 어노테이션으로 엔티티 2개로 다대다 관계를 표현할 수 있다.
  • @JoinTable 어노테이션은 연결 테이블을 지정해준다.

다대다 연관 관계의 한계점

하지만 @ManyToMany 를 이용한 매핑은 실무에서는 쓰이기엔 한계가 명확하다.
연결 테이블에 단순히 외래 키 2개의 정보 외에 다른 정보들도 포함되기 때문이다.
결국, 연결 테이블을 매핑하는 연결 엔티티를 만들고 추가적인 정보들을 포함시켜야 한다.

정리하면, 객체 관계는 연결 테이블 없이 다대다 관계를 풀어낼 수 있다.
실무적인 문제를 고려한다면 객체 관계에서도 테이블처럼 다대다 관계를 일대다, 다대일 관계로 풀어내야한다.

한계점 극복 (1) - 복합 기본 키 사용 (식별 관계)

부모 테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것을
DB에서는 식별 관계 라고 한다.

각 엔티티의 기본 키를 받아서 기본 키 + 외래 키로 사용한다.

  • 복합 키를 사용하기 위해서는 @IdClass로 식별자 클래스를 만들고, 지정해야 한다.
  • @EmbededId를 사용하는 방법도 있다.
  • 식별자 클래스는
    • Serializable을 구현해야 한다.
    • equalshashcode 메소드를 구현해야 한다.
    • 기본 생성자가 있어야 한다.
    • 식별자 클래스는 public 이어야 한다.

MemberProduct 클래스

@Entity
@IdClass(MemberProduct.class)
public class MemberProduct {
   
   @Id
   @ManyToOne
   @JoinColumn (name = "MEMBER_ID")
   private Member member;
   
   @Id
   @ManyToOne
   @JoinColumn (name = "PRODUCT_ID")
   private Product product;
   
   private int orderAmount;
}

MemberProductId 클래스

public class MemberProductId implements Serializable {
    
    private String member;
    private String product;
    
    @Override
    public boolean equals (Object O) { ... }
    
    @Override
    public int hashcode (Object O) { ... }
    
}

한계점 극복 (2) - 새로운 기본 키 사용 (비식별 관계) (추천)

받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가한다.

복합 키를 사용하는 방식은 항상 식별자 클래스를 생성해야한다.
하지만, 새로운 기본 키를 사용한다면 훨씬 간편하게 매핑을 할 수가 있다.

Member 클래스

@Entity
public class Member {
    
    @Id
    @Column(name = "MEMBER_ID")
    private String id;
    
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberproducts;
    ...
}

Product 클래스

@Entity
public class Product {
    
    @Id
    @Column(name = "PRODUCT_ID")
    private String id;
    
    private String name;
}

Order 클래스

@Entity
public class Order {
    
    @Id
    @GeneratedValue
    @Column (name = "ORDER_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    private int orderAmount;
}
profile
개발자 싱상형

0개의 댓글