반응형

1:N 관계 예제

  • 상황 : Team에는 여러 Player가 속할 수 있지만, 한 Player는 여러 팀에 속할 수 없는 1:N 관계
    • Team 객체에는 id와 name 정보가 들어가고, Player 객체에는 id, name, age 정보가 들어감
    • 각각의 id가 primary key(기본키)로 지정
  • 연관관계 매핑
    • 하나의 엔티티에 다른 엔티티의 primary key를 foreign key(외래키)로 가져와야 함
    • 이런 상황에서는 보통 1:N 관계에서 N쪽으로 foreign key를 가져오고, 연관관계의 주인으로 지정함
    • 이 예제에서는 team이 1, player가 N에 해당하므로 player가 연관관계 주인이 되고, team_id를 가져옴
    • 이 상황을 JPA를 사용해 객체 생성 및 DB 연결 해보는 예제 (양방향 매핑)

구현 코드

Team Entity

List<Player> players에는 team에 속한 player들을 arraylist형태로 넣어줌

  • Team이 1이므로 @OneToMany 어노테이션 사용
  • mappedBy는 연관관계 주인이 아닌쪽에 써줌 => Player.team에 Team 객체가 들어가기 때문에 mappedBy = "team"
@Entity
@Data
public class Team {

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

    private String name;

    // 연관관계 매핑
    @OneToMany(mappedBy = "team")
    private List<Player> players = new ArrayList<>();
}

Player Entity

  • Player가 N에 해당하므로 @ManyToOne 사용
  • FetchType.LAZY 적용

FetchType

  • FetchType에는 지연로딩(LAZY)와 즉시로딩(EAGER)가 있음
    • EAGER은 Player을 조회할 때 Team에 대한 정보를 즉시 가져옴
    • LAZY는 Player의 Team이 필요할 때 정보를 가져옴
    • Player의 수가 많아질수록 LAZY를 써야 효율적임
    • @OneToMany, @ManyToMany는 default가 LAZY방식이지만, @OneToOne, @ManyToOne은 default가 EAGER이기 때문에 따로 지정해 줘야 함
@Entity
@Data
public class Player {

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

    private String name;
    private Integer age;

    // 연관관계 매핑
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

Team 추가

  • Team을 추가할 때는 Player에 관한 정보는 필요 없음
@PostMapping("/team")
public String addTeam(@RequestBody String teamName) {
    Team newTeam = new Team();
    newTeam.setName(teamName);
    teamRepository.save(newTeam);
    return "Team 추가 성공";
}

Team 추가 결과

Player 추가

  • Player을 추가할 때는 teamId 필요
  • 입력받은 teamId를 통해 해당 team을 찾아오고, Player에 찾아온 Team을 넣어주는 방식
  • name, age, teamId를 입력받기 위해 PlayerAddRequest라는 DTO 사용
@PostMapping("/player")
public String addPlayer(@RequestBody PlayerAddRequest request) {
    Player player = new Player();
    player.setName(request.getName());
    player.setAge(request.getAge());
    player.setTeam(teamRepository.findById(request.getTeamId()).get());
    playerRepository.save(player);
    return "Player 추가 성공";
}
// PlayerAddRequset.class
@Getter
@AllArgsConstructor
public class PlayerAddRequest {
    private String name;
    private Integer age;
    private Long teamId;
}

Player 추가 결과

  • Player가 연관관계 주인이기 때문에 team_id라는 column에 Team의 id가 들어간 것을 확인할 수 있음

Player 조회

  • Player을 조회할 때에도 DTO를 사용해야 함
    • Player의 Team을 가져올 때 발생하는 순환 참조 문제 해결을 위해 DTO 사용
  • 입력받은 playerId로 Player을 찾아오고 PlayerDto로 변환해 출력
  • 변환하는 과정에서 Team을 불러올 때 player.getTeam()을 통해 Player의 Team을 쉽게 가져올 수 있음
@GetMapping("/player/{playerId}")
public String showPlayer(@PathVariable Long playerId) {
    Player player = playerRepository.findById(playerId).get();
    PlayerDto playerDto = PlayerDto.of(player);
    return playerDto.toString();
}
// PlayerDto.class
@AllArgsConstructor
@Builder
@ToString
public class PlayerDto {
    private Long id;
    private String name;
    private Integer age;
    private String teamName;

    // Player -> PlayerDto로 변환하는 함수
    public static PlayerDto of(Player player) {
        return PlayerDto.builder()
                .id(player.getId())
                .name(player.getName())
                .age(player.getAge())
                .teamName(player.getTeam().getName())
                .build();
    }
}

Player 조회 결과

Team 조회

  • Team 조회시에도 Player와 마찬가지로 DTO 사용
  • Team의 Player에 접근 시에도 team.getPlayers()로 접근할 수 있음
  • players는 List 타입이기 때문에 stream을 사용하여 각각의 player에 접근하고 player의 name만 추출해 playersName에 넣어줌
@GetMapping("/team/{teamId}")
public String showTeam(@PathVariable Long teamId) {
    Team team = teamRepository.findById(teamId).get();
    TeamDto teamDto = TeamDto.of(team);
    return teamDto.toString();
}
// TeamDto.class
@AllArgsConstructor
@Builder
@ToString
public class TeamDto {
    private Long id;
    private String name;
    private List<String> playersName;

    public static TeamDto of(Team team) {
        return TeamDto.builder()
                .id(team.getId())
                .name(team.getName())
                .playersName(team.getPlayers().stream().map(list -> {
                    return list.getName();
                }).collect(Collectors.toList()))
                .build();
    }
}

Team 조회 결과

반응형

↓ 클릭시 이동

복사했습니다!