728x90
반응형
목차는 jpa series 목차 에 있습니다.
source 는 Github 에 있습니다.
[Jpa 2편] OneToMany, ManyToOne 정리
단방향 OneToMany JoinColumn 없음
- 아래와 같이 JoinColumn 없이 OneToMany 단방향을 설정하면 team, team_member 테이블 간 mapping 테이블이 생성 됩니다. (team_list 테이블)
- 테이블이 새로 생성되고 해당 테이블에 insert 쿼리가 발생되기에 비효율적입니다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id @Column(name = "team_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String teamName;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
List<TeamMember> teamMembers = new ArrayList<>();
@Builder
public Team(Long id, String teamName, List<TeamMember> teamMembers) {
this.id = id;
this.teamName = teamName;
this.teamMembers = teamMembers;
}
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TeamMember {
@Id @Column(name = "member_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String name;
@Builder
public TeamMember(Long id, String name) {
this.id = id;
this.name = name;
}
}
insert into team (team_name) values (?)
insert into team_member (name) values (?)
insert into team_member (name) values (?)
insert into team_list (team_team_id, list_member_id) values (?, ?)
insert into team_list (team_team_id, list_member_id) values (?, ?)
@Test
public void 단방향_OneToMany_테스트() throws Exception {
// given
List<TeamMember> teamMembers = new ArrayList<>();
TeamMember teamMemer = TeamMember.builder()
.name("testMember1")
.build();
teamMembers.add(teamMemer);
TeamMember teamMemer2 = TeamMember.builder()
.name("testMember2")
.build();
teamMembers.add(teamMemer2);
Team team = Team.builder()
.teamName("testName")
.teamMembers(teamMembers)
.build();
// when
Team result = teamRepository.save(team);
// then
Assert.assertThat(result.getTeamMembers().size(), is(2));
}
단방향 OneToMany JoinColumn 존재
JoinColumn 을 설정해주면 맵핑테이블이 사라지고, 맵핑 테이블에 데이터를 넣는 쿼리가 빠집니다.
문제는 아래와 같이 테스트 했을 때, update 쿼리가 추가로 발생합니다.
아래와 같이 업데이트 쿼리가 발생하는 이유는 team_member 에서는 team 에 대한 정보가 없기에 전부 insert 한 후, team_member_id 로 데이터를 찾아서 team_id 를 넣어줍니다.
참고로 JoinColumn(name = "외래키 이름", referenceColumn = "조인할 컬럼") 입니다. referenceColumn 이 없을 경우, 조인하는 테이블의 PK 가 외래키가 됩니다.
public class Team {
@Id @Column(name = "team_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String teamName;
// 조인 컬럼 단방향 매핑
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "team_id")
List<TeamMember> teamMembers = new ArrayList<>();
}
@Test
public void 단방향_OneToMany_테스트() throws Exception {
// given
List<TeamMember> teamMembers = new ArrayList<>();
TeamMember teamMemer = TeamMember.builder()
.name("testMember1")
.build();
teamMembers.add(teamMemer);
TeamMember teamMemer2 = TeamMember.builder()
.name("testMember2")
.build();
teamMembers.add(teamMemer2);
Team team = Team.builder()
.teamName("testName")
.teamMembers(teamMembers)
.build();
// when
Team result = teamRepository.save(team);
// then
Assert.assertThat(result.getTeamMembers().size(), is(2));
}
insert into team (team_name) values (?)
insert into team_member (name) values (?)
insert into team_member (name) values (?)
update team_member set team_id=? where team_member_id=?
update team_member set team_id=? where team_member_id=?
양방향 OneToMany
- team, team_member 에 대한 insert 쿼리만 발생합니다.
- JoinColumn 이 설정돼있는게 연관관계의 주인이며, TeamMember 에 team_id 가 (외래키) 생성됩니다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
List<TeamMember> teamMembers = new ArrayList<>();
...
...
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
public class TeamMember {
@ManyToOne(optional = false)
@JoinColumn(name = "team_id")
private Team team;
...
...
}
@Test
@Transactional
public void 양방향_OneToMany_테스트() throws Exception {
// given
Team team = Team.builder()
.teamName("testName")
.build();
team.addTeamMember("teamMember11");
team.addTeamMember("teamMember22");
// when
Team result = teamRepository.save(team);
// then
Assert.assertThat(result.getTeamMembers().size(), is(2));
}
insert into team (team_name) values (?)
insert into team_member (name) values (?)
insert into team_member (name) values (?)
결론
- OneToMany 를 사용할 때, n+1 문제가 발생해서 성능에 악영향을 미칠 수 있습니다.
- 양방향보단 단방향을 쓰는게 좋다고 생각합니다.
- 양방향을 쓰는 순간 로직의 복잡도가 올라가고 어떤 문제가 생길지 모릅니다.
- 그렇기에 될 수 있으면 단방향을 쓰고 필요할 경우 양방향을 쓰는게 좋습니다.
- 단방향으로 쓰는게 좋으며, JoinColumn 을 써주면 좋습니다. (별도 맵핑 테이블이 안생기기에)
- 단방향으로 썼는데 불필요한 쿼리가 발생할 때는 양방향을 고려해보는 것이 좋습니다.
- 위와는 별개로 List 초기화를 해주는게 좋습니다. 데이터가 없어서 null 인지 의도적으로 null 로 선언한 것인지 구분 할 수가 없습니다. (Optional 사용 추천)
- 또한, Jpa OneToMany 와 같은 것을 사용할 때, 단위테스트를 해서 어떻게 동작하는지 파악하면 좋다고 생각합니다.
reference
'Jpa > series' 카테고리의 다른 글
[Jpa 4편] n+1 정리 (0) | 2022.07.07 |
---|---|
[jpa 3편] OneToOne 정리 (0) | 2022.06.16 |
[jpa 1편] jpa 개념 정리 (0) | 2022.06.16 |
댓글