섭섭한 개발일지

[필기용] 자바 ORM 표준 JPA 프로그래밍 Ch06 본문

프로그래밍/JPA

[필기용] 자바 ORM 표준 JPA 프로그래밍 Ch06

Seop 2023. 2. 1. 13:24

git : https://github.com/seop-kim/Java_JPABook/tree/ch06-jpa

CHAPTER 06

[ 다양한 연관관계 매핑 ]

  • 다대일
    • 다대일 연관관계에서는 항상 다 쪽이 외래키를 갖는다.
      • 멤버와 팀이 있다면 멤버가 외래키를 관리한다.
    • 단방향
      • 단방향은 외래키를 관리하는 객체 외의 상대 객체에서 외래키 관리 객체를 조회하지 못하는 경우 단뱡항이다.
    • 양방향
      • 단방향의 반대로 상대 객체에서도 주인 객체를 조회할 수 있는 경우를 말한다.
    • 다이어그램
      • 객체 연관관계 다이어그램에서 실선으로 된 화살표는 연관관계의 주인인다.
      • 점섬은 반대로 주인이 아닌 객체이다.
  • 일대다
    • 일대다 관계는 다대일 관계의 반대이다.
      일대다 관계에서는 엔티티를 하나 이상 참조할 수 있으므로 자바의 컬렉션, List, Map 중에 하나를 사용하여야 한다.
    • 단방향
      • 하나의 팀은 여러 회원을 참조할 수 있는 관계를 일대다 관계라고 한다. 팀은 회원을 참조하고 회원을 팀을 참조하지 않을때 관계는 단방향 관계이다.
      • 책에서 소개하는 단방향 매핑에서는 객체 연관관계와 테이블 연관관계가 아래와 같다
      • [객체] Team {id, name, List<Member> members} Member {id, username} [테이블] TEAM {TEAM_ID, NAME} MEMBER {MEMBER_ID, TEAM_ID, USERNAME}
      • 이 관계를 보면 객체 연관관계에서는 Team.members로 회원 테이블의 TEAM_ID 외래키를 관리한다. 보통은 자신이 매핑한 테이블의 외래키를 관리하는데 이 매핑은 반대쪽 테이블에 있는 외래 키를 관리한다.
      • 이럴 수 밖에 없는 것이 일대다 관계에서 외래키는 항상 다쪽에 있는데 다 쪽인 Member에 Team을 참조하는 객체가 없고 Team에 Member를 참조하는 객체가 있으므로 반대편 테이블에서 외래키를 관리하는 특이한 모습을 보인다.
      public class Team {
      @Id
      @Column(name = "TEAM_ID")
      private String id;
      private String name;
      
      //    @OneToMany(mappedBy = "team")
      @OneToMany
      @JoinColumn(name = "TEAM_ID")
      private List<Member> members = new ArrayList<>();
      
      protected Team() {
      }
      
      public Team(String id, String name) {
          this.id = id;
          this.name = name;
      }
      }
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;

protected Member() {
}

