단방향과 양방향 연관관계
연관관계 매핑 기초용어
- 방향 : 단방향, 양방향
- 다중성: 일대일, 일대다, 다대다, 다대일
- 연관관계 주인: 객체 양방향 연관관계는 주인이 필요하다
단방향 매핑
- 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를 사용하자
정리
단방향 매핑만으로도 이미 연관관계는 맺어진다.
양방향 매핑은 반대 반향으로 조회(객체 그래프 탐색) 기능이 추가된 거뿐이다.
연관관계의 주인은 가능한 한 외래 키 기준으로 정하자
'JPA' 카테고리의 다른 글
JPA 연관관계 매핑 4. 프록시 (0) | 2020.01.23 |
---|---|
JPA 연관관계 매핑 3. 상속관계 매핑 (0) | 2020.01.22 |
JPA 연관관계 매핑 2. 일대다, 다대일, 일대일, 다대다 연관관계 매핑 (0) | 2020.01.22 |
JPA 엔티티 매핑: DB 스키마 자동 생성, 필드와 칼럼 매핑, 기본키 매핑 (0) | 2020.01.06 |
JPA와 JPA 영속성 컨텍스트, 플러시와 준영속 상태 (0) | 2020.01.06 |