1. 객체 연관관계 vs 테이블 연관관계
객체
- 객체는 참조를 통해 연관관계를 맺는다.
- 참조를 통한 연관관계는 단방향이다. 즉, 객체에서의 양방향은 두 엔티티가 서로 단방향 연관관계를 갖고 있는 것이다.
테이블
- 테이블은 외래키로 연관관계를 맺는다.
- 외래키를 통한 연관관계는 언제나 양방향이다. 외래키가 있으면 해당 키를 중심으로 A JOIN B와 B JOIN A 모두 가능하기 때문이다.
2. 연관관계 매핑 종류
2. 1. 일대일(One To One) 매핑
2. 1. 1. 일대일 단방향 매핑
public class Member {
...
@OneToOne
@JoinColumn(name = "member_detail_id", referencedColumnName = "id")
private MemberDetail memberDetail;
}
일대다(1:N) 관계에서는 다(N) 쪽에서 외래키를 갖기 마련이지만, 일대일(1:1) 관계에서는 양쪽 어디든 외래키를 가질 수 있다. 다만 JPA에서 외래키를 갖는 쪽이 연관관계의 주인이 되어 외래키로 매핑한 대상 테이블에 대한 관리(등록, 수정, 삭제) 권한을 갖기 때문에 이 점을 고려하여 연관관계의 주인을 설정하는 것이 좋다.
위의 예제는 Member 클래스에서 MemberDetail 클래스를 참조하는 형태이다.
여기서 @JoinColum 어노테이션의 속성 중 name과 referencedColumnName의 차이에 주의할 필요가 있다. 위의 예제의 상황과 관련하여 살펴보면,
- name : Member 테이블에서 memberDetail에 대한 컬럼명을 지정
- referencedColumnName : 대상 테이블인 MemberDetail 테이블의 'id'라는 이름을 가진 컬럼과 매핑함
public @interface OneToOne {
...
FetchType fetch() default EAGER;
boolean optional() default true;
}
@OneToOne 인터페이스를 살펴보면 FetchType의 기본값은 EAGER, 즉 즉시 로딩 전략이 채택되었다. 그리고 optional() 메서드는 기본값으로 true가 설정돼 있는데, 이는 매핑되는 값이 nullable임을 의미한다.
2. 1. 2. 일대일 양방향 매핑
public class Member {
...
@OneToOne
@JoinColumn(name = "member_detail_id", referencedColumnName = "id")
private MemberDetail memberDetail;
}
public class MemberDetail {
...
@OneToOne(mappedBy = "memberDetail") // 연관관계 주인 설정
private Member member;
}
일대일 양방향 매핑은 단방향 매핑을 서로 설정함으로써 설정할 수 있다. 한쪽의 테이블에만 외래키를 주어 연관관계의 주인을 설정해야 하는데, 이때 @OneToOne 어노테이션의 mappedBy 속성을 사용하여 어떤 객체가 주인인지 표시한다. 이 속성에 지정되는 값은 연관관계를 갖고 있는 상대 엔티티에 있는 연관관계 필드의 이름이 된다. 위 예제의 경우 MemberDetail 엔티티가 Member 엔티티의 주인이 되는 것이다.
한가지 주의할 점은, 양방향으로 연관관계가 설정된 상태에서 @ToString을 사용할 때 순환참조가 발생함에 따라 StackOverFlowError가 발생할 수 있다. 따라서 되도록 단방향으로 연관관계를 설정하거나 양방향 설정이 필요한 경우 순환참조 제거를 위해 연관관계를 설정한 참조 필드에 @ToString.Exclude 어노테이션을 붙여 ToString에서 제외 설정을 하는 것이 좋다.
2. 2. 다대일, 일대다 매핑
2. 2. 1. 다대일 단방향 매핑
public class Product {
...
@ManyToOne
@JoinColumn(name = "provider_id")
private Provider provider;
}
위와 같이 Product 엔티티에서 단방향으로 Provider 엔티티 연관관계를 맺고 있기 때문에 ProductRepository만으로도 product 객체에 연관되어 있는 provider 객체까지 조회할 수 있다.
2. 2. 2. 다대일 양방향 매핑
public class Product {
...
@ManyToOne
@JoinColumn(name = "provider_id")
private Provider provider;
}
public class Provider {
...
@OneToMany(mappedBy = "prodvider", fetch = FetchType.EAGER) // 외래키 설정
private List<Product> productList = new ArrayList<>();
}
일대다 연관관계의 경우 여러 상품 엔티티가 포함될 수 있어 위와 같이 컬렉션 형식으로 필드를 생성한다. 또한, @OneToMany가 붙은 쪽에서 @JoinColumn 어노테이션을 사용하여 상대 엔티티에 외래키를 설정한다.
추가로 @OneToMany 내 fetch 속성의 기본값은 LAZY이다.
2. 1. 다대다 매핑
다대다 연관관계는 실무에서 거의 사용되지 않는 구성이다. 이 관계는 각 엔티티에서 서로를 리스트로 가지는 구조가 만들어지는데 이런 경우에는 교차 엔티티라고 부르는 중간 테이블을 생성해서 다대다 관계를 일대다와 다대일의 복합 구조로 해소한다.
3. 영속성 전이(Cascade)
영속성 전이란 특정 엔티티의 영속성 상태를 변경할 때 그 엔티티와 연관된 엔티티의 영속성에도 영향을 미쳐 영속성 상태를 함께 변경하는 것을 말한다.
연관관계를 설정하는 @OneToOne, @ManyToOne 등의 어노테이션 인터페이스를 살펴보면 cascade()라는 요소를 볼 수 있는데, 이를 통해 영속성 전이 타입을 설정한다. 영속성 전이 타입의 종류는 아래와 같다.
추가로 cascade 속성은 배열 형태로 여러 가지 타입을 설정할 수 있다.
종류 | 설명 |
ALL | 모든 영속 상태 변경에 대해 영속성 전이 |
PERSIST | 엔티티가 영속화할 때 연관된 엔티티도 함께 영속화 |
MERGE | 엔티티를 영속성 컨텍스트에 병합할 때 연관된 엔티티도 병합 |
REMOVE | 엔티티를 제거할 때 연관된 엔티티도 제거 |
REFRESH | 엔티티를 새로고침할 때 연관된 엔티티도 새로고침 |
DETACH | 엔티티를 영속성 컨텍스트에서 제외하면 연관된 엔티티도 제외 |
3. 1. 고아객체
JPA에서 고아(orphan)란 부모 엔티티와 연관관계가 끊어진 엔티티를 의미한다. JPA에서는 orphanRemoval이라는 기능을 통해 고아가 된 객체를 자동으로 제거하는 기능을 제공한다. 다만, 자식 엔티티가 다른 엔티티와 연관관계를 가지고 있다면 이 기능은 사용을 자제해야 할 것이다.
public class MemberDetail {
...
@OneToOne(mappedBy = "memberDetail", cascade = CascadeType.PERSIST, orphanRemoval = true)
private Member member;
}
[참고 자료] 『스프링 부트 핵심 가이드』 장정우 지음
'Java > Spring' 카테고리의 다른 글
[Spring Boot] 액추에이터 (0) | 2023.04.21 |
---|---|
[Spring Boot] 유효성 검사와 예외처리 (0) | 2023.04.14 |
[Spring Boot] 테스트 코드 작성 (0) | 2023.03.31 |
[Spring Boot] 스프링 부트와 데이터 베이스 연동 (0) | 2023.03.27 |
[Spring Boot] HTTP 요청을 위한 API 작성법 (0) | 2023.03.18 |