JPA - 연관관계 매핑(Realation Mapping)


  • 방향(Direction): 단방향, 양방향
  • 연관관계의 주인(Owner): 객체 양방향 연관관계는 관리 주인이 필요

단방향 연관관계

DB 기준에서 MemberTeam관계는 다대일(N:1)이다.

Member.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
var id: Long? = null

@Column(name = "USERNAME")
var userName: String? = null

@ManyToOne
@JoinColumn(name = "TEAM_ID")
var team: Team? = null
}
Team.kt
1
2
3
4
5
6
7
8
9
@Entity
class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
var id: Long? = null

var name: String? = null
}
JpaMain.kt
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
32
33
fun main() {
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
val tx = em.transaction

tx.begin()

try {
// 저장
var team = Team()
team.name = "TeamA"
em.persist(team)

var member = Member()
member.username = "member1"
member.team = team
em.persist(member)

val findMember = em.find(Member::class.java, member.id)

val findTeam = findMember.team
if (findTeam != null) {
println("findTeam = " + findTeam.name)
}

tx.commit()
} catch (e: Exception) {
tx.rollback()
} finally {
em.close();
emf.close();
}
}
result
1
findTeam = TeamA
  1. 다대일(N:1)관계 이므로 @ManyToOne 사용
  2. 객체관계여서 Team에 아이디 Join을 하기 위한 @JoinColumn(name = "TEAM_ID") 사용
  3. find를 이용해서 Member에 있는 Team 객체를 이용할 수 있다.

양방향 연관관계와 연관관계의 주인

Team.kt
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
var id: Long? = null

var name: String? = null

@OneToMany(mappedBy = "team")
var members: MutableList<Member> = arrayListOf()
}

단방향과 같으나 members객체를 추가

JpaMain.kt
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
32
33
34
35
36
fun main() {
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
val tx = em.transaction

tx.begin()

try {
// 저장
var team = Team()
team.name = "TeamA"
em.persist(team)

var member = Member()
member.username = "member1"
member.team = team
em.persist(member)

em.flush()
em.clear()

val findMember = em.find(Member::class.java, member.id)
val members = findMember.team?.members

members?.forEach {
println("member = " + it.username)
}

tx.commit()
} catch (e: Exception) {
tx.rollback()
} finally {
em.close();
emf.close();
}
}
result
1
member = member1
  • 양방향 객체 연관관계는 회원 -> 팀 연관관계 1개(단방향), 팀 -> 회원 1개(단방향)서로 다른 방향으로 각각 1개씩 해서 2개(양방향)이라고 생각해야된다.
  • 테이블 연관관계는 PK, FK로 JOIN 을 해서 회원, 팀을 서로 알 수 있다.

연관관계의 주인(Owner)

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정(외래 키가 있는 곳을 주인으로 지정)
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용 안함
  • 주인이 아니면 mappedBy 속성으로 주인 지정

양방향 매핑시 주의점

연관관계 주인에 값을 입력 하지 않음

1
2
3
4
5
6
7
8
var member = Member()
member.username = "member1"
em.persist(member)

var team = Team()
team.name = "TeamA"
team.members.add(member)
em.persist(team)

  • Member테이블을 보면 TEAM_ID컬럼 값이 null 값인걸 확인 할 수 있다. 그 이유는 위에서 설정한(@OneToMany(mappedBy = "team")) 연관관계의 주인이 team 이므로 아래의 코드와 같이 주인을 먼저 insert 쿼리를 실행 해야된다. (mappedBy 설정한 부분은 읽기 전용)
1
2
3
4
5
6
7
8
var team = Team()
team.name = "TeamA"
em.persist(team)

var member = Member()
member.username = "member1"
member.team = team
em.persist(member)

순수한 객체 관계를 고려하면 양쪽에 값을 입력하는 것이 좋음

team.members 입력하면 영속상태(1차캐시)이므로 바로 조회해서 사용 할 수 있다.

1
2
3
4
5
6
7
8
9
10
var team = Team()
team.name = "TeamA"
em.persist(team)

var member = Member()
member.username = "member1"
member.team = team
em.persist(member)

team.members.add(member)

연관관계 편의 메소드를 생성

양쪽에 값을 입력 하다보면 실수를 할 수 있으니, 주인에 값을 입력할때 같이 입력 할 수 있는 함수(편의 메소드)를 생성

Member.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
var id: Long? = null

@Column(name = "USERNAME")
var username: String? = null

@ManyToOne
@JoinColumn(name = "TEAM_ID")
var team: Team? = null

// 편의 메소드 생성
fun changeTeam(team: Team) {
this.team = team;
team.members.add(this)
}
}
JpaMain.kt
1
2
3
4
5
6
7
8
var team = Team()
team.name = "TeamA"
em.persist(team)

var member = Member()
member.username = "member1"
member.changeTeam(team) // 편의 메소드 추가
em.persist(member)

양방향 매핑시에 무한 루프 조심

  • toString, JSON 생성 라이브러리 사용시 반복적으로 객체 를 호출 하면서 무한 루프에 빠지게 된다.
  • JSON 생성 라이브러리 사용시 Entity 를 반환하지 말고 DTO로 변환해서 반환을 해야 된다.

소스코드

참조