[SpringBoot(3)] 해당 게시물에 대한 댓글(리뷰) 출력

배지원·2022년 12월 3일
0

실습

목록 보기
20/24

이번에는 해당 게시물의 데이터를 출력할때 해당 게시물에 작성된 리뷰들을 모두 출력할 수 있도록 해보겠다.

파일 구조

이번 실습에 필요한 파일

  • Entity : Review, Hospital
  • DTO : ReviewReadResponse, HospitalResponseWithReview
  • Service : HospitalService
  • Repository : HospitalRepository
  • Controller : HospitalController

병원 정보와 해당 병원 리뷰 출력

Domain

(1) Entity

Review

@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가 됨
}

Hospital

@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;
}

(2) DTO

ReviewReadResponse

@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;
    }
}
  • 리뷰를 내가 원하는 구조의 형태로 반환해주기 위해 DTO를 설정해줌
  • 기존에는 Service에서 build를 통해 값을 대입해주었지만 여러 곳에서 사용할 경우 코드 중복이 발생할 수 있으므로 DTO안에 build 메서드를 만들어서 호출해서 대입하는 방식으로 구성하였다.

HospitalResponseWithReview

@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

HospitalRepository

@Repository
public interface HospitalRepository extends JpaRepository<Hospital,Long> {
}

Service

HospitalService

@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;
    }
}
  • JPA에서는 데이터를 호출할때 null값의 유무를 판단해줘야 한다.
    따라서 이때는 Optional을 많이 사용하는데 자세한 정보는 정리해둔 Optional이란? 을 참고하길 바란다.

Controller

@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 Entity에는 Review와 Join이 되어 있기 때문에 병원 정보와 해당 병원의 리뷰가 저장되어 있다. 따라서 Hospital id값만 넘겨 해당 데이터만 찾아줘도 병원정보와 리뷰의 값을 받아와 DTO로 넘겨 원하는 형식으로 반환을 받을 수 있다.


결과

현재 hospital_id가 2번 게시물을 입력하면 2번에 해당하는 병원 정보가 출력이 되고 해당 리뷰의 정보들이 출력이 된다.

profile
Web Developer

0개의 댓글