[JPA/김영한] 연관관계를 매핑해보자
이 글은 김영한님의 JPA 강의 중 5장을 듣고 정리한 내용입니다 :)
강의 : 자바 ORM 표준 JPA 프로그래밍 - 기본편
교재 : 자바 ORM 표준 JPA 프로그래밍🤷♀️
- 객체 지향 책 추천 - 사실 Jpa보다, 객체지향적인 설계가 어려움
객체지향의 사실과 오해
오브젝트
❗ 객체를 테이블에 맞춰 모델링 하게 되면 어떤 문제가 생길까?
단방향 연관관계
IF) 회원과 팀은 다대일관계를 가짐! 이때, 객체를 관계형 DB에 맞춰 모델링하면
- 객체지향스럽지 않게 엔티티에 외래키값용 필드를 만듦
- 이렇게 하면, 객체에서 조회했을 때 member랑 team의 연관관계가 없음!
IF) 회원과 팀은 다대일관계를 가짐! 이때, 객체를 관계형 DB에 맞춰 모델링하면
테이블은 외래 키로 조인해서 찾는 반면, 객체는 참조로 연관 객체를 찾음
⇒ 패러다임의 차이!
(SQL : select * from member a join a team t on a.team_id = t.team_id; )
// 맴버 엔티티
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "TEAM_ID")
**private Long teamId;** // 객체지향 스럽지않음! 관계형DB에 맞춘 방식
****...
}
// 팀 엔티티
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
…
}
// ------------ 연관 관계 매핑 ------------
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId()); //이건 객제 지향적이지 않음!!
em.persist(member);
// ------------ 조회 ------------
//조회
Member findMember = em.find(Member.class, member.getId());
//member랑 team의 연관관계가 없음!
Team findTeam = em.find(Team.class, team.getId());
👍 그럼 객체지향 모델링을 하자!
// 맴버 엔티티
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
//@Column(name = "TEAM_ID")
//private Long teamId; // 객체지향스럽지않음! 관계형DB에 맞춘 방식
@ManyToOne
@JoinColumn(name = "TEAM_ID")
**private Team team;** // 객체 지향스러운 방법!
****...
}
// -------------조회-------------
Member member = em.find(Member.class, member.getId);
Team team = member.getTeam();
// -------------수정-------------
Team newTeam = member.getTeam();
member.setTeam(newTeam); // 팀 수정!
-
1차 캐시에 있어서 쿼리 날라가는 게 안 보일때? 근데 쿼리를 보고 싶다면!
em.persist(member); // 영속성 컨텍스트에 있음 Team findTeam = em.findMember.getTeam(); //쿼리 안나가고 1차 캐시에서 가져옴 em.flush(); // 그냥 빨리 1차 캐시에 있는 거 강제로 디비에 보내기 em.clear(); // 영속성 컨텍스트(1차캐시) 클리어하면 쿼리는 이제 새롭게 나감!
💫 양방향 연관관계일 때는 주인을 따지자!
양방향 연관관계란?
: 양쪽으로 참조해서 서로 조회할 수 있는 관계
- 테이블의 경우, 원래 방향의 개념이 없음! FK로 받으면 양쪽에서 다 조회가능
- 객체의 경우, 양쪽에 서로의 객체를 넣어주어야 조회가능!
- 이것 또한 테이블과 객체의 패러다임 차이!
다대일 관계(Member)에는 @ManyToOne만! (단방향과 동일한 방법)
일대다관계(Team)에는 @OneToMany
그리고 컬렉션 추가해주고, mapped by에는 해당 다대일이 매핑되어있는 변수명 넣어주기
//멤버 엔티티
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne // 다대일관계 매핑(단방향 연관관계와 동일한 방법)
@JoinColumn(name = "TEAM_ID")
private Team team;
…
}
//팀 엔티티
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // 일대다관계 매핑
List<Member> members = new ArrayList<Member>();
//(컬렉션 추가해주기, mapped by에는 해당 다대일이 매핑되어있는 변수명!)
…
}
// 한 멤버가 속한 팀의 전체 멤버 리스트도 받아올 수 있음
List<Memeber> members = member.getTeam().getMembers();
mapped by란?
- mapped by 를 제대로 이해하려면 객체-테이블의 연관관계 패러다임 차이를 알아야 한다!
- 객체의 양방향 관계는 사실 서로 다른 단방향 2개를 양방향이라고 부르는 것뿐임
IF ) 멤버가 속한 팀을 바꾸고 싶다면,
팀에서 멤버를 바꿔야될까, 아니면 멤버에서 팀을 바꿔야 될까?
⇒ DB 입장에서는 멤버에서 팀아이디인 FK값만 바꾸면 된다!
⇒ 그러면 객체 입장에서는 ??
2개의 단방향 관계로 억지로 만든 양방향관계인데, 무슨 값을 바꿔야 하나?!
⇒ 그래서 양방향 연관관계의 주인을 정해서, 주인이 FK를 관리하게끔 해보자!
그리고 주인이 아닌 쪽(mapped by로 주인을 걸어줌)은 읽기만 가능하게 하자!
- 주인이 아닌 쪽에는 @OneToMany (mapped by = 주인이름), 읽기(조회)만 가능함
- 주인인 쪽에는 @ManyToOne, 업데이트나 등록 등등은 주인만 참조
그렇다면 누구를 주인으로 할까?
주인은 외래키가 있는 곳으로 정하자 즉, '다'쪽(Member)을 주인으로 하자
반대로 하면 헷갈리고 성능 이슈가 있음!, 쿼리가 반대쪽으로 나가면 헷갈리니까
양방향 매핑할 때 주의하기!
: 주인이 아닌쪽에만 매핑하면 매핑이 안됨!
: 객체지향적인 관점에서는 양방향에서 다 매핑해주어야 안전
(단, 처음에는 주인쪽에서만 매핑 후 필요시 양방향 매핑 → 아래 설명)
실습 ) 연관관계 매핑은 주인인 쪽에서는 꼭 해주어야하고, 만약 역방향에서만 하면 설정 안됨
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team); // 이렇게 주인인 쪽에서 연관관계를 설정해주어야함!
// 역방향(주인이 아닌 방향)만 연관관계 설정 => 이것만 하면 설정안됨
team.getMembers().add(member);
em.persist(member);
- 편의상 메소드로 만들기
: 여기서는 Member 엔티티쪽에서 둘다 설정하는 걸로 했는 데, Team쪽에서 메소드 만들어도 OK (한쪽에서만 할것! → 이건 로직마다 편한쪽에서)
//멤버 엔티티 ('다'쪽, 주인!)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
…
}
//연관관계 편의 메소드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
- 양방향 매핑 시 무한 루프 주의
- toString(), lombok : 양쪽에서 toString 함수 만들면 계속 호출해서 무한루프 오류 발생!
⇒ 쓰지 말것 권장 - JSON 생성 라이브러리 : 컨트롤러에서 (리턴값으로)엔티티를 JSON으로 변환할 때 무한루프 오류 발생!
⇒ 컨트롤러에서 (양방향 매핑되어있는) 엔티티를 반환하지 말 것! → DTO로 변환해서 반환할 것! (엔티티가 바뀌면 API 스펙이 바뀌니까!)
정리 : 양방향 설계할 때는,
- 처음 설계할 때는 단방향 매핑으로 설계함!
- 필요할 때 양방향은 추가해주면 됨! - 테이블에 영향을 주지 않으니
(역방향에서 조회(주인X쪽에서 주인 조회)하려고 양방향으로 만든 것뿐!)
(역방향에서 조회(주인X쪽에서 주인 조회)하려고 양방향으로 만든 것뿐!)
⇒ 가능하면 단방향으로 하되, 실무에서 양쪽으로 조회가 필요한 경우에 양방향 매핑을 하자!
(양방향은 복잡하니까 필요시에 매핑하자)
Author And Source
이 문제에 관하여([JPA/김영한] 연관관계를 매핑해보자), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sooyoungh/연관관계를-매핑해보자저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)