본문 바로가기

JPA

JPA 연관관계 매핑 1. 단방향 양방향 연관관계 이해하기

단방향과 양방향 연관관계

연관관계 매핑 기초용어

  • 방향 : 단방향, 양방향
  • 다중성: 일대일, 일대다, 다대다, 다대일
  • 연관관계 주인: 객체 양방향 연관관계는 주인이 필요하다

단방향 매핑

  • JPA에서 단방향 매핑은 JoinColumn과 One(Many) ToOne(Many)를 통해 할 수 있다.
  • Member 클래스 필드의 Team을 보면 Team과 일대일(OneToOne)으로 매핑을 한 것이다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String teamName;

}

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;


    private String name;

    @OneToOne
    @JoinColumn(name = "team_id")
    private Team team;

}

 

생성 내역

  • 위와같이 엔티티를 작성하고 애플리케이션을 구동시키면 아래와 같이 테이블 생성 내역을 볼 수 있는데 DB에서도 관계가 맺어진 것을 알 수 있다.
Hibernate: 

create table Member (
   member_id bigint generated by default as identity,
    name varchar(255),
    team_id bigint,
    primary key (member_id)
)
Hibernate: 

create table Team (
   team_id bigint not null,
    teamName varchar(255),
    primary key (team_id)
)
Hibernate: 

alter table Member 
   add constraint FK5nt1mnqvskefwe0nj9yjm4eav 
   foreign key (team_id) 
   references Team

 

 


 

양방향 매핑

  • 양방향 매핑은 Team Class에 Member를 추가하고 mappedBy를 통해 맺을 수 있다.
  • 사실 연관관계는 단방향 매핑으로 다 맺어진것이다.
  • 단지 객체 그래프 탐색을 위해 설정을 하는 것이다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String teamName;

    @OneToOne(mappedBy = "team")
    private Member member;

}

 

이렇게 하더라도 테이블이 생성되는 것을 확인해보면 차이는 없다. 테이블에서는 양방향으로 연관관계를 맺기 때문이다.

 

객체와 테이블과의 관계 차이

  • 현재 관계를 맺을 것을 보면 Member, Team에서 각각 Team, Member를 가지고 있다.
  • 하지만 테이블에서는 둘 중 하나에서 상대편 id를 외래 키로 가지고 있다.
  • 객체에서는 양방향 매핑이라고 하기보단 서로서로 단방향으로 관계를 맺는다.
  • 즉 양방향 매핑을 위해서는 서로서로 단방향 연관관계를 만들면 되는 것이다.

 

왜래키와 연관관계 주인

  • 외래 키를 가지는 쪽이 연관관계 주인이 되게 관계를 맺는 것이 좋다.
  • 보통 일대다 관계에서는 다가 외래 키를 가지므로 다인 객체가 연관관계의 주인이 되는 게 좋다.
  • 현재와 같이 일대일일 경우는 편한 곳을 연관관계 주인으로 지정하면 된다.
  • 연관관계의 주인 쪽에서 @JoinColumn을 통해 관계를 맺고 반대쪽은 mappedBy 속성을 사용하여 관계를 맺으면 된다.

 

// Member Class
@OneToOne
@JoinColumn(name = "team_id")
private Team team;

// Team Class
@OneToOne(mappedBy = "team")
private Member member;

 

현재는 Member Class에서 Team을 JoinColumn으로 관계를 맺었으니 연관관계의 주인은 Member이고 테이블 생성 내역을 보면 Member Table에 Team Id가 생겨난 것을 알 수 있다.

 

매핑 시 주의점

  • 실제 데이터를 넣을 때 아래와 같이 연관관계의 주인인 member에만 team을 세팅하고 commit을 하면 DB에 문제없이 데이터가 잘 들어간다.
  • 하지만 team에서 Member를 가져오면 null이 될 것이다.
Team team = new Team();
team.setTeamName("ATeam");
em.persist(team);  
// team에는 member가 존재하지않는다.


Member member = new Member();
member.setName("Dexter");
member.setTeam(team);
em.persist(member);

// team.getMember() = null;

tx.commit();

 

 

Hibernate: 
  /* insert blogJpa.Team
      */ 
insert 
into
    Team
    (teamName, team_id) 
values
    (?, ?)
Hibernate: 
  /* insert blogJpa.Member
      */ 
insert 
into
    Member
    (member_id, name, team_id) 
values
    (null, ?, ?)

 

해결법

  • 아래와 같이 member class에 연관관계 편의 메서드를 작성하여 양쪽 다 값을 추가 해주자
public void setTeam(Team team) {
    team.setMember(this);
    this.team = team;
}

 

 

참고 사항

lombok이 생성해주는 toString이나 JSON으로 값을 보낼 때 무한 루프에 빠질 수 있다.

 

왜냐하면 서로가 서로를 참조하고 있기 때문이다.

 

그러므로 toString은 따로 설정하고 JSON은 @JsonIgnore를 사용하자

 

정리

단방향 매핑만으로도 이미 연관관계는 맺어진다.

 

양방향 매핑은 반대 반향으로 조회(객체 그래프 탐색) 기능이 추가된 거뿐이다.

 

연관관계의 주인은 가능한 한 외래 키 기준으로 정하자

 


참고도서