5. 연관관계 매핑 기초 - (5.2 양방향 연관 관계와 연관관계의 주인2 - 주의점, 정리)

HotFried·2023년 9월 26일
0

양방향 매핑시 가장 많이 하는 실수

연관관계의 주인에 값을 입력하지 않는다.

public class JpaMain {

    public static void main(String[] args) {
        ...
        Team team = new Team();
        team.setName("TeamA");
        em.persist(team);
        
        Member member = new Member();
        member.setName("member1");

		//역방향(주인이 아닌 방향)만 연관관계 설정
		team.getMembers().add(member);

        em.persist(member);
    }
}
insert into Member(team_id, username, member_id) values (?, ?, ?)
insert into Team(name, team_id) values (?, ?)

  • insert 쿼리는 두번 나갔지만, 회원에 TEAM_ID가 저장되지 않았다.
  • JPA는 insert, update시 읽기 전용 필드를 고려하지 않는다.
    - 연관 관계의 주인은 Member.team이다.
    - Team.members는 mappedBy 된 읽기 전용이다.
    - Team.members에서 수정을 했으니 반영되지 않은 것이다.
public class JpaMain {

    public static void main(String[] args) {
        Team team = new Team();
        team.setName("TeamA");
        em.persist(team);

        Member member = new Member();
        member.setName("member1");
        
        // 연관 관계의 주인인 Member.team에 추가한다.
        member.setTeam(team);

        em.persist(member);
    }
}

team.getMembers().add(member);가 아닌 member.setTeam(team)'코드를 작성해야한다.

연관관계의 주인에 값을 꼭 입력하자


양방향 연관관계 주의사항

양쪽에 값을 설정하지 않는다.

public class JpaMain {

    public static void main(String[] args) {
        Team team = new Team();
        team.setName("TeamA");
        em.persist(team);

        Member member = new Member();
        member.setName("member1");
        
        // 연관 관계 주인 값 넣기
        member.setTeam(team);

        em.persist(member);

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

        Team findTeam = em.find(Team.class, team.getId());
        List<Member> members = findTeam.getMembers();

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

위 코드를 자세히 살펴본다.

team, memberem.persist()를 통해 영속 상태로 만들어 주었고,
member.setTeam(team) 코드를 통해 연관관계의 주인데 값을 넣어주었다.
이후em.flush() 플러시를 통해 DB에 직접 insert Query를 넣어주었다.

여기서 em.find(Team.class, team.getId())코드가 실행되면 영속성 컨텍스트에 존재하는 team의 Id를 조회해서, findTeam객체를 얻게 된다.

List<Member> members = findTeam.getMembers(); 코드를 살펴보자
우리는 위 코드에서 Team엔티티의 members필드에 값을 추가해주지 않았다.
하지만 코드 실행 시 select Query가 나가면서 원하는 값 m = member1이 출력된다.

=> 양쪽 모두에 값을 설정하지 않았지만, 직접 em.flush()코드를 통해 DB에 데이터를 추가하였기 때문에 select Query를 통해 값을 얻을 수 있다.


만약 위 코드에서 em.flush()코드를 실행하지 않으면 어떤 결과가 나올까?

teamem.persist(team) 코드를 통해 영속상태로 전환되고 1차 캐시에 들어간다.
우리는 teammembers필드에 어떤 값도 추가하지 않았다.

Team findTeam = em.find(Team.class, team.getId()); 코드가 실행되면 영속성 컨텍스트에 존재하는 team의 Id를 조회해서, findTeam객체를 얻게 된다.

이후 List<Member> members = findTeam.getMembers(); 코드가 실행될 때
1차캐시에도 DB에도 findTeam에 어떤 member가 존재하는지에 대한 정보가 없으므로 어떠한 결과도 출력되지 않는다.

commit()시점에 insert Query만 나갈 뿐이다.


∴ 따라서 양쪽 모두 값을 설정하도록 주의한다.


연관관계 편의 메서드 생성

public class Member {
    ...

    // 연관 관계 메서드
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}

Member엔티티에서 연관 관계 메서드를 만든다.
this.team = team 코드를 통해 연관관계의 주인에 값을 넣을 수 있고
team.getMembers().add(this) 코드를 통해 반대 쪽에도 값을 넣을 수 있다.

즉, 연관관계 편의 메서드를 통해 양쪽에 값을 설정하는 것을 원자적으로 실행할 수 있는 것이다.


  • 연관관계 편의 메서드는 반대로 Team 엔티티에서도 가능하다.
  • Member, Team 양쪽에서 연관관계 편의 메서드를 사용하면 문제가 될 수 있다.
    -> 둘 중 하나만 사용하도록 하자.

무한루프를 조심하자

public class Member {

    @Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", name=" + name + '\'' +
                // 무한 루프
                ", team=" + team + '\'' +
                '}';
    }
}
public class Team {

    @Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", name=" + name + '\'' +
                // 무한 루프
                ", members=" + members + '\'' +
                '}';
    }
}

Member, Team엔티티에 모두 toString()을 오버라이딩 했다.
만약, 어떠한 Team엔티티를 출력한다고 가정해보자.

TeamtoString()메서드에 members즉, member엔티티의 배열이 존재한다.
Member엔티티의 toString() 메서드에는 team이 존재한다.

toString()메소드 사이에서 무한루프가 발생하게 된다.


정리

양방향 매핑

  • 가능하면 단방향 매핑으로 끝내도록 한다.
    (단방향 매핑만으로도 이미 연관 관계 매핑은 완료된다.)

  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색)하는 기능을 추가한 것 뿐이다.
    -> 양방향을 매핑을 하면 고려할 것들만 많아진다.

  • 실무에서 JPQL로 역방향 참조가 필요한 경우 그때 추가하면 된다.
    -> 단방향 매핑을 잘 해놓은 다음, 양방향은 필요할 때 사용


연관관계의 주인을 정하는 기준
비즈니스 로직 기준 X, 외래 키의 위치 기준 O
-> N(다)에 해당하는 엔티티를 주인으로 정한다.


참고 :

김영한. 『자바 ORM 표준 JPA 프로그래밍』. 에이콘, 2015.

자바 ORM 표준 JPA 프로그래밍 - 기본편

profile
꾸준하게

0개의 댓글