티스토리 뷰
자바 ORM 표준 JPA 프로그래밍 - 기본편
김영한님 강의듣고 정리하기
1. 다양한 연관관계 매핑
연관관계 매핑시 고려사항 3가지
- 다중성
- 다대일 : @ManyToOne
- 일대다 : @OneToMany
- 일대일 : @OneToOne
- 다대다 : @ManyToMany
- 단방향, 양방향
- 테이블 : 외래키 하나로 양쪽 조인 가능, 사실 방향이라는 개념이 없음
- 객체 : 한쪽만 참조하면 단방향, 양쪽이 서로 참조하면 양방향
- 연관관계의 주인
- 연관관계의 주인 : 외래키를 관리하는 참조
- 주인의 반대편 : 외래키에 영향을 주지않음, 단순조회만 가능
다대일 [N:1]
- 다대일 단방향 - 가장 많이 사용하는 연관관계
- 다대일 양방향 - 외래키가 있는 쪽이 연관관계의 주인, 양쪽을 서로 참조하도록 개발
일대다 [1:N]
- 일대다 단방향
- 일(1)이 연관관계의 주인
- 테이블에서는 일대다 관계는 항상 다(N) 쪽에 외래키가 있음
- 객체와 테이블의 차이 발생, JoinColumn을 꼭 사용해야함 (그렇지 않으면 중간에 테이블을 하나 추가해서 사용한다.)
- 엔티티가 관리하는 외래키가 다른 테이블에 있으므로 연관관계 관리를 위해 추가로 UPDATE SQL 실행, 일대다 매핑 보다는 다대일 양방향 매핑을 사용하자
- 일대다 양방향
- 이런 매핑은 공식적으로 존재 X
- @JoinColumn(insertable = false, updateable = false) , 읽기전용 필드를 사용해서 양방향 처럼 사용하는 방법
- 다대일 양방향을 사용하자
일대일 [1:1]
일대일 관계는 그 반대도 일대일
주 테이블 (둘중에 더 자주쓰는 테이블)이나 대상 테이블 중에 외래키 선택 가능
외래키에 데이터베이스 유니크(UNI) 제약조건 추가
- 일대일 : 주 테이블에 외래키 단방향
- 다대일 단방향 매핑과 유사 (외래키에 UNI키만 추가)
- 일대일 : 주 테이블에 외래키 양방향
- 다대일 양방향 매핑처럼 외래키가 있는곳이 연관관계의 주인, 반대편은 mappedBy 적용
- 일대일 : 대상 테이블에 외래키 단방향
- 대상 테이블에 단방향 관계는 JPA에서 지원 X
- 일대일 : 대상 테이블에 외래키 양방향
- 일대일 주테이블에 외래키 양방향과 매핑 방법은 같다.
일대일 정리
- 주 테이블에 외래키 (많이씀)
- 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점 : 값이 없으면 외래키에 null 허용
- 대상 테이블에 외래키
- 장점 : 주 테이블과 대상 테이블에 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
- 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩된다.
다대다 [N:M]
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다.
연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함
하지만 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능하다.
@ManyToMany를 사용하면 주문시간, 수량 같은 데이터가 들어올 수 있음
편리해 보이지만 실무에서 사용 X
다대다 한계 극복
- 연결 테이블용 엔티티 추가 (연결 테이블을 엔티티로 승격)
- @ManyToMany => @OneToMany, @ManyToOne
- 테이블에 중간 테이블이 있는것처럼 객체에서도 중간 엔티티를 사용해 ManyToMany를 풀어서 사용해야한다.
- PK키는 위에 그림처럼 같이사용 해도 되지만 테이블 유연성을 고려해 ORDER_ID 같은 PK를 따로 만들어주는게 편리하다고 함
@JoinColumn
외래키를 매핑할 때 사용
referencedColumnName은 참조할때 기본키 말고 다른 값을 참조하고 싶을때 사용하면된다.
2. 고급매핑
상속관계 매핑
관계형 데이터베이스는 상속 관계 X
슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사
상속관계 매핑 : 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑
상속관계 매핑할때 부모 클래스는 독단적으로 사용 할 일이 없으므로 abstract 추상클래스로 선언하는게 좋다.
주요 어노테이션
- 상속관계 매핑 어노테이션은 @Inheritance(strategy = InheritanceType.XXX)로 3가지 전략이 존재한다.
- JOINED : 조인 전략
- SINGLE_TABLE : 단일 테이블 전략
- TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
- @DiscriminatorColumn(name="DTYPE") : 부모 엔티티에 붙여준다, 어느 자식의 값이 들어온건지 알 수 있다.
- @DiscriminatorValue("XXX") : 자식 엔티티에 붙여주고 부모엔티티 DiscriminatorColumn(name="DTYPE")에 들어갈 이름을 정의할 수 있다.
조인 전략
장점
- 테이블 정규화
- 외래키 참조 무결성 제약조건 활용가능
- 저장공간 효율화
단점
- 조회시 조인을 많이 사용, 성능 저하
- 조회 쿼리가 복잡함
- 데이터 저장시 INSERT SQL 2번 호출
단일테이블 전략
장점
- 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
- 조회 쿼리가 단순함
단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 조회 성능이 오히려 느려질 수 있다.
구현 클래스마다 테이블 전략
이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천 X
장점
- 서브 타입을 명확하게 구분해서 처리할때 효과적
- not null 제약조건 사용가능
단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기 어려움
@MappedSuperclass
공통 매핑 정보가 필요할 때 사용 (DB는 바뀌지 않는데 객체만 중복되는 것을 줄이기 위해 사용해준다.)
- 이 어노테이션은 상속관계 매핑이 아니고 엔티티도 아니고 테이블과 매핑이 되지도 않는다. 테이블과 관계없고, 단순히 엔티티가 공통으로 사용하는 매핑정보를 모으는 역할
- 조회, 검색 불가, 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장
- 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 사용하는 정보를 모을 때 사용
- 참고 : @Entity 클래스는 @Entity나 @MappedSuperclass로 지정한 클래스만 상속 가능
3. 프록시와 연관관계 관리
프록시 기초
- em.find() vs em.getReference()
- em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
프록시 객체의 초기화
프록시에 실제 객체의 값이 없을경우 영속성 컨텍스트를 통해서 실제 객체를 찾아 연결한다.
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화 되면 프록시 객체를 통해서 실제 엔티티에 접근가능
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
프록시 주의 사항
- 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교하지 말고, instance of 사용)
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제발생
즉시로딩과 지연로딩
member를 조회할 때 연관관계인 Team을 많이 사용한다면
즉시로딩(Eager)을 사용하여 member를 조회할때 Team도 같이 들어오도록 쿼리를 보낸다.
member를 조회할 때 연관관계인 Team을 거의 사용하지 않는다면
지연로딩(Lazy)으로 값이 필요할떄만 쿼리가 나가도록 하고 값을 찾기전에는 Team은 프록시로 받아온다.
지연로딩
지연로딩(LAZY)를 사용하면 team을 프록시로 조회하고 실제로 team을 사용하는 시점에 초기화(DB 조회)를 한다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
즉시로딩
member를 조회할때 연관관계인 team도 함께 조인해서 같이 조회한다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
즉시로딩 주의사항
- 가급적 지연 로밍만 사용 (특히 실무에서)
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- @ManyToOne, @OneToOne은 기본이 즉시로딩(EAGER) -> 지연로딩(LAZY)으로 설정
- @OneToMany, @ManyToMany는 기본이 지연로딩
영속성 전이 : CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을때 사용한다.
(부모 엔티티를 저장할 때 자식 엔티티도 함께 저장)
CASCADE 주의사항
- 영속성 전이는 연관관계를 매핑하는 것과 아무관련이 없다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화 하는 편리함을 제공할 뿐이다.
CASCADE의 종류
- ALL : 모두 적용
- PERSIST : 영속
- REMOVE : 삭제
- MERGE : 병합
- REFRESH : REFRESH
- DETACH : DETACH
고아 객체
고아 객체 제거 : 부모엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
orhanRemoval = true를 붙여주면 된다.
고아객체 주의사항
- 참조하는 곳이 하나일 때 사용해야한다.
- 특정 엔티티가 개인 소유할 때 사용
- @OneToOne, @OneToMany만 가능
- 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE 처럼 동작한다.
영속성 전이 + 고아객체
- CascadeType.ALL + orphanRemovel = true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리 할 수 있다.
- 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용
Reference
www.inflearn.com/course/ORM-JPA-Basic/dashboard
'Programming > Springboot' 카테고리의 다른 글
[Springboot] JPA, N + 1 쿼리 증가 문제 (0) | 2021.05.15 |
---|---|
[Springboot] 자바 ORM 표준 JPA 프로그래밍 - 3 (0) | 2021.04.16 |
[Springboot] 자바 ORM 표준 JPA 프로그래밍 - 1 (0) | 2021.04.08 |
[Springboot] Spring 헷갈리는 내용 (21.03.25) (0) | 2021.03.25 |