JPA의 역할 중 DB와 애플리케이션 간의 불일치성을 해결해 주는 역할이 있는데,
DB의 FK와 JOIN을 통한 JOIN Table은 코틀린에서 어떻게 표현해줘야 할까?
JPA를 사용하여 해당 문제를 해결해 보자.
얻어갈 지식
- 연관 관계를 사용 한 DB와 애플리케이션 ( kotlin )간의 불일치 해결 방법
사전 지식
- RDB에서의 1:1, 1:N, N:M 관계
"여러 문제와 해결"
DB에서 테이블 A와 테이블 B가 아래와 같은 구조로 있을 때
CREATE TABLE A(
id int not null primary key,
name nchar(20),
age int
);
CREATE TABLE B(
a_id int not null references A(id)
id int not null primary key
);
A의 데이터와 B의 데이터를 연관지어서 보고 싶다면 ( 조인해서 보고 싶다면 )
SELECT A.*, B.*
FROM A INNER JOIN B
ON A.id B.a_id
위처럼 하면 된다.
이것을 JPA를 이용해 코틀린으로 표현해보자
일단 A테이블과 B테이블에 있는 속성을 그대로 객체로 가져오면 아래와 같다.
@Entity
data class A (
var id: Int,
var name: String,
var age: Int
)
@Entity
data class B (
var a_id: Int,
var id: Int
)
문제 1.
DB는 FK와 JOIN 키워드를 통해 두 개의 테이블을(객체를) 하나의 테이블로(객체로) 표현할 수 있었지만,
코틀린은 FK와 JOIN과 같은 기능이 없으므로, 두 개의 객체를 합쳐서 하나의 객체로 표현할 수 없다.
이것을 어떻게 해결할까?
해결 1.
A와 B가 합쳐진 새로운 객체로부터 A, B의 값에 접근하는 방법이 아닌 다른 방법을 사용해야 한다.
JPA는 A객체에서 직접 B객체에 접근하는, B객체에서 직접 A객체에 접근하는 방법을 채택했다.
( 새로 만들어진 어떤 객체에서 각각의 값에 접근하는게 아니라 이미 존재하는 객체에서 다른 객체로 접근 함 )
코드 1.
이를 가능케 하려면 속성을 추가하면 된다.
// 1:1 관계일 경우
@Entity
data class A(
var id: Int,
var name: String,
var age: Int,
// 추가
var b: B
)
@Entity
data class B(
// 추가
var a: A,
var a_id: Int,
var id: Int
)
// A:B = 1:N 관계인 경우
data class A(
var id: Int,
var name: String,
var age: Int,
// 추가
var bList: List<B>
)
data class B(
// 추가
var a: A,
var a_id: Int,
var id: Int
)
위 속성을 추가하면 코틀린에서도 DB에서 조인한 것과 같이 두 객체를 서로 연관 지음으로써 연관된 모든 데이터에 접근할 수 있게 된다.
단, 그냥 저렇게 적으면 JPA는 DB의 열 하나가 추가된 것으로 인식하게 되므로
아래와 같이 어노테이션을 적어 JPA에게 다음의 속성들은 연관 관계를 위해 표현되었음을 알린다.
/* @OneToOne 1:1
* @OneToMany 1:N
* @ManyToOne N:1
* @ManyToMany N:M
*/
// 1:1 관계인 경우
@Entity
data class A(
var id: Int,
var name: String,
var age: Int,
@OneToOne
var b: B
)
@Entity
data class B(
@OneToOne
var a: A,
var a_id: Int,
var id: Int
)
// A:B = 1:N 관계인 경우
data class A(
var id: Int,
var name: String,
var age: Int,
@OneToMany
var bList: List<B>
)
data class B(
@ManyToOne
var a: A,
var a_id: Int,
var id: Int
)
문제 2.
여기까지 적어 놓고서 곰곰이 생각해보면 한 가지 문제점을 찾을 수 있다.
data class의 어느 곳에도 FK에 대한 정보는 없다는 것이다.
data class에서 외래 키를 정의해 주지 않았으므로 객체를 DB 테이블로 변환할 때 외래 키에 대한 어떤 정의도 할 수 없을 것이다.
해결 2.
이때 @JoinColumn( name = "xxx_xx" )과 mappedby속성을 사용한다.
@JoinColumn 어노테이션은 JPA에게 해당 어노테이션이 쓰인 엔티티에 FK가 존재함을 알려주는 동시에 해당 엔티티가 "연관 관계의 주인"임을 나타내는 것이고,
mappedby 속성은 자신이 "연관 관계의 주인"이 아니며 다른 엔티티가 "연관 관계의 주인"임을 나타내는 속성이다.
* 연관 관계의 주인이란 JPA에서 연관된 데이터를 등록, 수정, 삭제할 수 있는 권한을 가진 엔티티를 뜻하며, 연관 관계의 주인이 아닌 쪽은 오직 조회만 가능하다.
** 연관 관계의 주인이라는 컨셉이 필요한 이유는 아래와 같다.
... 잘 모르겠음 추후 업데이트 예정 ...
... 그냥 양쪽에서 수정하고 뭐하고 다 하면 좋은 거 아닌가 ...
코드 2.
// 1:1 관계인 경우
@Entity
data class A(
var id: Int,
var name: String,
var age: Int,
// 이 엔티티는 연관 관계의 주인이 아니며 B 엔티티의 a라는 이름을 가진 속성이 외래키이다
@OneToOne(mappedby = "a")
var b: B
)
@Entity
data class B(
@OneToOne
// @JoinColumn으로 a_id라는 FK를 생성하므로 아래의 var a_id:Int 는 필요 없음
@JoinColumn( name = "a_id" )
var a: A,
// var a_id: Int,
var id: Int
)
// A:B = 1:n 관계인 경우
data class A(
var id: Int,
var name: String,
var age: Int,
@OneToMany(mappedby = "a")
var bList: List<B>
)
data class B(
@ManyToOne
@JoinColumn( name = "a_id" )
var a:A,
// var a_id: Int,
var id: Int
)
위와 같이 적으면
코틀린에서도 문제없이 JOIN을 사용한 것과 비슷한 효과를 내며 각 엔티티의 값을 변경할 수 있고
자동으로 Table이 생성될 때도 문제 없이 잘 실행 된다.
참고 문서
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
https://victorydntmd.tistory.com/208
https://ecsimsw.tistory.com/entry/JPA-%EC%96%91%EB%B0%A9%ED%96%A5-%EB%A7%A4%ED%95%91-MappedBy
'JPA' 카테고리의 다른 글
JPA N+1 문제 해결 (0) | 2023.07.04 |
---|---|
QueryDSL (0) | 2023.04.02 |
JPA의 개념과 이해 (0) | 2021.07.04 |