스프링 핵심 원리 - 기본편 - 인프런
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 초급 프레임워크 및 라이브러리 웹 개발 서버 개발 Back-End Spring 객체지향 온
www.inflearn.com
강의를 들으며 생각 정리 + "자바 ORM 표준 JPA 프로그래밍" 책 참고
앞서 배운 것을 정리해보자.
엔티티의 연관관계를 매핑할 때는 다음 3가지를 고려해야 한다.
- 다중성
- 단방향, 양방향
- 연관관계의 주인
다중성
- 다대일: @ManyToOne
- 일대다: @OneToMany
- 일대일: @OneToOne
- 다대다: @ManyToMany
보통 다대일 관계를 가장 많이 사용한다.
단방향, 양방향
- 테이블은 외래 키 하나로 조인을 사용해서 양방향으로 쿼리가 가능하다.
- 객체는 참조용 필드가 있는 쪽으로만 참조가 가능하다. 한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향이다.
연관관계의 주인
- 테이블의 연관관계를 관리하는 포인트는 외래 키 하나다.
- 양방향인 경우, 객체의 연관관계를 관리하는 포인트는 2곳이다.
따라서 두 객체 연관관계중 하나를 정해 데이터베이스 외래 키를 관리하는데 이것을 연관관계의 주인이라 한다. 일반적으로 외래 키를 가진 테이블과 매핑한 엔티티를 연관관계의 주인으로 선택한다. 주인이 아닌 방향은 외래 키를 변ㄴ경할 수 없고 읽기만 가능하다.
지금부터 다중성, 단방향, 양방향을 고려한 가능한 모든 연관관계를 하나씩 알아보자.
다대일 [N:1]
다대일 단방향
JPA에서 가장 많이 사용하는 연관관계이다. 이전 포스팅인 연관관계 매핑 기초에서 자세히 공부했기 때문에 간단히 넘어간다.
다대일 양방향
양방향은 외래 키가 있는 쪽이 연관관계의 주인이다.
양방향 연관관계는 항상 서로를 참조해야 한다. -> 연관관계 편의 메소드를 사용하면 편리하다.
단방향만 매핑해도 연관관계는 완성된다. 양방향은 상대 객체의 조회 기능을 추가하는 것이다.
일대다 [1:N]
여기서 일대다 관계라는 것은 일 쪽이 외래 키를 관리하는 상태라고 본다.
일대다 단방향
팀이 멤버를 참조하지만 멤버에서 팀을 참조할 일이 없다면 위와 같은 그림이 나온다.
다대일, 일대다 관계에서는 다 쪽에 외래 키가 있기 때문에 상대 객체의 테이블의 외래 키를 참조하는 특이한 모습이다.
팀 객체에 @JoinColumn(name = "TEAM_ID")을 추가하면 멤버 테이블에 TEAM_ID 외래 키가 생성된다.
단점
매핑한 객체가 관리하는 외래 키가 다른 테이블에 있기 때문에 데이터베이스에 멤버 -> 팀 순으로 저장할 때를 생각해보자. 처음 저장된 멤버는 자신의 외래 키인 TEAM_ID를 관리하지 않기 때문에 팀이 저장될 때까지 TEAM_ID를 모른다. 그래서 이후에 팀을 insert할 때, 따로 멤버에 update 쿼리를 사용해야 한다.
+) 팀을 수정하면 멤버가 update 되기 때문에 혼동이 온다.
일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.
update 쿼리가 더 나간다고 해서 성능상 큰 문제는 없을 수 있지만 테이블이 많아지면 운영 상 복잡해질 수 있다. 설령 멤버에서 팀을 참조할 일이 없더라도 같은 객체의 테이블의 외래 키를 관리하는 것이 좋기 때문에 다대일 양방향 매핑을 사용하는 것이 좋다.
일대다 양방향
이런 매핑은 공식적으로 존재하지 않는다. 다대일, 일대다 관계에서는 다 쪽이 외래 키를 갖고 있기 때문에 대신 다대일 양방향을 사용하면된다. 애초에 @ManyToOne에는 mappedBy 속성이 없다.
그렇다고 일대다 양방향 매핑이 완전히 불가능한 것은 아니다.
일대다 단방향 매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 하나 추가하면 된다.
멤버 객체에 @JoinColumn(insertable=false, updatable=false)을 추가한다. 이러한 읽기 전용 옵션은 데이터를 변경하면 안되는 경우 종종 사용한다. 그러나 이는 너무 복잡한 구조가 된다.
결론 : 다대일 양방향을 사용하자.
일대일 [1:1]
일대일 관계는 주 테이블(많이 access하는 테이블)이나 대상 테이블 중 어느 곳이나 외래 키를 가질 수 있다.
주 테이블에 외래 키
단방향
양방향
단방향, 양방향 모두 다대일 연관관계와 비슷한 구조다. 외래 키가 있는 곳이 연관관계의 주인이다.
대상 테이블에 외래 키
단방향
일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다. 생각해보면 다대일 처럼 외래 키가 무조건 다 쪽에 있는 것이 아니라 어느 쪽에 외래 키를 설정해도 되는 일대일이기 때문에 다른 테이블에 외래 키를 설정하는 방법이 없다.
양방향
일대일 매핑에서 대상 테이블에 외래 키를 두고 싶으면 이렇게 양방향으로 매핑한다. 대상 엔티티를 연관관계의 주인으로 만들어서 대상 테이블의 외래 키를 관리하도록 한다.
사실 대상 테이블에 외래 키를 놓는 것은 주 테이블에 외래 키를 놓는 매핑방법은 같다. 그럼 어떤 방법을 선택해야 할까? 이는 객체지향 개발자와 데이터베이스 개발자 관점에서 볼 수 있다.
주 테이블에 외래 키
객체지향 개발자 선호
JPA 매핑 편리
장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
단점: 값이 없으면 외래 키에 null 허용
대상 테이블에 외래 키
전통적인 데이터베이스 개발자 선호
장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨(프록시는 뒤에서 설명)
다대다 [N:M]
객체는 컬렉션을 사용해서 객체 2개로 다대다 관계를 만들 수 있다.
그런데 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다.
매핑은 @ManytoMany와 @JoinTable을 사용한다. @JoinTable을 사용하면 데이터베이스 상에 연결 테이블을 추가한다. 이 역시 단방향, 양방향 모두 가능하다.
그러나 @ManytoMany는 보통 실무에서 사용하지 않는다. 연결 테이블이 단순히 연결만 하고 끝나지 않기 때문이다. 예를 들어, 회원과 상품의 다대다 매핑일 때, 회원이 상품을 주문하면 연결 테이블에 단순히 주문한 회원 아이디와 상품 아이디만 담고 끝나지 않는다. 보통은 연결 테이블에 주문 수량 컬럼이나 주문한 날짜 같은 컬럼이 더 필요하다.
그래서 다음과 같이 연결 엔티티를 만들고 이곳에 추가한 컬럼들을 매핑한다. 이를 통해 다대다 매핑을 일대다, 다대일 관계로 나눌 수 있다.
연결 테이블에서 멤버, 상품의 FK를 PK로 사용하는 방법이 있지만 다음과 같이 새로운 기본 키를 사용하는 방법도 있다. 이 방법의 장점은 복합 키를 만들지 않아도 되어 간편하고 거의 영구히 쓸 수 있으며 비즈니스에 의존하지 않는다.
실전 예제 3 - 다양한 연관관계 매핑
공부한 예제를 깃허브에 등록한다.
코드 작성 시 배운 점
@JoinColumn에서 name은 FK의 이름이다. FK가 조인할 대상 테이블의 컬럼은 referencedColumnName으로 정한다.
이를 생략하면 대상 테이블의 PK로 자동 지정된다
'java > jpa' 카테고리의 다른 글
[JPA] 프록시와 연관관계 관리 (0) | 2021.01.29 |
---|---|
[JPA] 고급 매핑 (0) | 2021.01.27 |
[JPA] 연관관계 매핑 기초 (0) | 2021.01.26 |
[JPA] 엔티티 매핑 (0) | 2021.01.22 |
[JPA] 영속성 관리 - 내부 동작 방식 (0) | 2021.01.21 |