public Member(String id, String username) {
    this.id = id;
    this.username = username;
}
```
  • 이러한 일대다 단방향 매핑의 단점은 insert 문이 실행된 후 update 문을 통해 team에서 members를 관리하는 sql을 사용하여 sql문이 2번 실행된 다는 점에 있다.
    • 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.
    • 성능의 문제도 있지만 관리를 하는 것에도 부담이 있기에 다대일 양방향을 사용하는 것이 좋다.
  • 일대다 양방향
    • 일대다 양방향 매핑은 존재하지 않는다 대신 다대일 양방향이 존재한다 (사실 일대다, 다대일 양방향은 같은말이다..)
    • 양방향 매핑에서 @OneToMany는 연관관계의 주인이 될 수 없다. 관계형 데이터베이스의 특성상 일대다, 다대일 관계는 항상 다 쪽에 외래키가 있기때문이다.
    • 그렇다고 일대다 양방향 매핑이 완전히 불가능하다는 것은 아니다. 일대다에서 일 쪽에 읽기전용매핑을 두면 사용은 할 수 있다.
    • 일대일
    • 일대일 관계는 양쪽이 서로 하나의 관계만 가진다.
      • 일대일 관계에서 연관관계의 주인은 어느 누구나 가질 수 있다.
      • 주 테이블에 외래키를 둘 경우
      • 이는 객체지향 개발자들이 선호하는 방법이다. 이 방법의 장점은 주 테이블이 외래키를 가지고 있어 연관관계가 어떻게 이루어져 있는지 쉽게 확인할 수 있다.
      • 대상 테이블에 외래키
      • 데이터베이스 개발자들이 선호하는 방법이다. 이 방법의 장점은 추후에 일대다 관계로 변경이될 때 테이블 구조를 그대로 유지할 수 있다.
    • 주 테이블에 외래 키
      • 일대일 관계를 구성할 때 주 테이블에 외래키가 있으면 JPA도 편하게 매핑을 할 수 있다. 단방향관계와 양방향관계를 살펴보자
      • 단방향
        • 연관관계의 주인 객체에서만 반대편 객체를 조회할 수 있다. 이 관계는 다대일 단방향과 구조가 거의 유사하다.
      • 양방향
        • 반대편 객체에도 주인 객체를 mappedBy 속성을 주고 추가하면 된다.
    • 대상 테이블에 외래 키
      • 단방향
      • 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다.
      • JPA에서 이러한 관계로 하고자 한다면 양방향 관계로 연결하여 대상테이블을 연관관계로 수정하거나 단방향 관계의 방향을 주 테이블로 쪽으로 변경해야한다.
      • 허나 JPA 2.0 부터는 일대다 대상테이블에 외래키가 있는 것을 허용했다. 일대일은 허용하지 않았다.
  • 다대다
    • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 다대다 관계를 일대다 다대일 관계로 풀어내는 연결 테이블을 사용한다.
    • 객체는 다대다 관계를 객체 2개만을 사용하여 다대다 관계로 편리하게 매핑을 할 수는 있다.
    • JPA에서 다대다 매핑을 하면 중간 테이블이 하나 생성되어 다대다 관계를 일대다, 다대일 관계로 풀어내는 테이블을 생성하게 된다.
    [Member] 
    @ManyToMany 
    @JoinTable(name = "MEMBER_PRODUCT", 
    joinColumns = @JoinColumn(name = "MEMBER_ID"), 
    inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")) 
    private List<Product> products = new ArrayList<>(); 
    
    [Product] 
    @Id 
    @Column(name = "PRODUCT_ID") 
    private String id; private String name;
    • 이 코드는 다대다 단방향 매핑이다.
      • 이 코드에서 Member 객체에에서 다대다 관계에 대한 매핑을 진행했다.
      • 연결테이블 매핑을 @JoinTable을 통해 만들었다.
      • Jointable.joinColumn 은 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정하는 것으로 MEMBER_ID를 줬다.
      • inverseJoinColumns 은 반대 방향인 객체와 매핑할 조인 컬럼 정보를 지정하는 것으로 PRODUCT_ID를 줬다.
    • 다대다 양방향 매핑도 있으며 상대 객체에 읽기 전용으로 속성을 주고 만들면 된다.
    • 다대다 매핑의 한계와 극복, 연결 엔티티 사용
      • 다대다 매핑을 사용하면 연결 테이블을 자동으로 생성해줘서 도메인 모델이 단순해지고 편리한 부분이 많다. 다만 실무에서는 연결 테이블 속성을 단순히 사용하지 않기에
        JPA가 만들어주는 단순한 테이블을 사용하기는 현실과는 동떨어져있다. 실무에서는 주문한 날짜나 수량과 같은 컬럼을 추가하는 것이 현실적이다.
        • JPA는 다대다 관계에서 자동으로 생성하는 연결 테이블에 두 객체의 fk 값만 저장을 한다.
      • 복합키를 사용한 해결
        • 연결 엔티티를 사용하게 되면 연결 엔티티의 경우 회원과 상품의 외래키를 가지고 있고 회원 외래키를 기본키로 사용하게 된다. 이렇게 다른 테이블의 기본키를 받아 기본키 + 외래키로 사용하는 것이 테이터베이스 용어로 식별 관계라고 한다.
        @Getter
        @Setter
        @Entity
        @IdClass(MemberProductId.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;
        }
      • 연결 엔티티의 경우 두개의 ID 값을 가지고 있기에 복합 기본키 매핑을 해야한다. 복합 기본키 매핑을 위해서는 클래스에 @IdClass 를 선언해야 하며 복합키를 위한 식별자 클래스를 생성해줘야한다.
      public class MemberProductId implements Serializable {
        private String member; // MemberProduct.member 와 연결
        private String product; // MemberProduct.product 와 연결
      
        // hashCode and equals
      
        @Override
        public boolean equals(Object obj) {
            return super.equals(obj);
        }
      
        @Override
        public int hashCode() {
            return super.hashCode();
        }
      }
      • 복합기본키를 위한 식별자 클래스는 아래의 특징이 있어야한다.
        • 복합 키는 별도의 식별자 클래스로 만들어야한다.
        • Serializable을 구현해야한다.
        • equals와 hashCode 메소드를 구현해야한다.
        • 기본 생성자가 있어야한다.
        • 식별자 클래스는 public이여야 한다.
        • @IdClass를 사용하는 방법 외에 @EmbeddedId를 사용하는 방법도 있다.
  • 기본키를 사용한 해결방법
    • 기본키가 있는 객체를 하나 더 만들어서 사용하는 방법이 있다. 이는 간편하면서도 영구히 사용할 수 있으며 비즈니스에 의존하지 않는다.
    @Table(name = "ORDERS")
    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;
    }
  • 식별자 클래스를 사용하지 않아 코드가 한결 단순해졌다. 이러한 기본키를 사용해 다대다를 푸는 방법도 좋은 방법 중 하나이다.
    • 이러한 관계는 비식별관계라고 한다.
Comments