엔티티 매핑 기초
- 객체의 참조와 테이블의 외래 키 매핑
- 객체는 참조를, 테이블은 외래 키를 사용해서 관계를 맺으므로 객체의 참조와 테이블의 외래 키를 매핑하는 것이 중요함
- 연관관계의 방향에는 단방향, 양방향이 있고 연관관계의 다중성에는 다대일, 일대다, 일대일, 다대다가 있음
- 객체를 양방향 연관관계로 만든 경우에는 연관관계의 주인을 정해야 함
- 단방향 연관관계 (예 : 다대일 단방향 연관관계)
- 회원 객체는 Member.team 필드로 팀 객체와 연관관계를 맺으며 회원 객체와 팀 객체는 단방향 관계를 갖게 됨
그러므로 Member.team 필드를 통해 객체 그래프 탐색을 하여 회원의 팀을 알 수 있지만 반대로 팀의 회원을 알 수 없음 - 회원 테이블은 TEAM_ID 외래 키로 팀 테이블과 연관관계를 맺으며 회원 테이블과 팀 테이블을 양방향 관계를 갖게 됨
그러므로 회원 테이블의 TEAM_ID 외래 키를 통해 회원과 팀을 조인할 수 있고 반대로 팀과 회원을 조인할 수도 있음 - 즉, 객체 연관관계는 언제나 단방향이므로 양방향을 만들고 싶다면 반대쪽 필드에도 필드를 추가해 참조를 보관해야 하지만
테이블은 외래 키 하나로 양방향으로 조인이 가능
- 회원 객체는 Member.team 필드로 팀 객체와 연관관계를 맺으며 회원 객체와 팀 객체는 단방향 관계를 갖게 됨
- 연관관계 매핑 어노테이션
- 외래 키를 매핑하는 @JoinColumn
- 다대일 관계에서 매핑 정보를 위해 사용하는 @ManyToOne
- 단방향 연관관계 사용
- 저장을 위해서는
회원 엔티티가 팀 엔티티를 참조하여 저장하면 JPA는 참조한 팀의 식별자(Team.id)를 외래 키로 사용해 등록 쿼리를 생성 - 조회를 위해서는
객체 연관관계를 사용하여 객체 그래프 탐색을 하거나 객체지향 쿼리인 JPQL을 사용하여 조회 쿼리를 생성 - 수정을 위해서는
불러온 엔티티 값만 변경해두면 트랜잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동해 자동으로 반영됨 - 연관관계 제거를 위해서는
회원이 팀에 소속하지 않도록 연관관계를 제거하는 쿼리를 생성 - 연관된 엔티티 삭제를 위해서는
외래 키 제약조건 오류를 피하기 위해 기존에 있던 연관관계를 먼저 제거한 후 삭제하도록 함
- 저장을 위해서는
- 양방향 연관관계 (예 : 다대일 양방향 연관관계)
- 회원 객체는 Member.team 필드로 팀 객체와 다대일 단방향 연관관계를 맺고
팀 객체는 Team.members 필드로 회원 객체와 일대다 단방향 연관관계를 맺게 되며
일대다 관계는 여러 건과 연관관계를 맺을 수 있으므로 컬렉션을 사용하도록 함 - 데이터베이스 테이블은 외래 키 하나로 양방향으로 조회할 수 있으므로
두 테이블은 TEAM_ID 외래 키로 양방향 연관관계를 맺으므로 데이터베이스에 추가할 내용이 없음
- 회원 객체는 Member.team 필드로 팀 객체와 다대일 단방향 연관관계를 맺고
- 연관관계의 주인
- 객체에서 양방향 연관관계는 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 묶어놓은 것이지만
데이터베이스 테이블을 외래 키 하나로 양쪽이 서로 조인할 수 있어 양방향 연관관계를 맺게 됨
그렇기 때문에 엔티티를 양방향으로 매핑하게 될 경우 회원에서 팀, 팀에서 회원 두 곳에서 서로를 참조하므로
객체의 참조는 둘인데 외래 키는 하나가 되는 차이가 발생하게 됨 - 이로 인해 JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래 키를 관리하는 연관관계의 주인을 정해주어야 함
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑되어 외래 키를 관리하여 등록, 수정, 삭제가 가능하고,
주인이 아닌 쪽은 읽기만 할 수 있으므로 주인이 아닌 쪽에 mappedBy 속성을 사용하게 됨 - 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 정해야 하므로
회원 테이블이 외래 키를 가지고 있으므로 Member.team이 주인이 되고,
주인이 아닌 Team.members에는 mappedBy="team" 속성을 사용해 주인이 아님을 설정
- 객체에서 양방향 연관관계는 서로 다른 단방향 연관관계 2개를 애플리케이션 로직으로 묶어놓은 것이지만
- 양방향 연관관계 사용
- 저장을 위해서는
팀 엔티티를 저장한 다음 회원 엔티티는 팀 엔티티의 Member.team 필드를 통해 연관관계를 설정해 저장하게 됨
- 저장을 위해서는
- 양방향 연관관계의 주의점
- 양방향 연관관계를 설정한 후에는 JPA 뿐만 아니라 순수한 객체 상태에서의 문제까지 고려하여
연관관계의 주인과 연관관계의 주인이 아닌 곳 모두에 값을 입력해주는 것이 가장 안전함 - 그러므로 양쪽을 다 신경 써야하는 양방향 연관관계의 경우 모두에 값을 입력해주는 연관관계 편의 메소드를 설정하도록 함
- 양방향 연관관계를 설정한 후에는 JPA 뿐만 아니라 순수한 객체 상태에서의 문제까지 고려하여
다양한 연관관계 매핑
- 다양한 연관관계 매핑
- 엔티티의 연관관계를 매핑할 때는 다중성, 단방향/양방향, 연관관계의 주인 3가지를 고려
- 다중성에는 다대일(@ManyToOne), 일대다(@OneToMany), 일대일(@OneToOne), 다대다(@ManyToMany)가 존재
- 객체 관계에서 한쪽만 참조하는 단방향 관계, 서로 참조하는 양방향 관계가 존재
- JPA는 두 객체 연관관계 중 하나를 정해서 데이터베이스 외래 키를 관리하는 연관관계의 주인이 필요하며
외래 키를 가진 테이블과 매핑한 엔티티가 외래 키를 관리하는 것이 효율적임
- 다대일
- 다대일 관계의 반대 방향은 항상 일대다 관계이고 관계의 반대 방향은 항상 다대일 관계
- 데이터베이스 테이블의 일, 다 관계에서 외래 키는 항상 다쪽에 있으므로
따라서 객체 양방향 관계에서 연관관계의 주인은 항상 다쪽 - 다대일 단방향 / 다대일 양방향
- 일대다
- 일대다 관계는 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중에 하나를 사용해야 함
- 일대다 단방향 매핑은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있어 다른 테이블의 외래 키를 관리해야하므로
연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 함 - 일대다 양방향 매핑은 다대일 양방향 매핑으로 대신 사용하지만 일대다 양방향 매핑이 완전히 불가능한 것이 아니며
일대다 단방향 매핑 반대편에 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 하나 추가하면 됨
하지만 일대다 양방향처럼 보이도록 한 것이기 때문에 일대다 단방향 매핑이 가지는 단점을 그대로 가짐 - 일대다 단방향 / 일대다 양방향
- 일대일
- 일대일 관계는 어느 곳이나 외래 키를 가질 수 있으므로
테이블은 주 테이블이든 대상 테이블이든 외래 키 하나만 있으면 양쪽으로 조회가 가능함
- 일대일 관계는 어느 곳이나 외래 키를 가질 수 있으므로
- 일대일 - 주 테이블에 외래 키
- 객체지향 개발자들이 선호하는 주 테이블에 외래 키는
주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 참조하므로
주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있음 - 일대일 단방향 / 일대일 양방향
- 객체지향 개발자들이 선호하는 주 테이블에 외래 키는
- 일대일 - 대상 테이블에 외래 키
- 데이터베이스 개발자들이 선호하는 대상 테이블에 외래 키는
대상 테이블에 외래 키를 두기 때문에 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지 가능
하지만 일대일 단방향에서 JPA가 지원하지 않으므로 단방향 관계를 Locker에 Member 방향으로 수정하거나
양방향 관계로 만들고 Locker를 연관관계의 주인으로 설정해야 함 - 일대일 단방향 / 일대일 양방향
- 데이터베이스 개발자들이 선호하는 대상 테이블에 외래 키는
- 다대다
- 테이블 2개로 다대다 관계를 표현할 수 없으므로 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용하고
객체는 테이블과 다르게 컬렉션을 사용해 객체 2개로 다대다 관계를 만들 수 있으므로 @ManyToMany를 사용 - @ManyToMany를 사용하면 연결 테이블을 자동으로 처리해주므로 도메인 모델이 단순해지고 여러 가지로 편리하지만,
추가적인 컬럼을 매핑하기 위해서는 @ManyToMany를 사용할 수 없으므로 다대다에서 일대다, 다대일 관계로 풀어야함 - 하지만 기본 키와 외래 키를 한 번에 매핑한 복합 기본 키를 사용해야하므로 식별 관계에서는 별도의 식별자 클래스가 필요
- 그러므로 복합 키를 사용하지 않고 비식별 관계인 다대다 관계를 구성하기 위해서
데이터베이스에서 자동으로 생성해주는 대리 키를 Long 값으로 새로운 기본 키로 사용하면 간편하고 영구히 쓸 수 있으며
비즈니스에 의존하지 않고 ORM 매핑 시에 간단히 매핑을 할 수 있음 - 다대다 단방향 / 다대다 양방향
- 테이블 2개로 다대다 관계를 표현할 수 없으므로 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용하고
고급 매핑
- 상속 관계 매핑
- 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것이며
슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현하는 방법에는
조인 전략, 단일 테이블 전략, 구현 클래스마다 테이블 전략으로 3가지가 존재 - 조인 전략은 엔티티 각각을 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용함
- 단일 테이블 전략은 테이블을 하나만 사용하며 구분 컬럼으로 어떤 자식 데이터가 저장되었는지를 구분함
- 구현 클래스마다 테이블 전략은 자식 엔티티마다 테이블을 만들며 자식 테이블 각각에 필요한 컬럼이 모두 존재하게 됨
- 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것이며
- @MappedSuperclass
- 부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속 받는 자식 클래스에게 매핑 정보를 제공하고 싶을 때 사용함
즉, 단순히 매핑 정보를 상속할 목적으로만 사용 - 서로 관계가 없는 테이블과 엔티티일 때 테이블은 그대로 두고 공통 속성을 부모 클래스로 모아
자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하도록 객체 상속 관계를 만들 수 있음 - 이를 통해 등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리 가능
- 부모 클래스는 테이블과 매핑하지 않고 부모 클래스를 상속 받는 자식 클래스에게 매핑 정보를 제공하고 싶을 때 사용함
- 복합 키와 비식별 관계 매핑
- 데이터베이스 테이블 사이에 관계는 외래 키가 기본 키에 포함되는지 여부에 따라 식별 관계와 비식별 관계로 구분하며
최근에는 외래 키를 외래 키로만 사용하는 비식별 관계를 주로 사용하고 꼭 필요한 곳에만 식별 관계를 사용하는 추세 - 비식별 관계에서 기본 키를 구성하는 컬럼이 하나면 단순하게 매핑하며 복합 키를 구성하지 않아도 됨
- 하지만 비식별 관계 매핑에서 둘 이상의 컬럼으로 구성된 복합 키는
별도의 식별자 클래스를 생성하고 equals와 hashCode를 구현해 동등성을 비교해야 함
그러므로 JPA는 이러한 복합 키를 지원하기 위해
관계형 데이터베이스에 가까운 방법인 @IdClass와 좀 더 객체지향에 가까운 방법인 @EmbeddedId 2가지 방법을 제공함
- 데이터베이스 테이블 사이에 관계는 외래 키가 기본 키에 포함되는지 여부에 따라 식별 관계와 비식별 관계로 구분하며
- 복합 키와 식별 관계 매핑
- 식별 관계에서 일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만을 사용하므로
부모 테이블의 기본 키가 복합 키가 아니면 자식 테이블의 기본 키는 복합 키를 구성하지 않아도 됨 - 하지만 식별 관계 매핑에서 자식 테이블이 부모 테이블의 기본 키를 포함해서 복합 키를 구성해야 한다면
별도의 식별자 클래스를 생성하고 equals와 hashCode를 구현해 동등성을 비교해야 함
그러므로 JPA는 이러한 복합 키를 지원하기 위해
관계형 데이터베이스에 가까운 방법인 @IdClass와 좀 더 객체지향에 가까운 방법인 @EmbeddedId 2가지 방법을 제공함
- 식별 관계에서 일대일 식별 관계는 자식 테이블의 기본 키 값으로 부모 테이블의 기본 키 값만을 사용하므로
- 비식별 관계 매핑
- 식별 관계 테이블을 비식별 관계로 변경 후, 비식별 관계로 만든 테이블을 매핑할 수도 있음
- 식별 관계의 복합 키를 사용한 코드와 비교하면 복합 키 클래스를 만들지 않아도 되어 매핑도 쉽고 코드도 단순하게 변경됨
- 조인 테이블
- 데이터베이스 테이블의 연관관계를 설계하는 방법에는 조인 컬럼을 사용하는 방법과 조인테이블을 사용하는 2가지가 존재
- 조인 컬럼은 매핑을 위해 @JoinColumn을 사용하며 외래 키 컬럼을 사용해서 관리하게 됨
- 조인 테이블은 매핑을 위해 @JoinTable을 사용하며 별도의 테이블을 사용해서 연관관계를 관리하게 됨
- 조인 테이블의 경우 테이블을 하나 추가해야 하므로 관리해야 하는 테이블이 늘어나기 때문에
기본은 조인 컬럼을 사용하고 필요하다고 판단되면 조인 테이블을 사용
- 엔티티 하나에 여러 테이블 매핑
- @SecondaryTable을 사용해서 한 엔티티에 여러 테이블을 매핑할 수 있으며
더 많은 테이블을 매핑하려면 @SecondaryTables를 사용함 - 하지만 두 테이블을 하나의 엔티티에 매핑하는 방법은 항상 두 테이블을 조회하기 때문에 최적화하기 어려우므로
테이블당 엔티티를 각각 만들어서 일대일 매핑하여 원하는 부분만 조회하고 필요하면 둘을 함께 조회하는 것을 권장
- @SecondaryTable을 사용해서 한 엔티티에 여러 테이블을 매핑할 수 있으며
프록시와 연관관계 관리
- 프록시
- 객체를 실제 사용하는 시점에 데이터베이스에서 조회를 하여 객체 그래프로 연관된 객체들을 마음껏 탐색하는
지연 로딩을 위해서는 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체인 프록시 객체가 필요함 - 엔티티를 직접 조회하여 조회한 엔티티를 사용하든 사용하지 않든 데이터베이스를 조회하는 EntityManager.find()와 달리
EntityManager.getReference() 메소드는 데이터베이스를 조회하지 않고 실제 엔티티 객체를 생성하지 않는 대신
데이터베이스 접근을 위임한 프록시 객체를 반환하게 됨 - 프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉 모양이 같아 진짜인지 구분하지 않고 사용하며
프록시 객체는 실제 객체에 대한 참조를 보관하여 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체 메소드를 호출
이렇게 프록시 객체는 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는 프록시 객체 초기화를 함 - 프록시를 사용하고 엔티티 접근 방식을 프로퍼티로 설정할 경우 프록시 초기화없이 식별자 값을 조회할 수 있으며
연관관계를 설정할 시에 식별자 값만 사용하므로 프록시 초기화 없이 데이터베이스 접근 횟수를 줄일 수 있음 - 이러한 프록시 인스턴스 초기화 여부 확인을 위해서는 JPA가 제공하는 PersistenceUnitUtil.isLoaded() 메소드를 사용
- 객체를 실제 사용하는 시점에 데이터베이스에서 조회를 하여 객체 그래프로 연관된 객체들을 마음껏 탐색하는
- 즉시 로딩과 지연 로딩
- JPA는 즉시 로딩과 지연 로딩이라는 방법을 모두 지원하며 지원 로딩의 경우 프록시 객체를 사용함
- 즉시 로딩은 엔티티를 조회할 때 연관된 엔티티도 함께 조회하며 하이버네이트는 가능하면 SQL 조인을 사용해 한 번에 조회
즉시 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.EAGER로 지정 - 지연 로딩은 연관된 엔티티를 실제 사용할 때 조회하며 연관된 엔티티를 프록시로 조회하며,
프록시를 실제 사용할 때 초기화하면서 데이터베이스를 조회
지연 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.LAZY로 지정 - 만약 조회 대상이 영속성 컨텍스트에 이미 있다면 프록시 객체를 사용하지 않고 실제 객체를 사용하게 됨
- 컬렉션을 로딩하는 것은 비용이 많이 들고 잘못하면 너무 많은 데이터를 로딩할 수 있기 때문에
JPA 기본 패치 전략은 연관된 엔티티가 하나면 즉시 로딩을, 컬렉션이면 지연 로딩을 사용하게 됨
그러므로 모든 연관관계를 지연 로딩으로 사용한 후 자주 사용하거나 꼭 필요한 곳에서 즉시 로딩을 사용하는 것으로 최적화
- 영속성 전이
- JPA는 연관된 객체를 함께 저장하거나 함께 삭제할 수 있는 영속성 전이 기능을 제공하며
이를 통해 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속성 상태로 만들어주게 됨 - CASCADE의 종류로는 ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH가 존재하며 여러 속성을 같이 사용 가능
- JPA는 연관된 객체를 함께 저장하거나 함께 삭제할 수 있는 영속성 전이 기능을 제공하며
- 고아 객체
- JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 고아 객체 제어 기능을 제공하므로
참고가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하게 됨 - 만약 삭제한 엔티티를 다른 곳에서 참조한다면 문제가 발생할 수 있으므로 @OneToOne, @OneToMany에서만 사용
- JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 고아 객체 제어 기능을 제공하므로
- 영속성 전이 + 고아 객체, 생명주기
- 엔티티는 EntityManager.persist()로 영속화, EntityManager.remove()로 제거되며 엔티티 스스로 생명 주기를 관리함
- 그러므로 CascadeType.ALL + orphanRemoval = true를 사용하면 부모 엔티티를 통해 자식의 생명주기도 관리 가능
+) 자세한 설명 자료
'Community > SOLUX' 카테고리의 다른 글
[221215-221221] 2022 2학기 프로젝트 - 4차 스터디 (0) | 2022.12.21 |
---|---|
[221208-221214] 2022 2학기 프로젝트 - 3차 스터디 (0) | 2022.12.14 |
[221124-221130] 2022 2학기 프로젝트 - 1차 스터디 (0) | 2022.11.29 |
[221123] 2022 2학기 프로젝트 - 5차 회의 (0) | 2022.11.23 |
[221118] 2022 2학기 프로젝트 - 프로젝트 기획 발표회 (0) | 2022.11.18 |