일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- node.js
- 상속
- vue
- Kotlin
- 개발이 취미인 사람
- 코틀린
- jpa
- kafka
- 개발자
- props
- 반복문
- swagger
- javascript
- back-end
- front-end
- restful api
- java
- file upload
- component
- state
- It
- AWS
- Producer
- spring boot
- SWIFT
- react
- Nest.js
- Sequelize
- 조건문
- class
- Today
- Total
개발이 취미인 사람
[JPA] - Entity 관계 설정 방법 본문
- 개요
안녕하세요. 이번 시간에는 Entity 간에 관계 설정에 대해 알아보겠습니다.
혹시 이전 시간에 내용을 학습하고 오시지 못 하신 분들은 학습하고 오시는 걸 추천드리겠습니다.
- Entity 관계설정
JPA에서 관계설정은 데이터베이스 테이블 간의 연관성을 엔티티 간에 매핑하는 기술입니다.
보통 관계를 설정하는 방식은 1 : 1, M : 1, M : N 관계를 설정합니다. (반대로 1 : N로 설정도 가능하지만 추천하지 않습니다.)
@OneToOne, @OneToMany, @ManyToOne, @ManyToMany 어노테이션을 사용해서 관계를 설정합니다.
한쪽 방향으로 관계를 연결하는 방식을 단반향 연관관계라고 표현하고, 양쪽 방향으로 연결하는 방식은 양방향 연관관계라고 표현합니다.
그럼 예제를 통해 하나씩 알아가 보도록 하겠습니다.
1 : 1 관계
MemberEntity
package com.ryan.kotlinspirngjpa.jpa.entity
import jakarta.persistence.*
import org.hibernate.annotations.SQLDelete
import org.hibernate.annotations.SQLRestriction
@Entity
@Table(name = "member")
@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?")
@SQLRestriction("deleted_at is NULL")
class MemberEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(name = "email", nullable = false, unique = true)
val email: String,
@Column(name = "password", nullable = false)
val password: String,
@Column(name = "name", nullable = false)
private var name: String,
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_profile_id", referencedColumnName = "id")
val profile: MemberProfileEntity,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id", referencedColumnName = "id")
val company: CompanyEntity
) : BaseEntity() {
fun getName() = this.name
fun setName(name: String) {
this.name = name
}
}
위 Entity에서 주목해야 할 부분은 @OneToOne이라는 어노테이션을 활용해서 MemberProfileEntity와 1:1 관계 설정을 했다는 걸 보실 수 있습니다.
그럼 반대로 MemberProfileEntity는 어떻게 설정 되어있을까요?
MemberProfileEntity
package com.ryan.kotlinspirngjpa.jpa.entity
import jakarta.persistence.*
@Entity
@Table(name = "member_profile")
class MemberProfileEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(name = "age", nullable = false)
val age: Int,
@Enumerated(EnumType.STRING)
@Column(name = "gender", nullable = false)
val gender: Gender,
@Column(name = "biography", nullable = true)
val biography: String? = null
) : BaseEntity()
MemberProfileEntity 코드를 확인해 보면 관계 설정에 대한 어노테이션이 존재하지 않습니다. 왜일까요?
JPA에서는 엔티티 간에 연관관계를 설정할 때 연관관계 주인이라는 개념이 있으며, 위 구조는 단방향 연관관계 설정을 뜻 합니다.
우리가 데이터베이스에서 두 테이블 간의 관계를 설정할 때 한쪽 테이블에 Foregin Key를 설정합니다.
Foregin Key를 관리하는 쪽을 연관관계 주인이라고 생각하시면 됩니다. 그렇다면 연관관계 주인이라는 설정은 어떻게 했을까요?
@JoinColumn(name = "member_profile_id", referencedColumnName = "id")
MemberEntity 코드를 보면 위와 같은 어노테이션을 보셨을 겁니다.
name이라는 설정은 실제 테이블 컬럼을 뜻하며, referencedColumnName 은 내가 참조하는 MemberEntity id 를 말합니다.
M : 1 관계
MemberEntity
package com.ryan.kotlinspirngjpa.jpa.entity
import jakarta.persistence.*
import org.hibernate.annotations.SQLDelete
import org.hibernate.annotations.SQLRestriction
@Entity
@Table(name = "member")
@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?")
@SQLRestriction("deleted_at is NULL")
class MemberEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(name = "email", nullable = false, unique = true)
val email: String,
@Column(name = "password", nullable = false)
val password: String,
@Column(name = "name", nullable = false)
private var name: String,
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_profile_id", referencedColumnName = "id")
val profile: MemberProfileEntity,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id", referencedColumnName = "id")
val company: CompanyEntity
) : BaseEntity() {
fun getName() = this.name
fun setName(name: String) {
this.name = name
}
}
M:1 관계는 우리가 생각하는 전통적인 테이블 연관관계 설정과 동일합니다. @ManyToOne 어노테이션이 M:1 관계를 나타냅니다.
어노테이션 설정을 쉽게 이해하기 위해서 다음과 같이 기억하면 좋습니다.
첫 Many는 대상을 뜻하고 One 타겟을 나타냅니다. MemberEntity 가 CompanyEntity에게 연관관계를 설정한다.
그럼 ComapnyEntity를 확인해보겠습니다.
CompanyEntity
package com.ryan.kotlinspirngjpa.jpa.entity
import jakarta.persistence.*
import java.time.Instant
@Entity
@Table(name = "company")
class CompanyEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
val status: CompanyStatus = CompanyStatus.ACTIVE,
@Column(name = "name", nullable = false, unique = true)
val name: String,
@Column(name = "business_number", nullable = false)
val businessNumber: String,
@Column(name = "ceo_name", nullable = false)
val ceoName: String,
@Column(name = "founded_date", nullable = false)
val foundedDate: Instant,
@Column(name = "email", nullable = false)
val email: String,
@Column(name = "phone_number", nullable = false)
val phoneNumber: String,
@Embedded
val address: Address,
@ManyToMany
@JoinTable(
name = "company_category_mapping",
joinColumns = [JoinColumn(name = "company_id")],
inverseJoinColumns = [JoinColumn(name = "category_id")]
)
val categories: MutableSet<CategoryEntity> = mutableSetOf()
) : BaseEntity()
CompanyEntity도 동일하게 양방향 설정이 아닌 단방향 설정으로 MemberEntity와 연결하지 않고 있습니다.
M : N 관계
다대다 설정은 실제 두 테이블만으로는 구현할 수 없기 때문에 중간에 두 테이블을 연결하는 맵핑 테이블을 만들어 구현합니다.
위에 CompanyEntity 코드를 확인하면 @JoinTable이라는 어노테이션을 확인할 수 있습니다.
실제 company_category_mapping이라는 맵핑 테이블과 연동을 할때 사용됩니다. 만약 위 어노테이션을 사용하지 않는다면
중간 맵핑 엔티티를 만들어서 @ManyToMany 어노테이션을 설정해서 만들 수 있습니다.
그럼 CategoryEntity는 어떻게 구현되어 있을까요?
CategoryEntity
package com.ryan.kotlinspirngjpa.jpa.entity
import jakarta.persistence.*
@Entity
@Table(name = "category")
class CategoryEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(name = "name", nullable = false)
val name: String,
@ManyToOne
@JoinColumn(name = "parent_id", referencedColumnName = "id")
val parent: CategoryEntity?,
@OneToMany(mappedBy = "parent")
val childCategorise: List<CategoryEntity> = listOf(),
@ManyToMany(mappedBy = "categories")
val companies: MutableSet<CompanyEntity> = mutableSetOf()
) : BaseEntity()
CategoryEntity도 @ManyToMany 어노테이션을 통해 CompanyEntity와 연결하고 있습니다.
그런데 눈치가 빠르신 분들은 코드를 보고 이상하다고 생각하실 수 있습니다.
왜냐하면 CompanyEntity에서는 @JoinTable이라는 어노테이션을 사용했는데 CategoryEntity는 사용하지 않고 @ManyToMany 어노테이션에 mappedBy 라는 속성을 정의해서 사용하고 있습니다.
아까 위에서 연관관계 주인이라는 개념을 설명했습니다. 데이터베이스에서는 양방향 연관관계라는 개념이 존재하지 않습니다.
한쪽에 Foregin Key를 알고 있으면 양쪽 다 join 쿼리를 사용해서 조회가 가능하기 때문입니다.
JPA에서는 이 부분을 해결하기 위해 Foregin Key를 관리하지 않는 엔티티에 mappedBy라는 속성을 지정해서 위 기능을 구현합니다.
mappedBy라는 속성을 지정하면 해당 엔티티는 읽기 전용으로 설정 됩니다.(실제 값을 수정하는 로직을 작성하면... 피를 볼수 있습니다.)
정리를 하면 CompanyEntity에 @JoinTable이라고 어노테이션 설정은 연관관계 주인을 뜻하며 반대쪽 CategoryEntity는 읽기전용으로 설정합니다.
이번 시간에는 JPA에서 연관관계 설정에 대해 알아봤습니다.
꼭 실습을 통해 위 내용을 이해하시는 걸 추천드리겠습니다.
'백앤드(Back-End) > Jpa' 카테고리의 다른 글
[JPA] - Entity 정의 방법 (3) | 2024.12.14 |
---|---|
[JPA] - 영속성 컨텍스트(2) (2) | 2024.11.29 |
[JPA] - 영속성 컨텍스트(1) (1) | 2024.11.24 |
[JPA] - 기본 동작 방식과 단순 CRUD (0) | 2024.11.19 |