티스토리 뷰
단방향 연관관계
객체지향 모델링
하나의 팀이 여러 개의 멤버를 가질 때 보통 Member에서 team_id를 갖고 있는 설계를 많이 하게된다.
하지만 객체지향에서는 외래키 대신 객체 자체를 들고 있는 형태가 되어야 한다.
그래서 위와 같은 내용으로 Member쪽에 이렇게 설정을 해야한다.
@Entity
public class Member {
중략..
@ManyToOne(fetch = FetchType.LAZY) //Member, Team 관계를 나타내는 다:1(N:1)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
이런식으로 단방향 매핑을 한 뒤에 member를 find 할 경우 매핑된 Team의 정보까지 같이 join되어 불러온다.
Member findMember = em.find(Member.class, 1L);
Team findTeam = findMember.getTeam();
String temaName = findMember.getTeam().getName();
만약에 현재 위에서 불러온 findMember의 team_id가 1일 경우 100인 team으로 바꾸고 싶을 경우 find로 객체를 불러와서 set만 해주게되면 DB에서는 team_id가 교체가된다.
Member findMember = em.find(Member.class, 1L);
Team newTeam = em.find(Team.class, 100L);
//DB에서는 Member 테이블에 있는 team_id가 1 -> 100으로 변경됨
findMember.setTeam(newTeam);
양방향 연관관계와 연관관계 주인
지금 상태에서는 Team에서 Member가 조회가 안된다.
근데 테이블 관계에서 둘은 외래키로 왔다갔다할 수 있다.
객체에서는 양방향으로 설정해줘야 하는데 Team에 List로 members를 넣어줘야한다.
이 부분이 객체와 테이블 연관관계의 가장 큰 차이점이다.
@Entity
public class Team {
중략..
@OneToMany(mappedBy = "team") //Member에서 사용하는 Team 객체 변수명
private List<Member> members = new ArrayList<>(); //new ArrayList는 nullPoint도 막아주고, 관례적으로 사용
}
이렇게하면 이제 Team이 갖고 있는 멤버들이 조회가 된다.
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
for(Member m : members){
sout("m = " + m.getUsername());
}
테이블의 양방향 관계는 외래 키 하나로 두 테이블의 연관관계를 관리한다.
select *
from member m
join team t on m.team_id = t.team_id
select *
from team t
join member m on m.team_id = t.team_id
그러나 객체의 양방향 관계는 사실 양방향이 아니라 서로 다른 단방향 관계 2개다. 그래서 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
A -> B (a.getB())
B -> A (b.getA())
단방향일 때는 큰 문제가 없는데 양방향일 때는 둘다 참조가 되기 때문에 Member가 Team을 바꾸고 싶을 때 Member에서 외래키를 바꿔야할지? Team에서 외래키를 바꿔야할지? 고민을 하게된다.
그렇기때문에 둘 중 하나로 외래 키를 관리해야 한다.
연관관계의 주인(Owner)
양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용X
- 주인이 아니면 mappedBy 속성으로 주인 지정
그래서 Team에 mappedBy가 지정된 members에서는 조회만 가능할 뿐 어떠한 수정이 되지않는다.
누구를 주인으로?
외래 키가 있는 곳을 주인으로 정해라.
그리고 DB관점에서 외래 키가 있는 곳이 N이다. 없는 곳이 1이고(N:1), @ManyToOne 설정하는 곳
여기서는 Member.team이 연관관계 주인이며 진짜 매핑이고, Team.members는 가짜 매핑(조회용)
아래와 같은 실수를 하지 말아야한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Memeber member = new Member();
member.setName("member1");
team.getMembers().add(member);
//이럴경우 member에 team_id는 null이 된다. 연관관계 주인으로 값을 설정해야함
em.persist(member);
그리고 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Memeber member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
//이때 위에서 추가된 memeber는 출력되지 않는다, 왜냐면 1차 캐시 데이터를 불러오기 때문에
//em.flush()나 em.clear()를 하고 find하기전까진 미노출이다.
Team findTeam = em.find(Team.class, team.getId());
//그렇기에 양쪽 다 데이터를 넣어주는게 좋다.
team.getMembers().add(member);
연관관계 편의 메소드를 생성하자.
@Entity
public class Member {
중략..
public void setTeam(Team team){ //chageTeam 같은 메소드로 변경하자 setter는 지양
this.team = team;
team.getMembers().add(this);
}
}
양방향 매핑시에 무한 루프를 조심하자.
예) toString(), lombok, JSON 생성 라이브러리
@Entity
pulbic class Member {
중략..
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
@Override
public String toString(){
//간소화 리턴 값
return "team=" + team;
}
}
@Entity
pulbic class Team {
중략..
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
@Override
public String toString(){
//간소화 리턴 값
return "members=" + members;
}
}
이런식으로 서로가 서로를 계속 호출하게되어 무한루프에 빠져 StackOverflowError가 발생한다.
출처 : https://www.inflearn.com/course/ORM-JPA-Basic
'Back-end > JPA' 카테고리의 다른 글
자바 ORM 표준 JPA fetch join (0) | 2023.05.29 |
---|---|
JPA @MappedSuperclass (0) | 2023.05.28 |
자바 ORM 표준 JPA DDL, 기본키 매핑 (0) | 2023.05.25 |
자바 ORM 표준 JPA 플러시, 준영속 (0) | 2023.05.24 |
자바 ORM 표준 JPA 영속성 컨텍스트 (0) | 2023.05.23 |