-
JPA (4) - 연관 관계 매핑 (양방향,단방향)카테고리 없음 2024. 1. 11. 12:47
> 기본 용어 방향 : 양방향, 단방향 (객체에만 존재 참조가 양쪽에 있는지 한쪽에만 있는지)
> 다중성 : 다대일,일대다,일대일,다대다
> 연관관계의 주인 : 객체 양방향 연관관계에서 주인이 필요하다 (외래키 관리 -> 변경시 DB에 반영될 객체)
1. 연관관계가 필요한 이유
엔티티를 테이블에 맞추어 설계시 아래와 같음
@Entity public class Member{ @Id @GeneratedValue private Long id; @Column(name="USERNAME") private String name; @Cloumn(name="TEAM_ID") private Long teamId; }
- 참조 대신 외래키를 직접 저장해서 사용함
Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); member.setTramId(team.getId); em.persist(member); //기본 시퀀스 전략이라 다른테이블이지만 1씩증가함..! (동일 시퀀스 공유) //다르게 하고 싶으면 시퀀스 생성해서 각각하면될듯 //조회 Member findMember = em.find(Member.class,member.getId()); Team findTeam = em.find(Team.class,team.getId()); //member가 소속된 팀을 찾고싶은데 연관관계 딱히 없어서 가지고 있는 식별자로 따로 조회해야함
> 객체를 테이블에 맞추면 협력관계 (그래프 탐색)를 만들 수 없다.
2. 단방향 연관관계
출처: JPA기본(김영한) - 인프런 @Entity public class Member{ @Id @GeneratedValue private Long id; @Column(name="USERNAME") private String name; @ManyToOne @JoinColumn(name = "TEAM_ID") private Team teamId; }
- 여러 멤버가 한개의 팀에 소속됨을 표현
- JoinColumn은 실제 테이블에 외래키 저장할 칼럼 설정하는 것 (TEAM_ID에 TEAM의 기본키 저장)
- 기본키도 저장하고, 연관관계도 맺고!
Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); member.setTramId(team); em.persist(member); Member findMember = em.find(Member.class, member.getId)); Team findTeam = findMember.getTeam(); //** //그냥 이런식으로 작성하면, 1차캐쉬에서 가져옴 따라서 쿼리 보고싶으면 //flush(); clear()날리자
3. 양방향 연관관계와 연관관계의 주인
- 테이블의 연관관계는 기본적으로 fk하나로 join을 통해 둘다 식별가능
- 객체는 참조를 통해 식별하므로, 양방향을 위해선 (사실은 단방향 두번) 참조를 알고 있어야한다.
출처: JPA기본(김영한) - 인프런 @Entity public class Member{ @Id @GeneratedValue private Long id; @Column(name="USERNAME") private String name; @ManyToOne @JoinColumn(name = "TEAM_ID") private Team teamId; } @Entity public class Team{ @Id @generatedValue private Long id; private String name; @OneToMany(mappedBy = "team") List<Member> members = new ArrayList<Member>(); }
Team findTeam = em.find(Team.class, team.getId()); //Member의 Team fk와 Team의 pk join해서 Team 생성할때 쫙 가져옴
3.1 연관관계의 주인 mappedBy
> fk를 관리 -> 영속성 컨텍스트에서 변화를 감지하고 DB에 반영하는 쪽 정하는 것을 의미
> fk는 무조건 Many쪽에 생성됨! (테이블이 원래 그러하다) 또한 외래 키 하나로 두 테이블의 연관관계를 관리
> 주인으로 지정된 참조가 변화하는 것은 감지하고, DB에 반영
> 종속된 것이 변화하면 DB반영 안함 (어플리케이션 레벨에서만 반영됨)
* Team으로 예를 들면 Team을 DB에서 find할때 Member List를 가지고 오는데 어플리케이션에서 List의 값을 변경해도
DB에 반영안됨 그냥 읽기전용
* 만약 이런 개념이 없다면 DB는 Team에 변화를 DB에 반영할지, Member에 변화를 반영할지 알 수 없음!
-> 만약 Member에 setTeam은 없고 Team에 add만 있다면? 이런 불일치를 막기 위해 한쪽에만 위임
3.2 양방향 매핑 규칙
> 객체 중 하나를 연관관계의 주인으로 지정 (외래키 관리)
> 주인이 아닌쪽은 읽기만 가능
> 그러면 양방향에서 어디를 주인으로 지정해야하는가? :
- 테이블 기준으로 fk가 있는 쪽을 주인으로 두자!
- 테이블은 무조건 다쪽에 fk가 생성 따라서 위 상황이면 Member를 주인으로 두자
- Team을 주인으로 둘 수도 있음! -> 하지만, 어차피 이런식으로 맵핑해서 테이블 생성하더라도 항상 Many쪽에 fk생성됨
- 관계 파악이 쫌 힘들 수 있고, 또한 Team을 수정했는데 Member도 같이 수정되는 이상현상 겪을 수도 있다.
3.3 양방향 매핑 시 자주하는 실수
- 주인 아닌 쪽에 업데이트
Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("Member1"); //주인 아닌 곳에만 수정 -> DB반영안됨 차라리 반대만 해라.. team.getMembers().add(member); em.persist(member);
-> 이런 실수를 방지하고, 객체설계를 고려했을 때 둘다 초기화해두자
-> 엔티티에 연관관계 편의 메서드를 지정해서 사용하도록 하자 (이때 어느쪽이 가질 지 정해야함)
public void setTeam(Team team) { this.team = team; this.team.getMembers().add(this); } //사실은 team을 set하면, 기본 team에서 member제거하고, 해야하는데 이정만해도 충분할 수도 있다함! //어차피 update됨 team은 그냥 읽기만하고
-> 양방향 매핑시 무한 루프 매우 주의해야함
ex) toString(),JSON생성 라이브러리
toString -> Team.toString() -> members.toString-> member.toStirng()..반복
JSON문제는 컨트롤러에서 엔티티 반환하는 것을 금지!! toString()은 적절하게 알아서 잘 빼고쓰던지해라
아마 한 쪽만 빼주면 무한루프 안할 것 같긴함
4. 정리
- 일단 단방향 매핑으로도 연관관계는 충분하다
- 양방향 매핑은 반대 조회(그래프 탐색)이 꼭 필요할 때만 동적으로 추가하자 (어차피 테이블에 영향 없음)
- JPQL에서 특히 역방향 탐색할 일이 많다.
- 또한 외래키 관리 기준으로 잡아야함 비지니스상 우위를 주인으로 정하면 안됨