테이블은 외래키로 Join을 사용해서 연관된 테이블을 찾고, 객체는 참조를 사용해서 연관된 객체를 찾는다.
이러한 차이점을 해결하기 위해 연관관계 매핑을 사용한다.
@ManyToOne
@OneToMany
@OneToOne
@ManyToMany
⚠️ 연관관계의 주인은 항상 외래 키를 가지고 있는 객체이다! (1:N 제외)
연관관계의 주인이 아니면, 단순히 조회만 가능하다.
Issue
와 educationContent
클래스는 N:1 양방향 매핑이 되어있다.
@Entity
public class Issue {
@Id
@Column(name = "issue_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long issueId;
@ManyToOne
@JoinColumn(name = "education_content_id", referencedColumnName = "content_id")
EducationContent educationContent;
...
}
@Entity
public class EducationContent {
@Id
@Column(name = "content_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long contentId;
@OneToMany(mappedBy = "educationContent")
List<Issue> issueList = new ArrayList<>();
...
}
EducationContent
클래스에는 issueList 필드가 존재하지 않았을 것이다.외래 키를 매핑할 때는 @JoinColumn
어노테이션을 사용한다.
위의 경우에는, 연관관계의 주인인 Issue 클래스의 필드에 @JoinColumn
을 적용했다.
@JoinColumn
은 다양한 속성을 가지고 있다.
name
: 매핑할 외래 키의 이름 설정
referencedColumnNmae
: 외래 키가 참조하는 대상 테이블의 컬럼명
필드명이 아닌, 컬럼명임에 유의할 것
foreignKey
: 외래 키 제약조건 지정
1:N 단방향 관계를 매핑할 때 사용하는 어노테이션이다.
@OneToMany
어노테이션의 속성은 다음과 같다.
mappedBy
: 연관관계의 주인 필드를 선택한다.
fetch
: 글로벌 페치 전략을 설정한다. - 기본값은 FetchType.LAZY
cascade
: 영속성 전이 기능 설정
1:N 관계에서는 양방향 매핑이 존재하지 않는다!
단방향 매핑만 존재한다.
1:1 매핑의 경우, N:1 매핑처럼 외래 키가 있는 곳이 연관관계의 주인이다.
그럼 다음과 같은 2가지 경우가 발생할 수 있다.
1. 주 테이블에 외래 키가 있는 경우
- 주 객체에서
@JoinColumn
을 사용한다.- 주 객체가 대상 객체의 참조를 가진다.
- ⚠️ 외래 키에 null을 허용하게 된다.
2. 대상 테이블에 외래 키가 있는 경우
- 대상 객체에서
@JoinColumn
을 사용한다.- ⚠️ 지연 로딩이 불가능하다.
관계형 DB는 다대다 관계를 표현할 수 없다.
➜ 연결 테이블을 추가해서 1:N, N:1 관계로 풀어내야 한다.
하지만, 객체는 @ManyToMany
어노테이션을 사용해서 다대다 관계를 표현할 수 있다.
@ManyToMany
는 실무에서 사용하면 안된다!
@ManyToMany
를 사용하면 연결 테이블을 자동으로 만들어준다.
하지만, 연결 테이블이 단순한 연결 뿐만 아니라 추가적인 정보를 가질 수 있기 때문에 쿼리가 복잡해질 수 있다.
따라서, 관계형 DB가 N:M을 1:N, N:1 로 풀어내는 것처럼 @ManyToMany
을 @OneToMany
, @ManyToOne
으로 풀어내야 한다.
연결 테이블 전용 엔티티를 추가하여 위의 동작을 수행하면 된다.
관계형 DB에는 상속 관계가 존재하지 않는다.
하지만, 슈퍼타입 - 서브타입 관계가 객체의 상속이 유사하다.
따라서, 객체의 상속 구조와 슈퍼타입 - 서브타입 구조를 매핑할 수 있다.
상속 구조를 구현하기 위한 3가지 방법이 존재한다.
각 엔티티를 모두 테이블로 만들고, 부모의 기본키를 기본키+외래키로 사용한다.
조회할 때는 조인을 사용한다.
DTYPE
컬럼을 추가하여 각 서브타입을 구분해줘야 한다.@Inhereitance(strategy = InheritanceType.JOINED)
를 사용한다.위와 같은 테이블 구조를 객체로 구현하려면, 다음과 같이 설계하면 된다.
@Entity
@Inhereitance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn // 기본이 DTYPE
public abstract class Item{
@Id
@GeneratedValue
private Long id;
private String name;
...
}
@Entity
@DiscriminatorValue("Switch")
public class Ipad extends Item{
private String camera;
}
테이블을 하나만 사용하고, 자식 테이블은 DTYPE을 사용하여 구분하는 전략이다.
@Inhereitance(strategy = InheritanceType.SINGLE_TABLE)
을 사용한다.⚠️ 사용하면 안된다!!
자식 테이블이 부모 테이블의 컬럼들을 포함하는 구조이다.
@Inhereitance(strategy = InheritanceType.TABLE_PER_CLASS)
를 사용한다.위의 상속관계 매핑 방법들은 부모 & 자식 엔티티를 DB의 테이블과 매핑하였다.
@MappedSuperclass
어노테이션을 사용하면 부모 클래스는 테이블에 매핑하지 않아도 된다.
@MappedSuperclass
어노테이션을 사용한 클래스는 엔티티가 아니다.예를 들어, Menu
와 Issue
테이블에 id, subject 라는 컬럼이 공통적으로 존재한다면, 다음과 같이 BaseEntity
클래스를 사용할 수 있다.
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String subject;
}
@Entity
public class Menu extends BaseEntity {
// id와 subject 필드를 상속받는다.
private String title;
...
}