자라선

[자바 ORM 표쥰 JPA 프로그래밍] 14일차 - 복합키 매핑과 1:1 매핑 본문

Develop/JPA

[자바 ORM 표쥰 JPA 프로그래밍] 14일차 - 복합키 매핑과 1:1 매핑

자라선 2021. 8. 30. 17:21

 

복합키란 PK가 2개 이상 있는 테이블의 키를 말한다.

 

PARENT 테이블에는 PK가 2개가 있으며 이와 관계를 맺는 CHILD 에서는 2개의 PK를 참조하고 있다.

이럴 경우 JPA는 2가지의 방식으로 매핑 방식을 지원해주고 있다.

 

1. @IdClass

데이터베이스 관점으로 하여 복합키의 매핑을 사용할때 작성하는 어노테이션

@Data
public class ParentId implements Serializable { // 식별클래스는 Serializable 을 필수로 상속해야한다.

    // 식별클래스의 필드와 복합키 엔티티의 PK 필드 명은 무조건 같아야한다.
    private long parentId1; 
    private long parentId2;

}

@Data
@IdClass(ParentId.class)    // 식별클래스를 명시
@Entity
public class Parent {

    @Id // @IdClass 어노테이션없이 @Id를 2개 작성하면 런타임에러가 뜬다.
    @Column(name = "PARENT_ID1")
    private long parentId1;

    @Id
    @Column(name = "PARENT_ID2")
    private long parentId2;

}

@Data
@Entity
public class Child {

    @Id
    @GeneratedValue
    @Column(name = "CHILD_ID")
    private long id;

    // 복합키 매핑
    @ManyToOne
    @JoinColumns({  // 조인 맺을 여러 컬럼을 명시해준다.
            @JoinColumn(name = "PARENT_ID1"),
            @JoinColumn(name = "PARENT_ID2")
    })
    private Parent parent;

}

 

복합키를 매핑하기 위해서는 식별클래스를 추가로 구현해야한다.

 

ParentId 라는 클래스를 구현하여 Parent의 복합키 식별 클래스로 만들었고

Parent 엔티티에 @IdClass로 하여 명시해주었다.

 

여기서 중요한점은 식별 클래스와 엔티티의 PK 필드명을 일관화 시켜야한다.

 

그후 이를 조인을 맺을 엔티티에서는 @JoinColumns 라고하여 2개이상의 조인 맺을 컬럼을 정의하여 마친다.

 

식별클래스 구현의 주의점

  • 엔티티와 식별클래스의 필드명을 일치시켜야한다.
  • Serializable 인터페이스를 상속받아야한다.
  • equals, hashCode 를 구현해야한다.
  • 기본 생성자가 있어야한다.
  • 식별자 클래스는 무조건 public 이어야만 한다.

대부분 별도로 정의하지않으면 문제될것은 없기 때문에 Serializable 만 작성에 주의해주면 될듯하다.

    public static void login(EntityManager em){

        // INSERT DATA
        Parent parent = new Parent();
        parent.setParentId1(1l);
        parent.setParentId2(2l);
        em.persist(parent);

        Child child = new Child();
        child.setParent(parent);
        em.persist(child);

        // SELECT DATA
        ParentId parentId= new ParentId();
        parentId.setParentId1(1l);
        parentId.setParentId2(2l);
        
        final Parent parent1 = em.find(Parent.class, parentId);
        System.out.println("parent1 = " + parent1);
    }

조회할때는 식별클래스를 정의하여 PK를 사용하면 된다.

 

2. @EmbeddedId

이 어노테이션은 객체지향적인 방식으로 PK를 매핑해준다.

@Entity
public class Parent {
    
    // @Id가 아닌 @EmbeddedId 를 정의해주어야한다.
    @EmbeddedId
    private ParentId id;    // 식별클래스를 타입으로 해야함

}


@Data
@Embeddable // 식별클래스에 @Embeddable 를 필수로 사용해야한다.
public class ParentId implements Serializable {

    // 식별클래스의 필드와 복합키 엔티티의 PK 필드 명은 무조건 같아야한다.
    @Column(name = "PARENT_ID1")
    private long parentId1;

    @Column(name = "PARENT_ID2")
    private long parentId2;

}

@Entity
public class Child {
	... @IdClass와 동일함
}

@IdClass 와 비슷하지만 엔티티의 PK를 아예 식별클래스로 정의했다.

 

그리고 식별클래스에서 컬럼 속성을 정의하여 한꺼번에 식별클래스에서 조정하는것으로 되어있다.

이렇게 보면 객체로 하여 한꺼번에 관리할수있어서 편하겠지만 

복합키를 조회하기 위해서 식별클래스를 한단계 더 들어가야하는 등 불편함이 있는듯하다.

        // INSERT DATA
        Parent parent = new Parent();

        // 복합키용 식별클래스를 인스턴스로 구현
        ParentId parentId= new ParentId();
        parentId.setParentId1(2l);
        parentId.setParentId2(3l);

        // 식별클래스를 PK로 사용한다.
        parent.setId(parentId);
        em.persist(parent);

사용할때의 한가지 차이점은 엔티티의 복합키의 타입이 변경되었기 때문에

복합키인 식별클래스를 구현하여 넣어주어야한다는 점이다.

 

 

+ 추가

@Entity
public class Child {

    @EmbeddedId
    private ChildId id;

    @MapsId("parentId") // 식별 클래스에 있는 조인시킬 필드명을 작성한다.
    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    public Parent parent;

    @Column(name = "CHILD_NAME")
    private String name;
}

추가로 만약에 @EmbeddedId 어노테이션을 사용하여 매핑을 하려할때는

조인하려고 하면 엔티티에 PK필드가 존재하지 않는다.

 

그래서 조인을 하기 위한 필드를 추가하고 @MapsId 어노테이션을 추가하여

식별클래스에 있는 조인시킬 필드명을 작성하여준다.

 

3. 일대일 매핑

BOARD_ID - BOARD_ID

서로 PK끼리 1:1 매핑되는 경우는 @OneToOne 어노테이션을 사용한다.

@Data
@Entity
public class Board {

    @Id
    @Column(name = "BOARD_ID")
    private long id;

    @Column(name = "BOARD_NAME")
    private String name;

    @OneToOne(mappedBy = "board")   // 양방향 조인을 위해 조인한 필드를 작성
    private BoardDetail boardDetail;

}

@Data
@Entity
public class BoardDetail {

    @Id
    @Column(name = "BOARD_ID")
    private long id;

    @MapsId // BoardDetail.id 매핑
    @OneToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;

    private String contents;
}

외래키가 있는 BoardDetail에는 @JoinColumn 으로 Board.BOARD_ID 와 조인하였고

@MapsId 어노테이션으로 PK를 BoardDetail.id 와 매핑시켜주었다.

 

따로 속성값을 추가하지 않은 이유는 PK가 1개 뿐이라 자동으로 매핑시켜주기 때문이다.

 

Board 엔티티는 @OneToOne(mappedBy = "board") 로 서로 매핑할 필드를 지정해주었다.

    public static void login(EntityManager em){

        Board board = new Board();
        board.setName("책");
        em.persist(board);

        BoardDetail boardDetail = new BoardDetail();
        boardDetail.setContents("내용");
        boardDetail.setBoard(board);
        em.persist(boardDetail);
    }

INSERT 시 쿼리 이다. 별 다를게 없다.

Comments