JPA- 연관관계 외래키의 주인과 주인의 참조자 관계

2021. 4. 16. 00:54 Spring Data/Spring Data JPA

JPA에서 일대일,일대다,다대일,다대다 관계에서는 항상 연관관계의 주인이 존재한다. 연관관계의 주인이라고 함은 데이터베이스 테이블에서 외래키의 주인을 뜻한다. 보통 일대다,다대일(다대다 포함, 다대다는 일대다,다대일관계로 매핑을 시킴) 관계에서는 보통 다쪽에 외래키가 존재한다. 일단 예제를 보면서 설명하겠다.

 

연관관계 예시

package com.spring.jpa.entitiy;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
 
@Entity
@Table(name = "TEAM_MEMBER")
public class TeamMember {
    
    @Id
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @Column(name = "MEMBER_NAME")
    private String memberName;
    
    //연관관계 매핑, 한팀에 여러 회원이 소속될 수 있음으로 다대일 관계이다.
    @ManyToOne
    //외래키로 사용될 컬럼의 이름이다.(디폴트는 해당 객체의 필드명+조인테이블의 칼럼명이다.)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getMemberName() {
        return memberName;
    }
 
    public void setMemberName(String memberName) {
        this.memberName = memberName;
    }
 
    public Team getTeam() {
        return team;
    }
    
    //연관관계에서는 객체관점에서도 똑같이 값을 세팅해주는 것이 좋다.
    //team.getMembers().add(this)는 데이터베이스에는 절대 영향을 미치지는 않지만
    //객체관점에서는 같이 set을 해주는 것이 맞다.
    public void setTeam(Team team) {
        this.team = team;
        
        if(!team.getMembers().contains(this)) {
            team.getMembers().add(this);
        }
    }
 
    @Override
    public String toString() {
        return "TeamMember [id=" + id + ", memberName=" + memberName + ", team=" + team.getName() + "]";
    }
    
}
package com.spring.jpa.entitiy;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name = "TB_TEAM")
public class Team {
    
    @Id
    @Column(name = "TEAM_ID")
    private Long id;
    
    
    @Column(name = "TEAM_NAME")
    private String name;
    
    //컬렉션의 제네릭타입이 존재할때
    //양방향 관계를 정의할때, mappedBy = "team" 여기서 team은 TeamMember테이블의
    //Team 엔티티의 필드 네임이다.(테이블 칼럼명이 아니다)
    @OneToMany(mappedBy = "team")
    private List<TeamMember> members = new ArrayList<TeamMember>();
    
    //컬렉션의 제네릭타입이 존재하지 않을때
    /*@OneToMany(targetEntity=TeamMember.class)
    private List members;*/
 
    public Long getId() {
        return id;
    }
 
 
    public void setId(Long id) {
        this.id = id;
    }
 
 
    public String getName() {
        return name;
    }
 
 
    public void setName(String name) {
        this.name = name;
    }
 
 
    public List<TeamMember> getMembers() {
        return members;
    }
 
 
    public void setMembers(List<TeamMember> members) {
        this.members = members;
    }
 
 
    @Override
    public String toString() {
        return "Team [id=" + id + ", name=" + name + ", members=" + Arrays.toString(members.toArray()) + "]";
    }
    
    
}

 

데이터베이스에서는 양방향 참조가 가능하다.(외래키를 이용) 하지만 JPA에서는 사실 양방향이라는 것은 존재하지 않는다. 즉, 각각 다른 단방향으로 마치 양방인것처럼 꾸미는 것이다. 여기서 중요한 것은 연관관계의 주인인 TeamMember에서는 JPA로 조회를 할 경우 연관관계에 있는 Team 필드가 데이터로 채워진다. 그말은 즉, 객체 그래프탐색이 가능하다는 것이다. 하지만 mappedBy ="team"으로 어노테이션 속성이 달린 연관관계의 주인이 아닌 엔티티는 JPA로 조회를 해도 연관관계에 있는 엔티티리스트를 가져오지 않는다. 그렇기 때문에 연관관계의 주인인 엔티티의 setter에서 위와같은 로직이 포함되어야 한다. 그래야 연관관계의 주인이 아닌 엔티티에서도 객체 그래프 탐색이 가능하다. 왜냐하면 영속성 컨텍스트에서는 원래 엔티티의 복사본을 가지고 있기 때문에 원래 엔티티의 복사본과 1차캐시에서 가져간 엔티티를 비교하여 변경이 발생한다면 1차 캐시에 변경된 내용을 반영하여 주인이 아닌 엔티티에서도 해당 연관관계의 엔티티 데이터가 반영이 된다. 하지만 여기서 중요한 것은 setter로 엔티티를 변경하던가 위의 setter에 로직이 추가되어 List에 add가 되던 1차캐시의 복사본과 비교를 통해 변경을 반영하려면 반드시 트랜잭션 내에 존재해야한다는 것이다.

 

출처: https://coding-start.tistory.com/73?category=781616 [코딩스타트]