드디어 실전 프로젝트가 마무리가 되는 날이다.
내가 드디어 발표를 하지 않게 되어서 너무 편했다.
이번 프로젝트에서 기본 API를 만들고, 직접 정해본 API에 캐싱 처리를 해보는 목적이 주가 된 프로젝트 경험을 해보게 되었다. 처음에 구상했을 때 프로젝트에 대해서 캐싱과 동시성 제어를 다루는 것에 목표를 두고 잡았었다.
하지만 Redis와 캐싱에 대한 정보가 노베이스라서 시간이 다소 부족했던 결함이 있었다. 기본 API 같은 CRUD 같은 경우에는 하루 이틀 만에 다들 구현이 되어서 빠르게 테스트를 해볼수 있어서 좋았다.
발표할때 처음으로 줌 화면에서 카메라만 킨 경우는 처음인것 같았다.
원래 항상 발표자료와 마이크와 캠을 키고 하는 것이 익숙해져 있었는데 처음으로 발표를 안하게 되서, 어색하긴 하였다.
발표하는 팀원이 발표가 마치고, 튜터님의 질문이 나한테 할줄 알았는데 튜터님의 시선에서는 추천 기능에 눈이 더욱 많이 갔던것 같았다. 그래서 아무런 질문을 하지않아 약간의 아쉬움이 있다. ( 내 파트는 깔끔하게 코드를 봤었는데 )
아무튼 좋게 마무리가 되었고, 실전 프로젝트도 끝을 향해 달려가고 있다.
그러고 이제 KPT 회고록도 정리를 해보았다.
각자 느낀점과 아쉬웠던점 등등 작성하고 회고록을 작성해 제출하는 시간을 가졌다.
2025.05.26 - [[내일배움캠프-Sparta]/KPT 회고] - 책읽아웃 실전 프로젝트 KPT 회고록
책읽아웃 실전 프로젝트 KPT 회고록
개요스프링부트 실전 프로젝트깃 허브 링크 : https://github.com/minjee2758/BookReviewApp프로젝트 정리본 : https://www.notion.so/teamsparta/13-S-A-1e52dc3ef514803db535df121c608ade?pvs=4시연 영상 링크 : https://drive.google.com/f
dimenshun.tistory.com
💥 트러블 슈팅 1
배경
도서를 생성할때 nullPointerException 되는 현상. [ 이번 팀프로젝트에서 이때까지 배워왔던 컨트롤러 방식과는 다르게 색다른 방식으로 ApiReponse 클래스에 담아서 적용을 해보게 되었다. ]

과정

final을 적용해주지 않아서 생성자 주입이 되지 않았던 상황이다 아무리 @RequiredArgsConstructor어노테이션을 적용해도 final을 해주지 않으면 생성자의 필요성을 느끼지 않다는것을 깨달았다.
해결
final을 주입해줌으로써 정상적으로 작동이 가능하게 되었다.
추가적인 문제상황 ( 배경 2 )
정상적인 요청값을 보내 이번엔 도서 생성이 되지만, 생성일과 수정일이 null 표시가 된다는 현상 발견.
과정

처음에는 BaseEntity가 잘못됫나 싶어서 확인해보았지만, 전 프로젝트를 참고하면서 보니까 정상적으로 잘 작성 되어 있었다. 그런데도 왜 안됬었냐? 하고 보니까 SpringBoot 프로젝트의 api 실행하는 시점에서 @EnableJpaAuditing가 누락 되어 있어서 자동으로 생성/수정일을 Auditing 하고 있지 않아서, null이 발생한다는 점을 인지하였다.
해결
과정을 기반으로 해결을 하여 해당 어노테이션을 추가함으로써 문제 상황을 해결할 수 있게 되었다.
💥 트러블 슈팅 2
배경
조회 코드 작성후 IntelliJ내 'Type expected', 'Identifier expected' 에러 하지만 서버는 정상적으로 작동
과정

