API spec을 토대로 강의 부분을 먼저 작성하였다.
@RestController
public class LectureRestController {
private final LectureService lectureService;
public LectureRestController(LectureService lectureService) {
this.lectureService = lectureService;
}
//강의 목록 조회
@GetMapping("/lectures")
public List<LectureListResponse> readAll(){
return lectureService.findAll();
}
//강의 상세 조회
@GetMapping("/lectures/{lectureId}")
public LectureDetailResponse readById(@PathVariable Long lectureId){
return lectureService.findById(lectureId);
}
@PostMapping("/lectures")
public LectureResponse create(@RequestBody LectureCreateRequest lectureCreateRequest){
return lectureService.save(lectureCreateRequest);
}
@PutMapping("/lectures/{lectureId}")
public LectureResponse update(@PathVariable Long lectureId,
@RequestBody LectureUpdateRequest lectureUpdateRequest){
return lectureService.update(lectureId, lectureUpdateRequest);
}
@DeleteMapping("/lectures/{lectureId}")
public void delete(@PathVariable Long lectureId){
lectureService.delete(lectureId);
}
}
controller는 이제 api spec만 봐도 짤 수 있어야 한다.
다른 dto, repository는 안적고 고민들과 구현된 코드만 작성해보도록 하겠다.
public List<LectureListResponse> findAll() {
return lectureRepository.findAll()
.stream()
.map(lecture ->
new LectureListResponse(
lecture.getId(),
lecture.getTitle(),
lecture.getTeacher().getName(),
lecture.getPrice(),
lecture.getCategory(),
lecture.getCreateTime()))
.toList();
}
강의 목록들에는 전체가 보여야 하니까, findAll로 찾아준다. 그리고 dto를 토대로 return해줘야 하기 때문에 .map을 통해 바꿔준다.
💡강의 목록은 최신순으로 정렬을 해야한다.
따라서 querydsl를 활용해서 작성해주었다.
@Repository
public class LectureQueryRepository {
private final JPAQueryFactory jpaQueryFactory;
private final QLecture lecture = QLecture.lecture;
public LectureQueryRepository(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
}
public List<Lecture> findAll(){
return jpaQueryFactory
.selectFrom(lecture)
.join(lecture.teacher).fetchJoin()
.orderBy(lecture.createTime.desc())
.fetch();
}
이 코드에서 정렬을 orderBy(~~.desc())이다.
이걸 토대로
public List<LectureListResponse> findAll() {
return lectureQueryRepository.findAll()
.stream()
.map(lecture -> new LectureListResponse(
lecture.getId(),
lecture.getTitle(),
lecture.getTeacher().getName(),
lecture.getPrice(),
lecture.getCategory(),
lecture.getCreateTime()))
.toList();
}
이렇게 바뀌었다. 구조는 크게 바뀌지 않았지만, queryRepository를 사용해서 바뀌어서 동작할 때 내부속에서 정렬이 되어져서 나온다.
public LectureDetailResponse findById(Long lectureId) {
Lecture lecture = lectureRepository.findById(lectureId)
.orElseThrow(() -> new NoSuchElementException("강의가 존재하지 않습니다."));
List<StudentResponse> studentResponses = lecture.getStudents()
.stream()
.map(s -> new StudentResponse(
s.getStudent().getNickName(),
s.getEnrollmentTime()
))
.toList();
return new LectureDetailResponse(
lectureId,
lecture.getTitle(),
lecture.getIntroduce(),
lecture.getPrice(),
lecture.getCountStudent(),
studentResponses,
lecture.getCategory(),
lecture.getCreateTime(),
lecture.getUpdateTime());
}
강의 상세 조회에는 학생의 정보라는 dto가 list의 형태로 반환해줘야하는 문제가 있었다. 이걸 어떻게 해결하지 싶었는데, 그냥 하나 늘 하던 것처럼 .map을 사용해서 dto형태로 바꿔주고 그 값을 그냥 받아주면 된다.
💡 조회를 할 때 수강신청한 수도 알아야한다.
따라서 수강 신청 할 때 count를 증가해줘야한다.
1. 먼저 count를 증가해주는 함수를 하나 만들어준다.
public void countStudent() {
this.countStudent++;
}
lecture.countStudent(); 을 호출해주면 count가 올라가게 된다.❓ <나의고민>
만약에 탈퇴한 회원이면 수강신청도 삭제되어야 하고, 그럼 그 수도 삭제해줘야 하는데, 그럼 삭제 count를 해줘야 하는건가?
이건, 다음에 한번 더 해보기로 하자
강의 저장
public LectureResponse save(LectureCreateRequest lectureCreateRequest) {
Teacher teacher = teacherRepository.findById(lectureCreateRequest.teacherId())
.orElseThrow(() -> new NoSuchElementException("강사가 없습니다."));
Lecture lecture = lectureRepository.save(new Lecture(
lectureCreateRequest.title(),
lectureCreateRequest.price(),
lectureCreateRequest.category(),
lectureCreateRequest.introduce(),
teacher,
LocalDateTime.now()
));
return new LectureResponse(
lecture.getId(),
lecture.getTitle(),
lecture.getIntroduce(),
lecture.getPrice(),
lecture.getCategory(),
teacher.getName(),
lecture.getCreateTime());
}
사실 여기서 teacherRepository가 없기 때문에, 그냥 넘어가야한다.
수강 신청이 구현되지 않은 상태에서 강의 상세 조회를 구현 중이었다면, 도중에 수강 신청을 구현하러 가지 말고, 수강생 수와 수강생 목록 데이터는 일단 임의의 값으로 채우고 넘어가는 것이 좋음.라고 하셨다. 이렇게 구현했어야 하는데 나는 수강신청 다녀왔다.ㅎㅎ 그래서 뭐,,,, 다음번에 기회가 되면 저렇게 구현해봐야겠다.
강의 수정
@Transactional
public LectureResponse update(Long lectureId, LectureUpdateRequest lectureUpdateRequest) {
Lecture lecture = lectureRepository.findByIdAndDeletedFalse(lectureId)
.orElseThrow(() -> new NoSuchElementException("찾으시는 강의가 없습니다."));
lecture.update(
lectureUpdateRequest.title(),
lectureUpdateRequest.introduce(),
lectureUpdateRequest.price());
return new LectureResponse(
lectureId,
lecture.getTitle(),
lecture.getIntroduce(),
lecture.getPrice(),
lecture.getCategory(),
lecture.getTeacher().getName(),
lecture.getUpdateTime()
);
}
강의 수정은 lecture엔티티안에 update함수를 넣어놔서 set으로 관리하지 않고, update로 한번에 관리해줬다.
강의 삭제
public void delete(Long lectureId) {
Lecture lecture = lectureRepository.findById(lectureId)
.orElseThrow(() -> new NoSuchElementException("찾는 강의가 없습니다."));
lectureRepository.delete(lecture);
}
일단 그냥 hard delete방식을 사용해서 했다. 다음에 soft delete방식으로 구현하자.