본문 바로가기
Jpa/series

[Jpa 2편] OneToMany, ManyToOne 정리

by 무대포 개발자 2022. 7. 7.
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

댓글