JPA/JPA 기본

16. 양방향 연관관계와 연관관계의 주인 1

sdafdq 2023. 10. 22. 20:10

 

양방향 연관관계

Member의 team과 Team의 members 처럼 양쪽 둘다 가지고 있는.

 

일단 테이블 연관관계는 바뀐 게 없다.

생각해보면, member에서 팀을 알고 싶으면 team의 team_id를 기준으로 조회해보면 되고,

team에서 자기 팀인 member를 알고 싶으면, 내 team_id를 가지고 있는 member들을 조회해 보면 된다.

그냥 join 하면 된다.

 

사실상 테이블의 연관관계는 방향이라는 개념 자체가 없다.

그냥 외래키 있으면 양쪽 둘 다 알 수 있다.

 

근데 객체가 문제다.

저렇게 객체지향적으로 바꾸게 되면, 

과거처럼 객체도 테이블 설계적으로 세팅했다면 그냥 외래키로 소통하면 되었을 테지만..

 

코드

@Entity
public class Member {
    public Member() {
    }

    public Member(String name, Team team) {
        this.name = name;
        this.team = team;
    }

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name="USERNAME", nullable = false)
    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

Member는 team을 가리킨다.

 

@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

members에 new ArrayList<>(); 해 줬는데,

이건 관례라고 보면 된다. 저렇게 해야 add해도 null이 안뜨니까.

 

팀 또한 Member들을 가리킨다.

 

즉, 객체상에서의 양방향 연관관계는

Member -> Team의 단방향 연관관계 하나와

Team -> Member의 단방향 연관관계 하나,

이 단방향 연관관계 두개를 그냥 억지로 양방향 연관관계 라고 부르는 것이다.

 

맨 위 그림을 보면 된다.

반면 테이블은 한쪽에서 외래키를 하나만 가지고 있어도(한쪽에서만 관리를 해도) 양방향 연관관계가 된다.

 

 

이렇게, 양방향 연관관계를 구현할 때 객체 상에서는 서로 참조를 가지고 있어야 하는 특성 때문에, 

이런 고민에 빠지게 된다.

원래 테이블에서는 양방향 연관관계라고 해도 Member에서만 외래키 가지고 관리를 했었다.

 

그러면 객체세상에서, 단방향 연관관계 2개를 억지로 양방향 연관관계라고 부르는 세상에서 

Member의 team을 바꿨을 때 

Member 테이블의 외래키를 바꿔야 하는 거냐,

 

아니면 Team의 Member들을 바꿨을 때

Member 테이블의 외래키를 바꿔야 하는거냐.

 

둘다 되긴 된다.

 

또,

Member의 Team에는 값을 넣고,

Team의 members에는 값을 안 넣는다면.

 

반대로,

Member의 team에는 값을 안넣고,

Team의 members에는 값을 넣는다면

 

아니면,

둘 다 값을 넣는다면.

 

그래서, 둘 중 하나면 명확하게 외래키를 관리 해야 한다.

 

Member의 Team으로 외래키를 관리할 지,

Team의 Members로 외래키를 관리할 지.

 

주인이 아닌 쪽은 오직 읽기만 가능하다.

 

지정하는 법은,

주인이 아닌 쪽에서 mappedBy, 즉 내가 저거에 의해 매핑되었다는 저 속성을 사용해서 주인을 지정해 준다.

 

누구를 주인으로 해야 할 까? 고민이라면,

원래 외래키를 가지고 있던 쪽으로 주인을 해라. 이유는 성능이슈도 좀 있고(아니면 한번 거치게 되는 거니까.), 명확하니까. 

 

Member 테이블이 외래키를 가지고 있었으니, 

Member의 team이 외래키를 관리할 주인

 

 

다시, 코드

@Entity
public class Member {
    public Member() {
    }

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name="USERNAME", nullable = false)
    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

team을 참조로 가지고 있고,

 

