연관관계 - 1

박미소·2024년 1월 31일
0

코틀린

목록 보기
38/44
  1. FK만 집어 넣은 연관관계 매핑
@GetMapping("/prac1")
fun practice1() {
	val emf = Persistence.createEntityManagerFactory("hello")
    val em = emf.createEntityManager()
    
    // transaction
    val transaction = em.transaction
    transaction.begin()
    
    var team = Team()
    team.teamName = "테스트 팀네임"
    
    val member = Member()
    member.memberName = "테스트 멤버네임"
    member.teamId = team.id
    em.persist(member)
    
    transaction.commit()
    
    em.close()
    emf.close()
}

team id로 매핑은 되지만 DB와 DB 사이를 억지로 집어넣은 느낌이 든다.

동작도 가능하지만 객체를 집어넣어서 매핑하는게 객체지향적으로 좋다.

이렇게 나온 개념이 ManyToOne, OneToMany라고 한다.

@Entity
class Member {
	@Id @GeneratedValue
    var id: Long = 0
    
    @Column(name = "title")
    var memberName: String = ""
    
    @Column(name = "team_id")
    var teamId: Long = 0L
    
    var team: Team
}

객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

  • 테이블: 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.

  • 객체 : 참조를 사용해서 연관된 객체를 찾는다.

    ==> 테이블과 객체 사이에는 이런 큰 간격이 있다.


  1. 단방향 지정
@Entity
class Member {
	@Id @GeneratedValue
    var id: Long = 0
    
    @Column(name = "title")
    var memberName: String = ""
    
    @Column(name = "team_id")
    var teamId: Long = 0L
    
    @ManyToOne
    var team: Team? = null
}

team id 를 외래키(FK)로 지정한다.

이렇게 단방향만으로도 모든 매핑이 가능!

그래서 설계할 때 단방향으로만 테이블 연관관계를 맺어놓고 만약, MEMBER가 TEAM을 참조해야 하는 경우에만 양방향으로 바꾸는 것! 처음부터 양방향 막 쓰지 않도록 주의!!


  1. 단방향 활용
@GetMapping("/prac1")
fun practice1() {
	val emf = Persistence.createEntityManagerFactory("hello")
    val em = emf.createEntityManager()
    
    // transaction
    val transaction = em.transaction
    transaction.begin()
    
    var team = Team()
    team.teamName = "테스트 팀네임"
    em.persist(team)
    
    val member = Member()
    member.memberName = "테스트 멤버네임"
    member.team = team
    em.persist(member)
    
    transaction.commit()
    
    em.close()
    emf.close()
}
@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
)

일대다 중 멤버가 FK의 주인이므로 멤버에 팀 객체를 담는다.

=> FK의 주인에 One객체를 집어 넣는다.

객체지향적이고 DB문제도 해결(DB를 어거지로 묶는 위의 예시 해결)

=> 이렇게 하면 join의 성질도 갖게 된다.


  1. 양방향 연관관계

만약 팀에 멤버 리스트가 존재한다면?

@Entity
class Team {
	@Id @GenenratedValue
    var id: Long = 0
    
    @Column(name ="title")
    var teamName: String = ""
    
  	@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true)
    val member: MutableList<Member> = mutableListOf()
}

한 팀에 여러멤버가 들어오니 멤버 매핑할 때 리스트로 설정

이렇게 해도 테이블 연관관계는 단방향과 변함이 없다.

양방향으로 하든 안하든 단방향으로 하면 DB 연결이 다 끝난다.

그래서 처음에 작업할 때 단방향으로 다 연결하고 나중에 도메인 로직을 작성할 때 댓글을 통해서 게시글을 찾게 된다면 양방향 연결하기(서비스 로직)

양방향은 객체지향 문제이기에 DB와는 아무관계가 없다.


  • 연관관계의 주인(mappedBy)
    연관관계의 주인을 지정한다는 것은, 객체의 두 관계 중 제어의 권한(데이터 조회, 저장, 수정, 삭제)를 갖는 실질적인 관계가 누구인지 JPA에게 알리는 것이다.

따라서 연관관계의 주인은 연관 관계를 갖는 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만, 주인이 아니면 조회만 가능하다.

주인은 외래키가 있는 곳으로 지정하면 된다.

Member엔티티가 외래 키를 가지고 있으믕로 연관관계의 주인이 된다.

연관관계의 주인이 아닌 객체는 mappedBy 속성을 사용한다.

  • mappedBy == 나는 키의 주인이 아니다.
  • team이 member에 묶여 있다. team은 member 객체만 조회할 수 있다, Team 엔티티의 member리스트는 mappedBy로 읽기 전용

<양방향 매핑 규칙>

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능(team에 해당)
@GetMapping("/prac1")
fun practice1() {
	val emf = Persistence.createEntityManagerFactory("hello")
    val em = emf.createEntityManager()
    
    // transaction
    val transaction = em.transaction
    transaction.begin()
    
    val member = Member()
    member.memberName = "새로운 멤버네임"
    em.persist(member)
    
    
	val team = Team()
    team.teamName = 새로운 팀네임"
    team.member.add(member)
    em.persist(team)
    
    
    transaction.commit()
    
    em.close()
    emf.close()
}

만약 주인이 아닌 방향(역방향)만 연관관계 설정을 하면 Member 테이블의 TEAM_ID는 NULL 값이 들어간다.


올바르게 사용하려면,

@GetMapping("/prac1")
fun practice1() {
	val emf = Persistence.createEntityManagerFactory("hello")
    val em = emf.createEntityManager()
    
    // transaction
    val transaction = em.transaction
    transaction.begin()
    
	val team = Team()
    team.teamName = "Z새로운 팀네임11"
    em.persist(team)
    
    val member = Member()
    member.memberName = "새로운 멤버네임11"
    member.team = team
    em.persist(member)
    
    
    transaction.commit()
    
    em.close()
    emf.close()
}

멤버에다가 팀을 넣어야 팀 아이디가 DB에 들어간다.

mappedBy Member List가 읽기 전용이라 멤버 추가를 JPA가 무시해버린다.


매번 양방향의 코드를 추가하는 것이 힘들기에 연관관계 편의 메서드 생성 권장

@Entity
class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    var id: Long
 
    @Column(name = "USERNAME")
    val username: String
 
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    val team: Team
 
	// 연관관계 편의 메서드
    fun addTeam(team: Team) {
        this.team = team;
        team.getMembers().add(this);
    }
    
    ... 생략
}

0개의 댓글