반응형

N:M 관계 예제

  • 상황 : 저자는 여러 책을 쓸 수 있고 학원도 여러 수험생을 받을 수 있는 N:M (다대다) 관계
    • 관계형 DB는 N:M 관계를 표현할 수 없음 => 1:N, N:1 관계로 풀어내야 함
    • EXAMINEE와 ACADEMY 사이에 EXAMINEE_ACADEMY 테이블을 추가로 생성해 풀어냄
    • 이 상황에서는 EXAMINEE_ACADEMY가 두 관계에서 연관관계의 주인으로 지정
    • 이렇게 하면 또다른 장점은 추가적인 정보를 더 집어넣을 수 있음
    • 예를 들면 수험생이 학원을 등록한 날짜 등을 추가로 집어넣을 수 있음 => 예제에서 등록 날짜까지 구현

구현 코드

Examinee Entity

@Entity
@Data
public class Examinee {

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

    private String name;
    private int age;

    // 연관관계 매핑
    @OneToMany(mappedBy = "examinee")
    private List<ExamineeAcademy> examineeAcademies = new ArrayList<>();
}

Academy Entity

@Entity
@Data
public class Academy {

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

    private String name;

    // 연관관계 매핑
    @OneToMany(mappedBy = "academy")
    private List<ExamineeAcademy> examineeAcademies = new ArrayList<>();
}

ExamineeAcademy Entity

  • 다대다 관계를 풀어내기 위한 객체(테이블)
@Entity
@Data
public class ExamineeAcademy {

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

    private LocalDateTime registerDate;

    // 연관관계 매핑
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "examinee_id")
    private Examinee examinee;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "academy_id")
    private Academy academy;
}

Repository

  • JpaRepository를 상속받은 ExamineeRepository, AcademyRepository 뿐만 아니라 ExamineeAcademyRepository 생성

초기 데이터 생성

  • 수험생 (Tom, 18), (Alice, 17), (Harry, 16) 3명과 수학학원, 영어학원, 국어학원 생성

학원 등록 기능 구현

  • examineeId와 academyId를 입력 받아 학원 등록
    입력받을 때는 DTO 사용 (DTO 코드 생략)
  • 학원 등록 시 등록 시간으로 현재 시간 추가
@PostMapping("/register-academy")
public String registerAcademy(@RequestBody RegisterRequest request) {
    Examinee examinee = examineeRepository.findById(request.getExamineeId()).get();
    Academy academy = academyRepository.findById(request.getAcademyId()).get();

    ExamineeAcademy newRegister = new ExamineeAcademy();
    newRegister.setExaminee(examinee);
    newRegister.setAcademy(academy);
    newRegister.setRegisterDate(LocalDateTime.now());
    examineeAcademyRepository.save(newRegister);

    return String.format("%s 수험생이 %s에 등록하였습니다.", examinee.getName(), academy.getName());
}

학원 등록 기능 구현 결과

학원 등록 조회 기능 구현

  • Tom => 수학학원, 영어학원 등록
  • Alice => 수학학원, 영어학원, 국어학원 등록
  • Harry => 수학학원 등록
  • 위 상황에서 모든 수험생들의 등록 기록을 출력하는 기능
  • 출력할때는 DTO 사용 => 모든 examineeAcademy를 불러오고 이를 모두 순회하면서 examineeId에 해당하는 수험생의 이름과 academyId에 해당하는 학원의 이름으로 변환 후 출력
@GetMapping("/register-academy")
public String showRegisterAcademy() {
    List<ExamineeAcademy> examineeAcademies = examineeAcademyRepository.findAll();

    List<ExamineeAcademyDto> dto = new ArrayList<>();
    for(ExamineeAcademy examineeAcademy : examineeAcademies) {
        dto.add(ExamineeAcademyDto.of(examineeAcademy));
    }

    return dto.toString();
}
// ExamineeAcademyDto
@AllArgsConstructor
@Builder
@ToString
public class ExamineeAcademyDto {
    private String examineeName;
    private String academyName;
    private LocalDateTime registerDate;

    public static ExamineeAcademyDto of(ExamineeAcademy examineeAcademy) {
        return ExamineeAcademyDto.builder()
                .examineeName(examineeAcademy.getExaminee().getName())
                .academyName(examineeAcademy.getAcademy().getName())
                .registerDate(examineeAcademy.getRegisterDate())
                .build();
    }
}

학원 등록 조회 기능 구현 결과

수험생 조회 기능 구현

  • 수험생을 조회하면 이름, 나이 뿐만 아니라 등록한 학원의 이름들을 출력
  • 이 때에도 DTO를 사용
  • DTO에서 수험생이 등록학 학원 리스트를 조회하며 각각의 학원 이름만 추출하는 기능 구현
@GetMapping("/examinee/{examineeId}")
public String showExaminee(@PathVariable Long examineeId) {
    Examinee examinee = examineeRepository.findById(examineeId).get();
    return ExamineeDto.of(examinee).toString();
}
// ExamineeDto
@AllArgsConstructor
@Builder
@ToString
public class ExamineeDto {
    private Long id;
    private String name;
    private int age;
    private List<String> academyNames;

    public static ExamineeDto of(Examinee examinee) {
        return ExamineeDto.builder()
                .id(examinee.getId())
                .name(examinee.getName())
                .age(examinee.getAge())
                .academyNames(examinee.getExamineeAcademies().stream().map(list -> {
                    return list.getAcademy().getName();
                }).collect(Collectors.toList()))
                .build();
    }
}

수험생 조회 기능 구현 결과

반응형

↓ 클릭시 이동

복사했습니다!