@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

mappedBy = "team"

저 team 저 객체에 의해 매핑되었다는 뜻임. Team에서는 읽기만 가능함.

"team" 이게 진짜 Member의 team 객체 말하는 거임.

 

그래서, 이제 Member가 주인이기 때문에,

Member를 통해서 team을 바꿔야 함.

Team의 members를 바꿔도 DB에 반영되지 않음.

읽기 전용 이기 때문에 DB에 반영할 때에는 저 값을 쓰지 않는다.

 

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member1 = new Member("memberA", team);
Member member2 = new Member("memberB", team);

em.persist(member1);
em.persist(member2);

em.flush();
em.clear();

Member findedMember  = em.find(Member.class, member1.getId());
List<Member> members = findedMember.getTeam().getMembers();

for (Member tMember : members) {
    System.out.println("tMember = " + tMember.getName());
}

em.flush();
em.clear();

Team team2 = new Team();
team2.setName("TeamB");
em.persist(team2);

Team findedTeam = em.find(Team.class, 1L);
List<Member> teamMembers = new ArrayList<>();

teamMembers.add(new Member("BteamMember1", team2));
teamMembers.add(new Member("BteamMember2", team2));

findedTeam.setMembers(teamMembers);

tx.commit();

TeamA 만들고, 등록

member1, member2 만들고 TeamA로 둘다 등록.

그 다음 flush 해서 쿼리 보내주고, 영속성 컨텍스트 비워서 초기화.

 

그 다음 Member 찾아온 다음, 그 다음 Member의 Team에 접근.

그 Team에 접근해서, Team이 가지고 있는 members를 가져와서, 조회해봄.

잘 나옴.

내가 따로 Member들을 가져온 것이 아님에도 불구하고 그 Team에 소속되어 있는 Member들이 모두 다 잘 나옴.

 

다시 쿼리 보내고 영속성 컨텍스트 초기화,

이제 TeamB라는 새 팀을 만들어 봄.

등록.

 

TeamA를 DB에서 찾아옴. 

이제 members를 아예 새로 생성해서, 

찾아온 TeamA에다가 넣어봄.

 

DB에 전혀 반영이 안됨.

 

 

 

그 후,

ddl.auto none으로 하고,

Member member = em.find(Member.class, 1L);
Team team = new Team();
team.setName("TeamC");
em.persist(team);

member.setTeam(team);

tx.commit();

이렇게 member 찾아와서,

새팀 만들고,

team에 id 없으니까 등록해줘서 완전하게 만들어 주고,

 

member의 team을 바꿨더니 이거는 DB에 잘 반영됨.

주인이기 때문에.

 

 

 

추가로 보면

외래키가 있는 쪽이 무조건 다

하긴 생각해보면 그게 맞지.

외래키가 있다는 것은 바깥 기준을 참고하는 거니.

즉, 다 쪽이 거의 연관관계 주인이 된다고 보면 됨.

 

비즈니스 적인거랑은 관계가 없음.

예를 들자면 비즈니스에서는 자동차와 자동차 바퀴 중 자동차가 더 중요하지만,

지금은 자동차 바퀴를 연관관계 주인으로 해 놓은 거 라고 보면됨.

 

 

 

 

꼭! 연관관계의 주인에 값을 입력.

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

team.getMembers().add(member);
em.persist(member);

이렇게 하면 member 조회해보면 team_id 가 null 뜸.

team.getMembers()는 그냥 읽기 전용이라, DB와 통신할 때는 저 값을 반영하지 않음.

'JPA > JPA 기본' 카테고리의 다른 글

18. 실전2 객체구조를 외래키에서 참조로  (0) 2023.10.24
17. 양방향 연관관계 주의점  (0) 2023.10.23
15. 연관관계 매핑  (0) 2023.10.22
14. 예제  (0) 2023.10.22
13. 기본 키 매핑  (0) 2023.10.21