[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편 06-07 다양한 연관관계 매핑, 고급 매핑
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의
김영한님의 인프런 강의(자바 ORM 표준 JPA 프로그래밍 - 기본편) 을 수강하면서 강의 내용을 일부 발췌해 요약한 글.
섹션 6 다양한 연관관계 매핑
연관관계 매핑시 고려사항
연관관계 매핑 시에는 다음의 세 가지를 고려해야 한다.
- 다중성
다대일, 일대다, 일대일, 다대다. jpa에서 annotation을 제공하고 있다. 이는 모두 DB와 매핑하기 위한 것이며 데이터베이스 관점에서의 다중성을 기준으로 고민하면 된다. 헷갈리면 대칭성을 이용해 반대로 생각해보자!
- 단방향, 양방향
테이블은 외래키 하나로 양족 조인이 가능하므로 방향이라는 개념이 없다. 반면 객체는 참조용 필드가 있는 쪽으로만 참조가 가능하다. 한쪽만 참조하는 경우는 단방향이고 양쪽이 서로 참조하면 양방향이다.
- 연관관계의 주인
양방향의 경우 테이블의 외래 키와는 달리, 사실상 객체의 양방향은 단방향 2개의 형태이다. 따라서 둘중 외래키를 관리할 곳을 지정해주어야 하는데 이 곳이 연관관계의 주인이 되는 곳이다.
다대일 [N:1]
다대일 단방향은 다쪽에 외래 키가 있어야 한다. 외래키가 있는 곳에 참조를 걸고 연관관계 매핑을 한다. 가장 많이 사용하는 연관관계이며 반대의 경우는 일대다이다.
다대일 양방향이 필요한 경우, 외래 키가 있는 쪽이 연관관계의 주인이며 반대쪽 객체에 읽기 전용으로 객체를 추가하고, mappedBy 옵션을 반드시 지정해주어야 한다.
일대다 [1:N]
일대다 단방향은 일이 연관관계의 주인임을 의미하는데, 테이블 일대다 관계는 항상 다쪽에 외래 키가 있다. 결국 반대편 테이블의 외래 키를 관리하는 특이한 구조인 것이다. 일대다의 경우 @JoinColumn을 사용하지 않으면 조인 테이블이 생성되므로 반드시 @JoinColumn을 넣어주자. 외래 키가 다른 테이블에 있으며 연관관계 관리를 위해 추가로 UPDATE SQL을 실행하게 되므로 비효율적인 방식이다. 일대다보다는 다대일 양방향 매핑을 사용하는 것을 권장한다.
@JoinColumn(insetable=false. updatable=false) 옵션으로 읽기 전용 필드를 만들면 일대다 양방향도 가능하긴 하나 공식적으로 존재하는 매핑은 아니다.
일대일 [1:1]
주 테이블이나 대상 테이블 중에 외래 키 선택이 가능하다. 다대일 단방향 매핑과 유사하며 외래 키가 있는 곳이 연관관계의 주인이고 반대쪽에 mappedBy를 적용해주면 된다. 대상 테이블에 외래 키가 있는 단방향인 경우에는 JPA에서 지원하지 않으며 양방향의 경우에만 가능하다. 내 엔티티에 있는 외래 키는 직접 관리해야 한다고 생각하자.
정리하자면 주 테이블에 외래 키를 두고 대상 테이블을 찾는 방식은 객체지향 개발자들이 선호하는 방식이며 JPA 매핑이 편리하다. 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인이 가능하다는 장점이 있으나 값이 없으면 외래 키에 Null을 허용해야 하므로 DB의 입장에서는 치명적일 수 있다. 반면 대상 테이블에 외래 키가 존재하는 경우는 전통적인 DB 개발자들이 선호하는 방식이며 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조가 유지되는 장점이 있으나 JPA 입장에서는 양방향으로 만들어야 하고, 프록시 기능의 한계로 지연 로딩으로 설정하더라도 즉시 로딩된다는 단점이 있다.
다대다 [N:N]
권장하지 않는 방식이다. 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없어 연결 테이블을 추가해 일대다, 다대일 관계로 풀어내야 한다. 한편 객체는 컬렉션을 사용해 객체 2개로 다대다 관계가 가능하다.
편리해보이지만 실무에서는 사용하는 방식이 아니다. 중간 테이블에 추가 정보를 넣을 수 없고 쿼리도 생각하지 못한 쿼리가 나갈 수 있으므로 연결 테이블용 엔티티를 추가해 테이블을 엔티티로 승격하는 방식을 사용하는 것이 좋다.
섹션 7 고급 매핑
상속 관계
관계형 데이터베이스는 상속 관계가 없고, 슈퍼타입-서브타입 관계라는 모델링 기법이 객체 상속과 유사하다. 결론적으로 상속관계 매핑이란 객체의 상속, 구조와 DB의 슈퍼타입-서브타입 관계를 매핑하는 것이라고 할 수 있다.
슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법은 크게 세 가지로 각각 테이블로 변환->조인 전략, 통합 테이블로 변환 -> 단일 테이블 전략, 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략이 있다. JPA에서는 다 매핑이 가능하며 주요 어노테이션은 다음과 같다.
- @Inheritance(strategy=InheritanceType.XXX)
- JOINED 조인 전략
- SINGLE_TABLE 단일 테이블 전략
- TABLE_PER_CLASS 구현 클래스마다 테이블 전략
@DiscriminatorColumn(name=”DTYPE”) 단일 테이블 전략에서는 필수로 셍성된다. 운영상 꼭 있는 것이 좋다.
- @DiscriminatorValue(“XXX”) 이름 바꾸기
장리하면,
조인 전략 - 정규화되어 있다. 외래 키 참조 무결성 제약 조건 활용이 가능하며 저장공간이 효율화된다는 장점이 있으나 조회 쿼리가 복잡하고 조회시 조인을 많이 사용한다. 단일 테이블에 비해 복잡한 것이 단점이나 유의미한 정도의 성능 저하는 아니다. 전략 중 정석이라고 할 수 있다.
단일 테이블 전략 - 조인이 필요 없으므로 조회 성능이 빠르고 조회 쿼리가 단순하다. 그러나 null을 모두 허용해야 하고, 단일 테이블에 모든 것을 저장하므로 테이블이 커져 오히려 조회 성능이 느려질 수 있다.
구현 클래스마다 테이블 전략 - 서브 타입을 명확하게 구분해서 처리할 때 효과적이며 not null 제약조건 사용이 가능하나 이 전략은 사용하지 않는 것을 권장한다. 시스템 통합, 쿼리가 어렵고 여러 자식 테이블을 함께 조회할 때 성능이 느리다(UNION)
조인 전략을 기본으로 두고 매우 단순한 경우에만 단일 테이블 전략을 사용하는 방식을 권장하며 Trade-off를 잘 고민해 봐야 한다.
Mapped Superclass
@MappedSuperclass 는 공통 매핑 정보가 필요할 때 사용한다. 예를 들어 모든 객체가 id, name이라는 공통 속성이 있어 이를 상속받아 사용하고자 할 때 사용할 수 있고 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다. 상속관계 매핑과 관련이 없으며, 엔티티도 아니고 테이블과 매핑되지도 않는다. 조회나 검색이 불가하며, 직접 생성해 사용할 일이 없으므로 추상 클래스로 생성하는 것을 권장한다.