[JPA/김영한] 연관관계를 매핑해보자

21502 단어 JPAJPA

이 글은 김영한님의 JPA 강의 중 5장을 듣고 정리한 내용입니다 :)
강의 : 자바 ORM 표준 JPA 프로그래밍 - 기본편
교재 : 자바 ORM 표준 JPA 프로그래밍🤷‍♀️

  • 객체 지향 책 추천 - 사실 Jpa보다, 객체지향적인 설계가 어려움
    객체지향의 사실과 오해
    오브젝트

❗ 객체를 테이블에 맞춰 모델링 하게 되면 어떤 문제가 생길까?

단방향 연관관계

IF) 회원과 팀은 다대일관계를 가짐! 이때, 객체를 관계형 DB에 맞춰 모델링하면

  1. 객체지향스럽지 않게 엔티티에 외래키값용 필드를 만듦
  2. 이렇게 하면, 객체에서 조회했을 때 member랑 team의 연관관계가 없음!

테이블은 외래 키로 조인해서 찾는 반면, 객체는 참조로 연관 객체를 찾음
⇒ 패러다임의 차이!

(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); 
}
  • 양방향 매핑 시 무한 루프 주의
  1. toString(), lombok : 양쪽에서 toString 함수 만들면 계속 호출해서 무한루프 오류 발생!
    ⇒ 쓰지 말것 권장
  2. JSON 생성 라이브러리 : 컨트롤러에서 (리턴값으로)엔티티를 JSON으로 변환할 때 무한루프 오류 발생!
    ⇒ 컨트롤러에서 (양방향 매핑되어있는) 엔티티를 반환하지 말 것! → DTO로 변환해서 반환할 것! (엔티티가 바뀌면 API 스펙이 바뀌니까!)

정리 : 양방향 설계할 때는,

  1. 처음 설계할 때는 단방향 매핑으로 설계함!
  2. 필요할 때 양방향은 추가해주면 됨! - 테이블에 영향을 주지 않으니
    (역방향에서 조회(주인X쪽에서 주인 조회)하려고 양방향으로 만든 것뿐!)

⇒ 가능하면 단방향으로 하되, 실무에서 양쪽으로 조회가 필요한 경우에 양방향 매핑을 하자!
(
양방향은 복잡하니까 필요시에 매핑하자)

좋은 웹페이지 즐겨찾기