33번 줄 pageable 뒤쪽에서 빨간줄 이 생겼는데 제 아무리 코드상으로 문제는 없고 서버가 잘 작동하였다. 그래서 도저히 문제가 해결이 되지가 않자 AI한테 문제를 제시해보았다. 그러더니 IDE 캐시나 인데스 문제일 가능성이 높다고 하였다. 내부적으로 코드를 분석한 정보(캐시, 인덱스)가 꼬였을 때 이런일이 발생할 가능성이 있다고 답변해주어 인지하게 되었다.
해결
IntelliJ "Invalidate Caches / Restart..." (캐시 무효화 및 재시작) 기능을 사용하여 문제를 해결할 수 있었다.
✅ AI의 답변
IDE 캐시나 인덱스 문제 (이게 제일 흔해! 짱 중요!)
- 놓치기 쉬운 이유: 코드는 전혀 건드리지 않았는데 갑자기 에러가 뜨거나, 분명 수정한 것 같은데 이전 에러가 그대로 남아있는 경우야. IDE(IntelliJ, Eclipse 등)가 파일 변경 사항을 제대로 반영하지 못하거나, 내부적으로 코드를 분석한 정보(캐시, 인덱스)가 꼬였을 때 이런 일이 일어나.
- 왜 에러가 뜰까?: IDE는 자기 머릿속 정보로 코드를 검사하는데, 그 정보가 최신이 아니거나 잘못되면 멀쩡한 코드를 이상하다고 생각하고 에러 표시를 띄우는 거지.
- 해결: IDE 메뉴에서 "Invalidate Caches / Restart..." (캐시 무효화 및 재시작) 기능을 사용해보자! 이게 마법처럼 해결될 때가 많단다!
💥 트러블 슈팅 3
배경
팀원 중 한분의 로컬 환경에서 PostMan으로 기능별로 테스트를 하는 중에 요청값으로 JSON으로 데이터가 전달이 되지 않는 현상
과정
- Postman에서 POST 요청 시 Content-Type: application/json 설정 확인
- JSON 구조도 올바르게 구성되어 있음에도 불구하고, 컨트롤러 파라미터의 필드값이 null로 들어옴
- 같은 API를 다른 팀원이 테스트하면 정상 작동하는 것으로 보아 환경 문제보다는 코드/구조적 이슈로 판단
- DTO 클래스의 필드명이 JSON 키와 일치하지 않음에도, 별도 매핑을 하지 않고 있었음
- Jackson에서 필드 이름이 다를 경우 직렬화/역직렬화가 되지 않음
해결
@JsonCreator
public CreateBookRequestDto(
@JsonProperty("title") String title,
@JsonProperty("author") String author,
@JsonProperty("category") String category
) {
this.title = title;
this.author = author;
this.category = category;
}
@JsonCreator 와 @JsonProperty 를 통해 JSON 키와 DTO 필드를 명확히 매핑함으로써 Postman 요청 시 데이터가 제대로 전달되었고, 컨트롤러에서 정상적으로 DTO가 바인딩되어 기능이 정상 작동
💥 트러블 슈팅 4
배경
원래 구성으로 도서 상세조회를 할때, 조회수가 요청할때마다 1씩 증가하는씩으로 구현하려고 하였는데, 실제로 요청시 응답 값이 1로만 되어 있는 현상
과정
1️⃣
트랜잭션으로 안묶여져 있어서 그런가 해서 추가 해주니 문제가 해결이 되었지만, 추가적인 문제상황이 발생. 수정일이 갱신 된다는 점이다.
2️⃣
직접 엔티티 메소드를 사용해서 하면 영속성 컨텍스트 때문에 수정일이 자동으로 갱신되어버려서 그 특징을 인지하였다.
@Modifying // 없으면 UPDATE / DELETE 쿼리가 실행이 안됨.
@Query("UPDATE Book b SET b.viewer = b.viewer + 1 WHERE b.id = :id")
void increaseViewer(@Param("id") Long id);
해당 레포지토리에 쿼리 문을 추가해서 다루면 해결이 되지 않을까 하여서 해당 로직을 추가하였다.
이때 @Modifying 가 없으면, 쿼리 오류가 발생한다. 왜냐하면 기본적으로 쿼리는 SELECT 기준으로 보기 때문에 해당 어노테이션 은 영속성 컨텍스트 변경 감지 특성 때문에 필요하다.
3️⃣
이러고 이제 직접 서비스단에서 비즈니스 로직을 잴 밑에 추가하고 상세조회를 해보니까 DB에는 반영이되어 있고, 응답 값에는 한 발짝 늦게 나오는 현상이다. ( DB는 1인데, 응답값은 0 ) 여기서 코드 순서는 위에서 밑으로 실행되기 때문에 이러한점을 고려하여 메소드 맨위로 위치를 옮기니까 해결이 되었다. (bookrepository.incresaceViewe(findBook.getId()) 로 하니까 반영이 안되어있음. )
해결
@Transactional
public BookDetailsResponseDto findByDetailsBook(Long id) {
// 조회수할때 마다 1씩 증가
bookRepository.increaseViewer(id);
Book findBook = bookRepository.findById(id).orElseThrow(() -> new ApiException(ErrorStatus.BOOK_NOT_FOUND));
// 리뷰 평점
Double rating = reviewRepository.averageScore(findBook.getId());
// 리뷰 수 체크
Long reviewCounts = reviewRepository.countByBookId(findBook.getId());
// 좋아요 수 체크
Long likeCounts = likeRepository.countByBookId(findBook.getId());
return new BookDetailsResponseDto(
findBook.getId(),
findBook.getTitle(),
findBook.getAuthor(),
findBook.getCategory(),
findBook.getCreatedAt(),
findBook.getUpdatedAt(),
findBook.getEnrollStatus(),
rating == null ? 0.0 : rating, // null 방지
reviewCounts,
likeCounts,
findBook.getViewer()
);
}
최종적으로 이번 트러블 슈팅을 통해서 영속성 컨텍스트의 특징 ( 변경감지 )에 대해서 다시 인지하게 되었고, 조회 메소드를 어느 위치에 호출하느냐 에 따라서 DB에 반영하는 방식이 다르다는 점을 인지할 수 있게 되었다.
실전 프로젝트가 막을 내렸다.
내일부터 커리큘럼의 마지막인 최종 프로젝트가 시작된다.
'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글
| TIL 66 [ Spring 최종 프로젝트 ( Day 2 ) - ERD설계, 컨벤션/규칙, 4-Layer 아키텍처 ] (0) | 2025.05.28 |
|---|---|
| TIL 65 [ Spring 최종 프로젝트 ( Day 1 ) - 주제/시장조사, API/ERD설계(초안), 와이어프레임 ] (0) | 2025.05.27 |
| TIL 63 [ Spring 실전 프로젝트 ( Day 6 ) - 코드리뷰, 발표자료, 대본작성 ] (0) | 2025.05.23 |
| TIL 62 [ Spring 실전 프로젝트 ( Day 5 ) - Cache 적용 ] (0) | 2025.05.22 |
| TIL 61 [ Spring 실전 프로젝트 ( Day 4 ) - Cache 💡, 오류 개선, Redis 기본세팅 + PostMan 웹 공유법 ] (0) | 2025.05.21 |