[JPA] 연관관계 매핑 기초

gyeol·2024년 7월 4일

JPA

목록 보기
4/13
post-thumbnail

김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'를 듣고 적은 글 입니다.

연관관계가 없는 객체


Team의 식별자를 Member에서 직접적으로 다루게 된다. 이는 객체 지향적인 방법이 아니다.

객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력관계를 만들 수 없다.
테이블은 외래키로 조인을 사용해 연관된 테이블을 찾고 객체는 참조를 사용해 연관된 객체를 찾기에 테이블 관계에 맞춰 설계를 하게되면 이런 간극을 극복할 수 없다.

단방향 연관관계


객체의 참조와 테이블의 외래키를 매핑한다.
이때 Member에서 Team 방향으로만 가능하며 Team에서 Member로 접근할 수 있는 방법은 없다.

//Member class
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private Long id;

@Column(name = "USERNAME")
private String username;

@ManyToOne //멤버에선 여러명이고 팀은 하나
@JoinColumn(name = "TEAM_ID")
private Team team;


Member에서 Team을 지정할 수 있다. 이 관계를 단방향 연관관계라고 한다.

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

이때 참조를 사용해서 연관관계를 조회할 수 있다. Member 클래스에서 Team의 참조값을 저장하고 있기에 이를 통해 멤버가 소속된 팀도 조회할 수 있다.

//조회
Member findMember = em.find(Member.class, member.getId()); 
//영속성 컨텍스트 1차 캐시에서 값 꺼내오기에 쿼리가 나가지 않음
 
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();

연관관계가 없었을 때에는 멤버 따로, 팀 따로 조회했어야 했다.

//조회
Member findMember = em.find(Member.class, member.getId()); 
//연관관계가 없음
Team findTeam = em.find(Team.class, team.getId());

이런 방법은 객체지향적인 방법이 아니다!!

양방향 연관관계


양방향 연관관계는 말 그대로 Member에서 Team클래스에 접근할 수 있고 Team 클래스에서도 Member 클래스에 접근할 수 있음을 뜻한다. 위와 다른 점이 있다면 Team에서 Member에 접근하기 위해 List<Member> member을 추가해줬다.

//Team class
@Id @GeneratedValue
private Long id;
private String name;

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

위와 같은 코드를 추가해준다.

조회할 때에도 양방향 연관관계를 가지기에 Team에서 Member클래스에 접근해 역방향 조회가 가능하다.

//조회
Team findTeam = em.find(Team.class, team.getId()); 
int memberSize = findTeam.getMembers().size(); //역방향 조회

객체와 테이블이 관계를 맺는 차이

객체의 연관관계는 2개이다

  • Member -> Team (단방향)
  • Team -> Member (단방향)
    즉 양방향 연관관계는 단방향 연관관계 2개를 합쳐놓은 것이다.

테이블의 연관관계는 1개이다.

  • MEMBER <-> TEAM (양방향)
    테이블의 경우 TEAM_ID로 JOIN을 하여 양방향으로 접근할 수 있다.

그래서 객체의 연관관계 두개 중 하나로 외래키를 관리해야 한다.

연관관계 주인

객체의 두 관계 중 하나를 연관관계의 주인으로 지정해야 하며 연관관계의 주인만이 외래키를 등록하고 수정할 수 있다. 주인이 아닌 쪽은 조회만 가능하다.
즉, 주인이라면 mappedBy 속성을 사용할 수 없다.

이때 그럼 주인을 어떻게 정하는가 ??

외래키를 가지는 곳을 주인으로 지정하고 그 외래키를 기본키로 가지는 클래스에 mappedBy를 사용해주면 된다.

여기에선 Member에서 Team을 외래키로 가지고 있기에 Member 클래스를 연관관계의 주인으로 지정해준다. Teammembers는 조회만 가능하고 값을 입력할 순 없다.

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

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

위의 코드를 보면 주인이 아닌 클래스에서 연관관계를 설정해주었기에 이때 TEAM_ID에는 NULL이 들어간다.
그렇기에 member.setTeam(team); 이와 같이 연관관계의 주인에 값을 입력해 NULL 값이 들어가지 않도록 한다.

정리

  • 양방향 매핑은 반대방향으로 조회 기능이 추가된 것 뿐이다.
  • 양방향 매핑 시 무한 루프를 조심해야한다. 자칫 StackOverflow error 가 발생할 수 있다.
  • 순수 객체 상태를 고려해 항상 양쪽에 값을 설정해주어야 한다.
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야한다.
profile
공부 기록 공간 '◡'

0개의 댓글