[JPA] 연관관계의 주인 (JPA 기본편 by 김영한)

su_y2on·2022년 1월 24일
0

JPA

목록 보기
7/17
post-thumbnail

연관관계의 주인

지금까지 단방향 매핑을 했다면 이번에는 반대로 team.members와 같이 팀의 Member들을 역참조할 수 있도록 해서 양방향 객체 연관관계를 만들어보겠습니다.





Team에 members리스트를 추가해줍니다 그리고 team의 입장에서 @OneToMany 어노테이션을 붙여준 뒤 mappedBy에는 연관될 객체자체를 넣어줍니다. 바로 Team클래스의 team이 그 객체이기 때문에 넣어주면 됩니다.

@Entitypublic class Team {private String name;



	@Id @GeneratedValueprivate Long id;@OneToMany(mappedBy = "team")List<Member> members = new ArrayList<Member>();
    
 }

이제 아래와 같이 역참조가 가능해집니다.

team.getMembers();




주인은 누가?

객체에서의 연관관계는 양방향으로 관리하기 위해서 단방향 2개를 만들어줬습니다. 하지만 테이블의 경우 FK값 하나로 JOIN을 통해서 양쪽으로 모두 조회가 가능하기 때문에 관계를 생성하는 것 자체가 양방향이 됩니다.

연관관계의 주인이라는 것은 객체 두 관계중 하나를 연관관계의 주인으로 지정하는 것입니다. 연관관계의 주인만이 외래키를 관리할 수 있게 하는 것인데요. 즉 연관관계의 주인쪽만 연관관계를 등록하고 수정할 수 있습니다. 주인이 아닌 쪽은 읽기만 가능합니다. 연관관계의 주인은 실제 테이블에서 외래 키가 생성되는 객체로 선택하면 됩니다. 여기서는 Member가 주인 객체가 됩니다. 코드로는 주인이 아닌 쪽에 mappedBy를 적어주면됩니다.


주의할 사항은 주인만이 등록과 수정이 가능하다는 것입니다. 따라서 아래와 같이 한다면 연관관계가 생성되지 않습니다.

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.setTeam(team);




주의사항

그렇다면 아래와 같이 하면 어떻게 될까요? 주인쪽에서 연관관계를 생성했기 때문에 members를 print해보면 프린트가 될까요? .. 결과는 그렇지 않습니다. 이유는 아직 관계가 생성되지 않았기 때문입니다. 더 쉽게 말하면 DB에 아직 방영이 안됐기 때문입니다. 아직 1차 캐시에 저장만 된 상태기 때문에 영속성 컨텍스트에서는 둘의 관계를 알지 못합니다. 따라서 members는 아무것도 출력되지 않습니다.

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

Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 주인입장에서 연관관계생성 
em.persist(member);

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

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

System.out.println("==========");

tx.commit(); // DB에 쿼리가 날라가는 순간




아래와 같이 코드를 바꿔서 flush로 DB에 쿼리를 날려주고 캐시를 참고하지않도록 clear까지 해주면 members가 DB에서부터 가져오기때문에 출력이 됩니다.

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

Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 주인입장에서 연관관계생성 
em.persist(member);

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

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

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

System.out.println("==========");

tx.commit(); // DB에 쿼리가 날라가는 순간



하지만 매번 이렇게 영속성컨텍스트에서 어떻게 행동할지 생각하면서 flush와 clear를 시켜줘야하는 것은 번거롭고 실수할 가능성도 많아집니다. 그래서 항상 아래와 같이 객체 차원에서 양쪽에 모두 적용을 시켜줍니다.

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

Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 주인입장에서 연관관계생성 
em.persist(member);

team.getMembers().add(member); // flush, clear 안하고 관계 조회할때는 꼭필요 + 객체지향적 설계

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

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

System.out.println("==========");

tx.commit(); // DB에 쿼리가 날라가는 순간




여기서 조금만 더 최적화를 하자면 Member 클래스에 아래와 같이 team을 넣어줄 때 members객체에도 member를 넣어주는 일을 한번에 하도록 changeTeam을 만들어줍니다

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

최종적으로 최적화 된 코드는 아래와 같습니다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.changeTeam(team); // 주인입장에서 연관관계생성 
em.persist(member);

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

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

System.out.println("==========");

tx.commit(); // DB에 쿼리가 날라가는 순간





마지막 💣 주의사항💣 은 설계를 할때 단방향(주인입장)으로 최대한 만들고 역참조가 필요한 경우에만 members같은 객체를 만들어줘서 양방향으로 관리하면됩니다. 이때 단방향이던 양방향이던 테이블 차원에서는 변화가 없습니다. 불필요하게 코드를 작성해줄 필요가 없기 때문에 객체입장에서는 단방향으로 최대한 해결을 하면 좋습니다.

0개의 댓글