ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 7. 고급 매핑
    Back-End/JPA 2022. 5. 3. 15:50

    1. 상속 관계 매핑

    관계형 데이터베이스에는 상속이라는 개념이 없다.
    대신 객체의 상속 개념과 가장 유사한 슈퍼타입 서브타입 관계라는 모델링 기법을 사용할 수 있다.

    슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현하기 위한 방법은 3가지가 있다.

    1. 각각의 테이블로 변환: JPA에서는 조인 전략이라 한다.
    2. 통합 테이블로 변환: JPA에서는 단일 테이블 전략이라 한다.
    3. 서브타입 테이블로 변환: JPA에서는 구현 클래스마다 테이블 전략이라 한다.

     

    객체 상속 모델

     

    1.1. 조인 전략 (Joined Strategy)

    엔티티 각각을 모두 테이블로 만들고, 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키와 외래 키로 사용하는 전략이다.

    예제 코드

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED) // 조인 전략 사용
    @DiscriminatorColumn(name = "DTYPE") // 구분 컬럼 지정 (기본값: DTYPE)
    public abstract class Item {
    	
        @Id @GeneratedValue
        @Column(name = "ITEM_NAME")
        private Long id;
        
        private String name;
        private int price;
        ...
    }
    
    @Entity
    @DiscriminatorValue("A") // 구분 컬럼의 값: A
    public class Album extends Item {
    	
        private String artist;
        ...
    }
    
    
    @Entity
    @DiscriminatorValue("M") // 구분 컬럼의 값: M
    public class Movie extends Item {
    	
        private String director;
        private String actor;
        ...
    }
    
    @Entity
    @DiscriminatorValue("B") // 구분 컬럼의 값: B
    @PrimaryKeyJoinColumn(name = "BOOK_ID")
    public class Movie extends Item {
    	
        private String author;
        private String isbn;
        ...
    }

     

    @Inheritance(strategy = ...)
    상속 매핑 시에 부모 클래스에 사용하는 어노테이션.
    매핑 전략은 strategy 속성을 사용하여 지정한다.
    여기서는 조인전략을 사용하므로 InheritanceType.JOINED로 설정하였다.

    @DiscriminatorColumn(name = ...)
    부모 클래스의 구분 컬럼을 지정하는 어노테이션.

    구분 컬럼을 통해 자식 테이블을 구분할 수 있다.
    구분 컬럼의 이름 기본값은 "DTYPE" 이다.

    @DiscriminatorValue(...)
    자식 엔티티를 저장할 때 구분 컬럼에 입력할 값을 지정하는 어노테이션.

    @PrimaryKeyJoinColumn(name = "")
    자식 테이블의 ID 컬러명은 기본값으로 부모 테이블의 컬럼명을 따른다.
    자식 테이블의 기본 키 컬럼명으로 변경하고 싶을 때 사용하는 어노테이션.


    장점
    • 테이블이 정규화된다.
    • 외래 키 참조 무결성 제약조건을 활용할 수 있다.
    • 저장 공간을 효율적으로 사용한다.

    단점
    • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
    • 조회 쿼리가 복잡하다.
    • 데이터를 등록할 때 INSERT SQL을 두 번 실행한다.

    특징
    • JPA 표준 명세는 구분 컬럼을 사용하도록 하지만 하이버네이트를 포함한 몇몇 구현체는 구분 컬럼 없이도 동작한다.
    • 관련 어노테이션
      @PrimaryKeyJoinColumn, @DiscriminatorColumn, @DiscriminatorValue

     

    1.2. 단일 테이블 전략 (Single-Table Strategy)

    이름 그대로 테이블을 하나만 사용하는 전략이다.
    구분 컬럼으로 어떤 자식 데이터가 저장되었는지 구분한다.
    조회할 때 조인을 사용하지 않으므로 일반적으로 가장 빠르다.

    이 전략에서 주의할 점은, 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다는 점이다.

    예제 코드

    @Entity
    @Inheritance(strategy = InheriganceType.SINGLE_TABLE) // 단일 테이블 전략 사용
    @DiscriminatorColumn(name = "DTYPE")
    public abstract class Item {
    	
        @Id @GeneratedValue
        @Column(name = "ITEM_ID")
        private Long id;
        
        private String name;
        private int price;
        ...
    }
    
    @Entity
    @DiscriminatorValue("A")
    public class Album extends Item { ... }
    
    @Entity
    @DiscriminatorValue("M")
    public class Movie extends Item { ... }
    
    @Entity
    @DiscriminatorValue("B")
    public class Book extends Item { ... }

    InheritanceType.SINGLE_TABLE 로 지정하여 단일 테이블 전략을 사용한다.
    테이블 하나에 모든 것을 통합하므로 구분 컬럼을 필수로 사용해야 한다.

     

    장점
    • 조인이 필요 없으므로 일반적으로 조회 성능이 가장 빠르다.
    • 조회 쿼리가 단순하다.

    단점
    • 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
      그러므로 상황에 따라서는 조회 성능이 오히려 느려질 수 있다.

    특징
    • 구분 컬럼을 꼭 사용해야 한다. → @DiscriminatorColumn을 꼭 설정해야 한다.
    • @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다. (ex. Movie, Album, Book)

     

    1.3. 구현 클래스마다 테이블 전략 (Table-per-Concrete-Class Strategy)

    자식 엔티티마다 테이블을 만드는 전략이다.

    예제 코드

    @Entity
    @Inheritance(strategy = InheriganceType.TABLE_PER_CLASS) // 구현 클래스마다 테이블 전략 사용
    public abstract class Item {
    	
        @Id @GeneratedValue
        @Column(name = "ITEM_ID")
        private Long id;
        
        private String name;
        private int price;
        ...
    }
    
    @Entity
    public class Album extends Item { ... }
    
    @Entity
    public class Movie extends Item { ... }
    
    @Entity
    public class Book extends Item { ... }

    InheritanceType.TABLE_PER_CLASS 로 지정하여 구현 클래스마다 테이블 전략을 사용한다.
    일반적으로  추천하지 않는 전략이다.

    장점
    • 서브 타입을 구분해서 처리할 때 효과적이다.
    • not null 제약조건을 사용할 수 있다.

    단점
    • 여러 자식 테이블을 함께 조회할 때 성능이 느리다. (SQL에 UNION을 사용해야 한다.)
    • 자식 테이블을 통합해서 쿼리하기 어렵다.

    특징
    • 구분 컬럼을 사용하지 않는다.

     

    이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는 전략이다.
    다른 전략을 고려하자!

     

    2. @MappedSuperclass

    지금까지 부모 클래스와 자식 클래스 모두 데이터베이스 테이블과 매핑했다.
    부모 클래스를 테이블과 매핑하지 않고 자식 클래스에게 매핑 정보만 제공하고 싶은 경우, @MappedSuperclass 를 사용한다.
    @MappedSuperclass 는 실제 테이블과는 매핑되지 않는다. (@Entity는 실제 테이블과 매핑된다.)

     

    예제 코드

    @MappedSuperClass
    public abstract class BaseEntity {
    	
        @Id @GeneratedValue
        private Long id;
        private String name;
        ...
    }
    
    @Entity
    public class Member extends BaseEntity {
    	
        private String email;
        ...
    }
    
    @Entity
    public class Seller extends BaseEntity {
    	
        private String shopName;
        ...
    }

    BaseEntity는 테이블과 매핑할 필요 없이 자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하면 된다.

     

    (자식 클래스의) 매핑 정보 재정의

    @AttributeOverrides 혹은 @AttributeOverride 를 사용한다.

     

    속성 1개 재정의

    @Entity
    @AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID"))
    public class Member extends BaseEntity { ... }

     

    2개 이상의 속성 재정의

    @Entity
    @AttributeOverrides({
    	@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
        @AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))
    })
    public class Member extends BaseEntity { ... }

     

    (자식 클래스의) 연관관계 재정의

    @AssociationOverrides 혹은 @AssociationOverride 를 사용한다.

     

    특징
    • 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용한다.
    • @MappedSuperclass 로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPQL에서 사용할 수 없다.
    • 이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 만드는 것을 권장한다.

     

    @MappedSuperclass 를 사용하면 등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리할 수 있다.

     

    3. 복합 키와 식별 관계 매핑

    3.1. 식별 관계 vs 비식별 관계

    데이터베이스 테이블 사이의 관계는 외래 키가 기본 키에 포함되어 있는지 여부에 따라 식별 관계와 비식별 관계로 구분한다.

    식별 관계 (Identifying Relationship)

    부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 관계.

    비식별 관계 (Non-Identifying Relationship)

    부모 테이블의 기본 키를 받아서 자식 테이블의 외래 키로만 사용하는 관계.
    외래 키에 NULL을 허용하는지 여부에 따라 필수적 비식별 관계와 선택적 비식별 관계로 나눈다.

    필수적 비식별 관계 (Mandatory): 외래 키에 NULL을 허용하지 않는다.
    선택적 비식별 관계 (Optional): 외래 키에 NULL을 허용한다.

    최근에는 비식별 관계를 주로 사용하고 꼭 필요한 곳에만 식별 관계를 사용하는 추세다. (JPA는 두 관계 모두 지원한다)

     

    3.2. 복합 키: 비식별 관계 매핑

    JPA에서 두 개 이상의 식별자 필드(복합 키)를 사용하려면 별도의 식별자 클래스를 만들고 그곳에 equals와 hashCode를 구현해야 한다.
    영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자를 키로 사용하고, 이때 식별자를 비교할 때 eqauls와 hashCode를 사용하기 때문이다.

    복합 키를 사용하기 위한 방법은 2가지가 있다.

    @IdClass: 관계형 데이터베이스에 가까운 방법
    @EmbeddedId: 좀 더 객체지향에 가까운 방법

     

    복합 키 테이블 (비식별 관계)

    @IdClass

    부모 클래스와 식별자 클래스는 다음과 같이 구현할 수 있다.

    @Entity
    @IdClass(ParentId.class)
    public class Parent {
    	
        @Id @Column(name = "PARENT_ID1")
        private String id1; // ParentId.id1 과 연결
        
        @Id @Column(name = "PARENT_ID2")
        private String id2; // ParentId.id2 과 연결
        
        private String name;
        ...
    }
    
    public class ParentId implements Serializable {
    	
        private String id1; //Parent.id1 매핑
        private String id2; //Parent.id2 매핑
        
        public ParentId() {}
        
        public ParentId(String id1, String id2) {
        	this.id1 = id1;
            this.id2 = id2;
        }
        
        @Override
        public boolean equals(Object o) { ... }
        
        @Override
        public int hashCode() { ... }
    }
    @IdClass 사용 시, 식별자 클래스의 조건
    • 식별자 클래스(ParentId)의 속성명과 엔티티(Parent)에서 사용하는 식별자의 속성명이 같아야 한다.
    • Serializable 인터페이스를 구현해야 한다.
    • equals, hashCode를 구현해야 한다.
    • 기본 생성자가 있어야 한다.
    • 식별자 클래스는 public 이어야 한다.

     

    자식 클래스는 다음과 같이 구현할 수 있다.

    @Entity
    public class Child {
    	
        @Id
        private String id;
        
        @ManyToOne
        @JoinColumns({
        	@JoinColumn(name = "PARENT_ID1",
            	referencedColumnName = "PARENT_ID1"),
            @JoinCOlumn(name = "PARENT_ID2",
            	referencedColumnName = "PARENT_ID2")
        })
        private Parent parent;
    }

    부모 테이블의 기본 키가 복합 키이므로 자식 테이블에서 외래 키 매핑 시, @JoinColumns 어노테이션을 사용하고 각각의 외래 키 컬럼을 @JoinColumn으로 매핑한다.

    참고) 예제처럼 @JoinColumn의 name 속성과 referencedColumnName 속성의 값이 같으면 referencedColumnName은 생략 가능하다.

     

    @EmbeddedId

    @Entity
    public class Parent {
    	
        @EmbeddedId
        private ParentId id;
        
        private String name;
        ...
    }
    
    @Embeddable
    public class ParentId implements Serializable {
    	
        @Column(name = "PARENT_ID1")
        private String id1;
        
        @Column(name = "PARENT_ID2")
        private String id2;
        
        //equals, hashCode 구현
        ...
    }

    @EmbeddedId를 적용한 식별자 클래스는 식별자 클래스에 기본 키를 직접 매핑한다.

    @EmbeddedId 사용 시, 식별자 클래스의 조건
    • @Embeddable 어노테이션을 붙여주어야 한다.
    • Serializable 인터페이스를 구현해야 한다.
    • equals, hashCode를 구현해야 한다.
    • 기본 생성자가 있어야 한다.
    • 식별자 클래스는 public 이어야 한다.

     

    @EmbeddedId 가 @IdClass 보다 객체지향적이고 중복도 없어서 좋아보이긴 하지만, 특정 상황에 JPQL이 조금 더 길어질 수 있다.

    참고) 복합 키에는 @GeneratedValue를 사용할 수 없다. 복합 키를 구성한느 여러 컬럼 중 하나에도 사용할 수 없다.

     

    3.3. 복합 키: 식별 관계 매핑

    식별 관계 구현

    식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해서 복합 키를 구성해야 하므로 @IdClass나 @EmbeddedId를 사용해서 식별자를 매핑해야 한다. (일대일 관계는 약간 다르다)

     

    @IdClass 와 식별 관계

    식별 관계는 기본 키와 외래 키를 같이 매핑해야 한다.
    따라서 식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 같이 사용하면 된다.

    //부모
    @Entity
    public class Parent {
    	
        @Id @Column(name = "PARENT_ID")
        private String id;
        private String name;
        ...
    }
    
    //자식
    @Entity
    @IdClass(ChildId.class)
    public class Child {
    	
        @Id
        @ManyToOne
        @JoinColumn(name = "PARENT_ID")
        private Parent parent;
        
        @Id @Column(name = "CHILD_ID")
        private String childId;
        
        private String name;
        ...
    }
    
    // 자식 ID
    public class ChildId implements Serializable {
    	
        private String parent;  //Child.parent 매핑
        private String childId; //Child.childId 매핑
        
        //equals, hashCode
        ...
    }
    
    //손자
    @Entity
    @IdClass(GrandChildId.class)
    public class GrandChild {
    	
        @Id
        @ManyToOne
        @JoinColumns({
        	@JoinColumn(name = "PARENT_ID"),
            @JoinColumn(name = "CHILD_ID")
        })
        private Child child;
        
        @Id @Column(name = "GRANDCHILD_ID")
        private String id;
        
        private String name;
        ...
    }
    
    // 손자 ID
    public class GrandChildId implements Serializable {
    	
        private ChildId child; //GrandChild.child 매핑
        private String id;     //GrandChild.id 매핑
        
        //equals, hashCode
        ...
    }

     

    @EmbeddedId 와 식별 관계

    @EmbeddedId로 식별 관계를 구성할 때는, 식별 관계로 사용할 연관관계 속성에 @MapsId를 사용하면 된다.

    @MapsId 는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻이다. (식별 관계 표현)
    속성 값으로 식별자 클래스의 기본 키 필드를 지정하면 된다.

    //부모
    @Entity
    public class Parent {
    	
        @Id @Column(name = "PARENT_ID")
        private String id;
        private String name;
        ...
    }
    
    //자식
    @Entity
    public class Child {
        
        @EmbeddedId
        private ChildId id;
        
        @MapsId("parentId") //ChildId.parentId 매핑
        @ManyToOne
        @JoinColumn(name = "PARENT_ID")
        private Parent parent;
        
        private String name;
        ...
    }
    
    // 자식 ID
    @Embeddable
    public class ChildId implements Serializable {
    	
        private String parentId;  //@MapsId("parentId")로 매핑
        
        @Column(name = "CHILD_ID")
        private String id;
        
        //equals, hashCode
        ...
    }
    
    //손자
    @Entity
    public class GrandChild {
    	
        @EmbeddedId
        private GrandChildId id;
    	
        @MapsId("childId") //GrandChildId.childId 매핑
        @ManyToOne
        @JoinColumns({
        	@JoinColumn(name = "PARENT_ID"),
            @JoinColumn(name = "CHILD_ID")
        })
        private Child child;
        
        private String name;
        ...
    }
    
    // 손자 ID
    @Embeddable
    public class GrandChildId implements Serializable {
    	
        private ChildId childId; //@MapsId("childId")로 매핑
        
        @Column(name = "GRANDCHILD_ID")
        private String id;
        
        //equals, hashCode
        ...
    }

     

    3.4. 비식별 관계로 구현

    비식별 관계 구현

    식별 관계의 복합 키를 사용한 코드와 비교하면 매핑도 쉽고 코드도 단순하며, 복합 키 클래스를 만들지 않아도 된다.

    //부모
    @Entity
    public class Parent {
    	
        @Id @GeneratedValue
        @Column(name = "PARENT_ID")
        private Long id;
        
        private String name;
        ...
    }
    
    //자식
    @Entity
    public class Child {
    	
        @Id @GeneratedValue
        @Column(name = "CHILD_ID")
        private Long id;
        
        private String name;
        
        @ManyToOne
        @JoinColumn(name = "PARENT_ID")
        private Parent parent;
        ...
    }
    
    //손자
    @Entity
    public class GrandChild {
    	
        @Id @GeneratedValue
        @Column(name = "GRANDCHILD_ID")
        private Long id;
        
        private String name;
        
        @ManyToOne
        @JoinColumn(name = "CHILD_ID")
        private Child child;
        ...
    }

     

    3.5. 일대일 식별 관계

    식별 관계 일대일

    일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만 사용한다.
    그래서 부모 테이블의 기본 키가 복합 키가 아니면 자식 테이블의 기본 키도 복합 키로 구성하지 않아도 된다.

    //부모
    @Entity
    public class Board {
    	
        @Id @GeneratedValue
        @Column(name = "BOARD_ID")
        private Long id;
        
        private String title;
        
        @OneToOne(mappedBy = "board")
        private BoardDetail boardDetail;
    	...
    }
    
    //자식
    @Entity
    public class BoardDetail {
    	
        @Id 
        private Long boardId;
        
        @MapsId //BoardDetail.boardId 매핑 - 식별자가 컬럼 하나일 경우 속성 값을 비워두어도 된다.
        @OneToOne
        @JoinColumn(name = "BOARD_ID")
        private Board board;
        
        private String content;
    	...
    }

    여기서 @MapsId는 @Id를 사용해서 식별자로 지정한 BoardDetail.boardId와 매핑된다.

     

    3.6. 식별, 비식별 관계의 장단점

    비식별 관계의 장점

    데이터베이스 설계 관점)
    • 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어난다.
      결과적으로 조인할 때 SQL이 복잡해지고, 기본 키 인덱스가 불필요하게 커질 수 있다.
    • 식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 많다.
    • 식별 관계를 사용할 때 기본 키로 비즈니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많다.
      반면에, 비식별 관계의 기본 키는 비즈니스와 전혀 관계없는 대리 키를 주로 사용한다.
      비즈니스 요구사항은 시간이 지나 변하게 되는데, 식별 관계의 자연 키 컬럼들이 자식에 손자까지 전파되면 변경하기 힘들다.
    • 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못하다.

    객체 관계 매핑 관점) 
    • 일대일 관계를 제외한 식별 관계는 복합 기본 키를 사용하므로, 복합 키 클래스를 별도로 만들어야 하고 많은 노력이 필요하다.
    • 비식별 관계의 기본 키는 주로 대리 키를 사용하는데 JPA는 @GeneratedValue처럼 대리 키를 생성하기 위한 편리한 방법을 제공한다.

     

    식별 관계의 장점

    • 기본 키 인덱스를 활용하기 좋다
    • 상위 테이블들의 기본 키 컬럼을 자식, 손자 테이블들이 가지고 있으므로 특정 상황에 조인 없이 하위 테이블만으로 검색을 완료할 수 있다.

     

    정리

    비식별 관계를 사용하는 것을 추천한다.
    • 대리 키는 비즈니스와 아무 관련이 없어 비즈니스가 변경되어도 유연한 대처가 가능하다.
    • JPA는 @GeneratedValue를 통해 간편하게 대리 키를 생성할 수 있다.
    • 식별자 컬럼이 하나여서 쉽게 매핑할 수 있다.

     

    식별자의 데이터 타입은 Long을 추천한다.
    • 자바에서 Integer는 최대 2,147,483,647 (약 20억) 까지 저장할 수 있어 데이터가 많아지면 문제가 발생 할 수 있다.
    • 반면에 Long은 9,223,372,036,854,775,807 (약 920경)까지 저장할 수 있으므로 안전한다.

     

    선택적 비식별 관계보다는 필수적 비식별 관계를 사용하는 것이 좋다.
    • 선택적 비식별 관계는 NULL을 허용하므로 조인할 때에 외부 조인을 사용해야 한다.
    • 반면에 필수적 관계는 NOT NULL로 항상 관계가 있다는 것을 보장하므로 내부 조인만 사용해도 된다.

     

    4. 조인 테이블

    데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지가 있다.

    1. 조인 컬럼 사용 (외래 키)
    2. 조인 테이블 사용 (테이블 사용)

     

    1. 조인 컬럼 사용

     

    2. 조인 테이블 사용

     

     

    4.1. 일대일 조인 테이블

    조인 테이블 일대일

     

    // 부모
    @Entity
    public class Parent {
    
        @Id
        @GeneratedValue
        @Column(name = "PARENT_ID")
        private Long id;
    
        private String name;
    
        @OneToOne
        @JoinTable(name = "PARENT_CHILD",
                joinColumns = @JoinColumn(name = "PARENT_ID"),
                inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
        )
        private Child child;
        ...
    }
    
    //자식
    @Entity
    public class Child {
    
        @Id
        @GeneratedValue
        @Column(name = "CHILD_ID")
        private Long id;
    
        private String name;
    
        @OneToOne(mappedBy = "child")
        private Parent parent;
        ...
    }

    @JoinColumn 대신에 @JoinTable을 사용했다.

    @JoinTable 속성
    • name: 매핑할 조인 테이블 이름
    • joinColumns: 현재 엔티티를 참조하는 외래 키
    • inverseJoinColumns: 반대방향 엔티티를 참조하는 외래 키

    양방향 매핑 시 다음 코드를 추가한다.

    public class Child {
        ...
        @OneToOne(mappedBy = "child")
        private Parent parent;
    }

     

    4.2. 일대다 조인 테이블

    조인 테이블의 컬럼 중 다(N)쪽 컬럼에 유니크 제약조건을 걸어야 한다. 
    여기서는 CHILD_ID 컬럼에 유니크 제약조건을 걸어야 하고, CHILD_ID는 기본 키이므로 유니크 제약조건이 걸려 있다. 

    조인 테이블 일대다, 다대일

    //부모
    @Entity
    public class Parent {
    
        @Id
        @GeneratedValue
        @Column(name = "PARENT_ID")
        private Long id;
    
        private String name;
    
        @OneToMany
        @JoinTable(name = "PARENT_CHILD",
                joincolumns = @JoinColumn(name = "PARENT_ID"),
                inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
        )
        private List<Child> children = new ArrayList<>();
        ...
    }
    
    //자식
    @Entity
    public class Child {
    
        @Id
        @GeneratedValue
        @Column(name = "CHILD_ID")
        private Long id;
    
        private String name;
        ...
    }

     

    4.3. 다대일 조인 테이블

    일대다에서 방향만 반대이므로 조인 테이블 모양은 앞의 그림과 같다.

    @Entity
    public class Parent {
    
        @Id
        @GeneratedValue
        @Column(name = "PARENT_ID")
        private Long id;
    
        private String name;
    
        @OneToMany(mappedBy = "parent")
        private List<Child> children = new ArrayList<>();
        ...
    }
    
    @Entity
    public class Child {
    
        @Id
        @GeneratedValue
        @Column(name = "CHILD_ID")
        private Long id;
    
        private String name;
    
        @ManyToOne(optional = false)
        @JoinTable(name = "PARENT_CHILD",
                joincolumns = @JoinColumn(name = "CHILD_ID"),
                inverseJoinColumns = @JoinColumn(name = "PARENT_ID")
        )
        private Parent parent;
        ...
    }

     

    4.4. 다대다 조인 테이블

    조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약조건을 걸어야 한다.
    여기서는 PARENT_ID, CHILD_ID가 복합 기본 키이므로 유니크 제약조건이 걸려 있다.

    조인 테이블 다대다

     

    //부모
    @Entity
    public class Parent {
    
        @Id
        @GeneratedValue
        @Column(name = "PARENT_ID")
        private Long id;
    
        private String name;
    
        @ManyToMany
        @JoinTable(name = "PARENT_CHILD",
                joincolumns = @JoinColumn(name = "PARENT_ID"),
                inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
        )
        private List<Child> children = new ArrayList<>();
        ...
    }
    
    //자식
    @Entity
    public class Child {
    
        @Id
        @GeneratedValue
        @Column(name = "CHILD_ID")
        private Long id;
    
        private String name;
        ...
    }

     

    참고)
    조인 테이블에 컬럼을 추가하면 @JoinTable 전략을 사용할 수 없다.
    대신에 새로운 엔티티를 만들어서 조인 테이블과 매핑해야 한다. 
    자세한 내용은 이전 글을 참고하자.

    2022.04.30 - [Back-End/JPA] - 6. 다양한 연관관계 매핑

     

    5. 엔티티 하나에 여러 테이블 매핑

    잘 사용하지는 않지만 @SecondaryTable을 사용하면 한 엔티티에 여러 테이블을 매핑할 수 있다.

    하나의 엔티티에 여러 테이블 매핑하기

     

    @Entity
    @Table(name = "BOARD")
    @SecondaryTable(name = "BOARD_DETAIL",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_DETAIL_ID"))
    public class Board {
    
        @Id
        @GeneratedValue
        @Column(name = "BOARD_ID")
        private Long id;
    
        private String title;
    
        @Column(table = "BOARD_DETAIL")
        private String content;
        ...
    }

    @Table 을 사용해서 Board 테이블과 매핑했다.
    그리고 @SecondaryTable을 사용해서 BOARD_DETAIL 테이블을 추가로 매핑했다.

    @SecondaryTable 속성
    • name: 매핑할 다른 테이블의 이름.
    • pkJoinColumns: 매핑할 다른 테이블의 기본 키 컬럼 속성.

    더 많은 테이블을 매핑하려면 @SecondaryTables 를 사용한다.

    728x90

    'Back-End > JPA' 카테고리의 다른 글

    9. 값 타입 (1)  (0) 2022.06.09
    8. 프록시와 연관관계 관리  (0) 2022.06.07
    6. 다양한 연관관계 매핑  (0) 2022.04.30
    5. 연관관계 매핑 기초  (0) 2022.04.23
    4. 엔티티 매핑  (0) 2022.04.17

    댓글

Designed by Tistory.