JPA - 연관관계 매핑 기초

노력하는 배짱이·2022년 7월 29일
0

JPA

목록 보기
4/9

용어
1. 방향 (Direction) : 단방향, 양방향
2. 다중성 (Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
3. 연관관계의 주인 (Owner) : 객체 양방향 연관관계는 관리 주인이 필요

1. 단방향 연관관계

예시 시나리오

  • 회원과 팀이 존재
  • 회원은 하나의 팀에만 소속
  • 회원과 팀은 다대일 관계
  • 객체를 테이블에 맞추어 데이터 중심을 모델링하면, 협력 관계를 만들 수 없다.
  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다. (테이블에 맞추어 모델링)
    -> 아래 예시처럼 Member 엔티티 안에 TeamId 가 프로퍼티로 들어가 있는 상태
    -> Team 을 조회할 때, Member 엔티티 안에 있는 TeamId를 찾아서 Team을 조회하게 됨 즉, 객체 지향적인 방법이 아님
@Entity
class Member(
    @Id
    @GeneratedValue()
    @Column(name = "MEMBER_ID")
    val id: Long = 0,

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

    @Column(name = "TEAM_ID")
    var teamId: Long
)

val team = Team(name = "TeamA")

em.persist(team)

val member = Member(
	username = "member1",
	teamId = team.id
)

em.persist(member)

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

// 연관관계가 없음
val findTeam = em.find(Team::class.java, findMember.teamId)

tx.commit()
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.(객체 지향 모델링)
    -> Member 엔티티 안에 TeamId 가 아닌 Team 객체를 프로퍼티로 넣는다.
    -> Member 와 Team 은 N : 1 관계이기 때문에, Member 엔티티 안에 Team 프로퍼티에는 @ManyToOne 을 지정한다.
    -> @JoinColumn 으로 TEAM_ID 를 지정한다.
@Entity
class Member(
    @Id
    @GeneratedValue()
    @Column(name = "MEMBER_ID")
    val id: Long = 0,

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    val team: Team
)

val team = Team(name = "TeamA")
em.persist(team)

val member = Member(
	username = "member1",
	team = team
)

em.persist(member)

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

val findTeam = findMember.team
        
tx.commit()

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

2.1 양방향 매핑

  • 회원(Member)과 팀(Team)은 N:1 관계

  • 회원에서 팀을 조회(객체 그래프 탐색)할 수 있고, 팀에서도 해당하는 회원들을 조회할 수 있어야 함
    즉, 양방향 연관관계가 형성

  • 아래 Team 엔티티 코드를 보면 Member 객체를 받는 컬렉션이 추가됨
    그로인해 Team 엔티티를 통해 Member 를 조회할 수 있음 (즉, 반대 방향 객체 그래프 탐색)
    -> Team 엔티티와 Member 엔티티는 1:N 이기 때문에, @OneToMany를 지정
    -> mappedBy 값은 Member 엔티티에 정의된 Team 변수 이름으로 지정

class Team(
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    val id: Long? = null,

    var name: String,

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

// Main

// Team 조회
val findTeam = em.find(Team::class.java, team.id)
// Team 에 속한 Member 조회
val members: MutableList<Member> = findTeam.members

2.2 연관관계 주인

  • 테이블 연관관계는 외래키 하나로 각 테이블의 연관 관계를 관리
  • 객체 연관관계는 각 객체에 단방향 연관관계를 만들어서 양방향 관계처럼 관리

객체의 양방향 관계일 때 어떤 객체에서 외래키를 관리해야 하는가?

연관 관계의 주인 (Owner)

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정

  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)

  • 주인이 아니면 mappedBy 속성으로 주인 지정하고, 읽기(Read)만 가능

  • 외래키가 있는 곳을 주인으로 지정하기
    -> 회원(Member) 엔티티 안에 팀(Team) 외래키가 있으니, 회원 엔티티가 주인이 됨

  • 양방향 매핑시, 항상 순수 객체 상태를 고려해서 연관관계 주인, 주인이 아닌 엔티티 즉 양쪽에 값을 설정
    -> 연관관계 편의 메소드를 생성 (아래 예시 코드)

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

fun updateTeam(team: Team) {
    this.team = team
	team.members.add(this)
}
  • 양방향 매핑시, 무한 루프 조심
    -> toString(), lombok, JSON 생성 라이브러리

정리

  • 단방향 매핑만으로 이미 연관관계 매핑은 완료
    -> 양방향 매핑을 굳이 먼저 할 필요 없으며, 필요할 때 구현하면 됨 (테이블 영향 X)
  • 양방향 매핑은 반대 방향으로 조회 기능이 추가된 것
  • JPQL에서 역방향으로 탐색할 일이 많음

참고 : 인프런 강의[자바 ORM 표준 JPA 프로그래밍 - 기본편]

0개의 댓글