이번에는 해당 게시물의 데이터를 출력할때 해당 게시물에 작성된 리뷰들을 모두 출력할 수 있도록 해보겠다.
이번 실습에 필요한 파일
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
public class Review {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 리뷰 id
private String title; // 리뷰 제목
private String content; // 리뷰 내용
private String userName; // 리뷰 작성자
@ManyToOne // 리뷰입장에서는 리뷰가 여러개고 병원은 한개이므로
@JoinColumn(name = "hospital_id") // hospital_id의 행을 Hospital DB와 연결함
private Hospital hospital; // 즉, hospital_id가 FK가 됨
}
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
public class Hospital {
@Id
private Long id;
private String roadNameAddress;
private String hospitalName;
/*
JPA에서는 데이터를 조회할 때 즉시 로딩(EAGER)과 지연 로딩(LAZY) 두 가지 방식이 있다.
이 두 가지 방식을 간단하게 설명하면 즉시 로딩은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이고,
지연 로딩은 필요한 시점에 연관된 데이터를 불러오는 것이라고 할 수 있다.
가급적이면 기본적으로 지연 로딩을 사용하는 것이 좋다.
*/
@OneToMany(mappedBy = "hospital", fetch = FetchType.LAZY)
// 병원은 1개이고 리뷰는 여러개 달리므로 One = 병원, Many = 리뷰들
// mappedBy를 통해 Review에서 연관맵핑으로 선언한 hospital 참조변수와 연동한다고 설정을 해준다.
private List<Review> reviews;
}
@AllArgsConstructor
@Getter
@Builder
@NoArgsConstructor
public class ReviewReadResponse {
private Long id;
private String title;
private String content;
private String patientName;
private String hospitalName;
// Entity에서 값을 받아와 원하는 DTO 구조로 변환
// 이때 hospitalName은 Hospital DB에서 가져와야 하므로 관계형 DB를 통해 FK가 가르키는 DB인 Hospital에서 데이터를 가져온다.
public static ReviewReadResponse fromEntity(Review review) {
ReviewReadResponse response = ReviewReadResponse.builder()
.id(review.getId())
.title(review.getTitle())
.content(review.getContent())
.patientName(review.getUserName())
.hospitalName(review.getHospital().getHospitalName())
.build();
return response;
}
}
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
// 병원 정보 + 리뷰 내용
public class HospitalResponseWithReview {
private Long id;
private String roadNameAddress;
private String hospitalName;
// 해당 병원에 대한 리뷰들의 정보
private List<ReviewReadResponse> reviews;
// Entity에서 값을 가져와 원하는 DTO 구조로 변환
public static HospitalResponseWithReview fromEntity(Hospital hospital){
return HospitalResponseWithReview.builder()
.id(hospital.getId())
.hospitalName(hospital.getHospitalName())
.roadNameAddress(hospital.getRoadNameAddress())
// reviews는 이미 hospital와 연결이 되어 있는 Review의 데이터를 가져와 맵핑한다
.reviews(hospital.getReviews().stream()
.map(review -> ReviewReadResponse.fromEntity(review)).collect(Collectors.toList())) // review를 ReviewReadResponse로
.build();
}
}
현재 DTO에는 병원정보 + 해당병원 리뷰정보가 들어있어야 한다.
따라서 병원정보(id, roadNameAddress, hospitalName)과 리뷰정보(reviews)를 선언해주고 병원 정보는 Hospital Entity의 해당 값을 가져와 대입해준다.
reviews의 경우에는 현재 Hospital Entity가 Review와 Join관계가 되어 있고 Hospital Entity의 reviews에는 해당 게시물에 대한 리뷰들이 저장이 되어 있다. 따라서 hospital.getReviews( )를 하게 되면 해당 게시물에 대한 리뷰들의 데이터를 모두 대입할 수 있다.
하지만 현재는 Review의 형식으로 저장이 되어 있고 내가 원하는 방식은 ReviewReadResponse 형태로 반환하고 싶은것이기 때문에 형태를 아래의 방식대로 변경해줘야 한다.
getReviews().stream()
.map(review -> ReviewReadResponse.fromEntity(review)).collect(Collectors.toList()))
getReviews() : 현재 Review Entity 형태(Review review)의 리뷰 List
.stream().map() : List형태로 저장되어 있는 데이터들을 모두 mapping 한다는 의미
(review -> ReviewReadResponse.fromEntity(review)) :
review 객체를 ReviewReadResponse DTO의 fromEntity 메서드에 넣어 ReviewReadResponse 형태로 변경하여 반환함
@Repository
public interface HospitalRepository extends JpaRepository<Hospital,Long> {
}
@Service
@RequiredArgsConstructor
public class HospitalService {
private final HospitalRepository hospitalRepository;
private final ReviewService reviewService;
// 병원정보에 대한 데이터를 가져옴
public Hospital findById(Long id){
Hospital hospital = hospitalRepository.findById(id)
.orElseThrow(()-> new IllegalArgumentException("id가 없습니다.")); // 값이 null일때 오류 출력
return hospital;
}
}
@RestController
@RequestMapping("/api/v1/hospital")
@RequiredArgsConstructor
public class HospitalController {
private final ReviewService reviewService;
private final HospitalService hospitalService;
// 병원 정보와 해당하는 리뷰 전체 출력
@GetMapping("/{id}")
public ResponseEntity<HospitalResponseWithReview> notice(@PathVariable long id){
Hospital hospital = hospitalService.findById(id); // 현재 게시물의 데이터를 가져옴
return ResponseEntity.ok().body(HospitalResponseWithReview.fromEntity(hospital));
// HospitalResponseWithReview의 fromEntity builder를 통해 DTO구조의 값을 가져옴
}
}
현재 hospital_id가 2번 게시물을 입력하면 2번에 해당하는 병원 정보가 출력이 되고 해당 리뷰의 정보들이 출력이 된다.