본문 바로가기
Backend, Server/JPA

[JPA] 연관관계 매핑 기초

by ggyongi 2021. 12. 14.
반응형

연관 관계 매핑에 앞서 객체와 테이블의 차이를 알아야 한다.

- 객체 연관관계 VS 테이블 연관관계


객체의 연관관계는 참조를 통해 이루어진다.
테이블의 연관관계는 외래키를 통해 이루어진다.

이때 객체의 연관관계는 단방향이고,
테이블의 연관관계는 양방향이 된다.

이러한 간극을 줄여주고자 연관관계 매핑이 필요한 것이다!

- 단방향

매핑은 아래와 같이 @ManyToOne 어노테이션, @JoinColumn을 설정해준다. (JoinColumn은 필수가 아니다.)

@Entity 
public class Member { 
@Id @GeneratedValue 
private Long id; 

@Column(name = "USERNAME") 
private String name; 
private int age; // 

@Column(name = "TEAM_ID") // 
private Long teamId; 

@ManyToOne 
@JoinColumn(name = "TEAM_ID") 
private Team team; …

그리고 아래와 같이 setTeam을 통해 member에게 team 지정을 해줄 수 있다.
이때 Jpa가 알아서 Team의 PK값을 꺼내서 INSERT 시에 이를 FK로 활용한다.

 //팀 저장 
 Team team = new Team(); 
 team.setName("TeamA"); 
 em.persist(team); 
 
 //회원 저장 
 Member member = new Member(); 
 member.setName("member1"); 
 member.setTeam(team); 
 
 //단방향 연관관계 설정, 참조 저장 
 em.persist(member);

 

- 양방향

양방향 매핑 규칙은 아래와 같다. 연관관계의 주인이라는 개념을 숙지해야 한다.
아래를 보면 알수 있듯이, 외래키를 가진 쪽이 주인이 되어야 한다.

• 객체의 두 관계중 하나를 연관관계의 주인으로 지정
• 연관관계의 주인만이 외래 키를 관리(등록, 수정)
• 주인이 아닌쪽은 읽기만 가능
• 주인은 mappedBy 속성 사용X
• 주인이 아니면 mappedBy 속성으로 주인 지정


양방향 매핑 시 Member 엔티티는 단방향과 동일하다.

@Entity 
public class Member { 
@Id @GeneratedValue 
Long id; 

Column(name = "USERNAME") 
private String name; 
private int age; 

@ManyToOne
@JoinColumn(name = "TEAM_ID") 
private Team team; …

Team 클래스에는 컬렉션 객체 members를 만들어준다. 그리고 어노테이션 @OneToMany를 붙여준다.
이때 속성으로 (mappedBy = "team")을 달아주어야 한다.

@Entity 
public class Team { 
@Id @GeneratedValue
private Long id; 
private String name; 

@OneToMany(mappedBy = "team") 
List<Member> members = new ArrayList<Member>(); … 
}

그러면 위와 같은 관계에서는 주인은 Member.team이 되고 주인의 반대편은 Team.members가 된다.

*주의해야 할 점 : 연관관계의 주인에 값을 입력해야 한다.
아래 코드처럼 주인이 아닌 객체에게 값을 입력해주면 제대로된 쿼리가 실행되지 않는다.

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

//역방향(주인이 아닌 방향)만 연관관계 설정 
team.getMembers().add(member); 
em.persist(member);


+) 순수 객체 상태를 고려해서 양쪽에 값을 설정해 주는 것이 좋다.
영속성 컨텍스트에서는 1차 캐시에서 객체를 가져오는 경우가 있기 때문에 양쪽에 값을 설정해주지 않을 경우
제대로 된 값을 반환받을 수 없을 수가 있다.
ex) 아래 코드에서 10번줄을 주석처리하면 출력되는 것은 아무것도 없음
왜냐면 em.find로 Team을 가져올때 1차캐시에서 가져오기때문에 그 안의 members에는 의도한 멤버가 들어있지 않음.
허지만 아래코드에서
em.flush(), em.clear() 주석처리를 없애주면
Team을 가져올때 db에서 가져오기 때문에 그때는 members안에 의도한 멤버가 들어있게 된다.

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);

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

Team findTeam = em.find(Team.class, team.getId()); // 1차 캐시 

List<Member> members = findTeam.getMembers();

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

 

- 알아두어야 할 점

• 단방향 매핑만으로도 이미 연관관계 매핑은 완료
• 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
• JPQL에서 역방향으로 탐색할 일이 많음
• 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)



------------------------------------------------------------------------------
*레퍼런스 : 인프런 김영한님 강의(자바 ORM 표준 JPA 프로그래밍)

 

비전공자 네카라 신입 취업 노하우

시행착오 끝에 얻어낸 취업 노하우가 모두 담긴 전자책!

kmong.com

댓글