-
Notifications
You must be signed in to change notification settings - Fork 2
장소 관련 정보 네이버에서 크롤링2차 장소 db구축 #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
장소 관련 정보 네이버에서 크롤링2차 장소 db구축 #94
The head ref may contain hidden characters: "20250519_#80_\uAE30\uB2A5\uCD94\uAC00_place_\uC7A5\uC18C_\uAD00\uB828_\uC815\uBCF4_\uB124\uC774\uBC84\uC5D0\uC11C_\uD06C\uB864\uB9C12\uCC28_\uC7A5\uC18C_db\uAD6C\uCD95"
Conversation
…_좌표_가져오기1차_장소db_구축
…기로직으로 바꿔서 한꺼번에 장소저장 #79
|
""" Walkthrough이 변경사항은 Kakao API 기반의 장소 데이터 동기화 및 크롤링, 데이터베이스 저장, 그리고 관련 API 엔드포인트를 추가하는 대규모 기능 확장입니다. WebClient, Selenium, OkHttp, Gson 등 다양한 라이브러리 의존성이 추가되었고, 새로운 엔티티, 서비스, DTO, 컨트롤러가 도입되었습니다. 서버 초기화 시 장소 데이터가 비어있으면 자동으로 동기화가 트리거됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PlaceController
participant PlaceService
participant PlaceFetchService
participant KakaoCrawler
participant PlaceRepository
participant Kakao API
User->>PlaceController: POST /api/place/refresh
PlaceController->>PlaceService: deleteAllAndRefresh()
PlaceService->>PlaceRepository: deleteAll()
PlaceService->>PlaceFetchService: fetchPlaces()
PlaceFetchService->>Kakao API: GET /v2/local/search/category.json (여러 카테고리/페이지)
Kakao API-->>PlaceFetchService: 장소 리스트 반환
PlaceFetchService-->>PlaceService: KakaoPlaceDocumentResponse 리스트 반환
PlaceService->>PlaceRepository: saveAll(Place 엔티티 리스트)
loop 각 Place
PlaceService->>KakaoCrawler: crawlByPlaceId(placeId)
KakaoCrawler->>Kakao API: Selenium+OkHttp로 상세정보 크롤링
Kakao API-->>KakaoCrawler: 상세정보 JSON 반환
KakaoCrawler-->>PlaceService: KakaoPlaceCrawlDetail 반환
PlaceService->>PlaceRepository: save(업데이트된 Place)
end
PlaceService-->>PlaceController: 완료 응답
PlaceController-->>User: HTTP 200 OK
Possibly related PRs
Poem
""" 📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
PR 빌드 검증 결과: ✅ 빌드 성공빌드 검증이 완료되었습니다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Nitpick comments (16)
src/main/java/jombi/freemates/controller/AuthController.java (1)
152-158: 인증 및 쿼리 파라미터 설명 개선 👍
회원 탈퇴 API에 JWT 인증 요구사항과hard파라미터 설명이 명확하게 추가되었습니다.
다만, 기존AuthController내 다른 메서드의 OpenAPI 문서와 들여쓰기·마크다운 스타일(예:-앞 공백 수)이 일관되도록 조정 부탁드립니다.build.gradle (1)
106-108: JSON 라이브러리 통일 제안
Gson 의존성을 추가하셨는데, Jackson 기반의 직렬화 설정과 충돌할 수 있습니다. JSON 처리 전략을 통일하거나 설정을 분리하는 리팩토링을 고려해보세요.src/main/java/jombi/freemates/config/TaskExecutorConfig.java (1)
8-10: @EnableAsync로 비동기 실행 활성화
비동기 작업 실행을 위해 @EnableAsync를 추가하신 점이 적절합니다. 다만 corePoolSize가 기본값(1)으로 동작하므로 실제 부하에 맞춰 corePoolSize 등을 세부 설정하는 것을 권장드립니다.src/main/java/jombi/freemates/config/WebClientConfig.java (1)
13-22: Kakao WebClient 빈 정의
baseUrl및 인증 헤더 설정이 적절합니다. 네트워크 지연이나 오류에 대비해.clientConnector()를 통한 타임아웃 설정이나 재시도 전략을 추가하는 옵션을 고려해보세요.src/main/java/jombi/freemates/model/dto/KakaoCrawlDetail.java (1)
1-22: Lombok 기반 DTO 구현
Lombok을 이용해 보일러플레이트를 제거하고 가독성을 높인 점이 좋습니다. OpenAPI 문서화를 위해@Schema어노테이션 적용을 고려해보세요.src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (2)
22-27: 애플리케이션 시작 시 데이터 초기화 로직 구현 👍
ApplicationReadyEvent이벤트 리스너를 통해 애플리케이션이 시작될 때 장소 데이터를 초기화하는 로직이 잘 구현되어 있습니다. 이 방식은 애플리케이션 시작 시 필요한 데이터를 준비하는 적절한 방법입니다.다만, 현재
placeRepository는 로깅 목적으로만 사용되고 있습니다. 이 책임을 서비스 계층으로 이동하는 것이 좋겠습니다.@EventListener(ApplicationReadyEvent.class) public void onAppReady() { - log.debug("장소 개수 {}", placeRepository.count()); placeService.refreshPlacesIfEmpty(); }
29-32: 불필요한 공백 라인 제거코드 가독성을 위해 파일 끝의 불필요한 공백 라인을 제거해주세요.
} - - - }src/main/java/jombi/freemates/model/dto/KakaoResponse.java (1)
20-25: Meta 클래스에 Setter 추가 고려
Meta내부 클래스에는@Getter만 적용되어 있고@Setter가 없습니다. 이는 의도적인 설계일 수 있지만, 만약 meta 정보를 수정해야 하는 경우 문제가 될 수 있습니다. 필요에 따라@Setter를 추가하거나, 불변 객체로 사용할 의도라면@NoArgsConstructor및@AllArgsConstructor를 추가하는 것이 좋겠습니다.- @Getter + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor public static class Meta { private int totalCount; private int pageableCount; private boolean isEnd; }src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
49-50: 문자 리터럴 결합 가독성 개선 제안
baseUrl + '/' + placeId표현은 동작에는 문제없지만,
'/'는char→String으로 암묵 변환되어 읽기 어려울 수 있습니다.-String mainUrl = baseUrl+'/' + placeId; +String mainUrl = baseUrl + "/" + placeId;src/main/java/jombi/freemates/model/postgres/Place.java (1)
32-45: 좌표·거리 타입을 String 으로 저장하면 후처리 비용 증가
x,y,distance가 모두String이라서
- 정렬/범위검색(반경검색) 시 매번 형 변환 필요
- 잘못된 형식의 값이 저장되어도 컴파일 시점에 검증 불가
BigDecimal(좌표)·Integer(distance, m 단위) 등 명확한 타입으로 맵핑하면 JPA 쿼리 최적화와 데이터 무결성에 유리합니다.src/main/java/jombi/freemates/model/dto/KakaoDocument.java (1)
35-42: 좌표·거리 필드 타입 검토 권장DTO 단계이므로 문자열 유지가 편할 수 있으나,
서비스 레이어에서 수치 연산(예: 반경 필터링)을 한다면 변환 비용이 반복 발생합니다.
필요 시double x,double y,int distance로 직접 매핑하도록 고민해 보세요.src/main/java/jombi/freemates/service/PlaceService.java (5)
55-63: 카카오 API 호출 병렬화 고려 제안
concatMap으로 인해 카테고리 → 코드 → 페이지 조회가 모두 순차적으로 진행되어 전체 수집 시간이 길어질 수 있습니다.
flatMap에 제한된 concurrency 값을 주어 병렬 요청을 허용하면 응답 지연을 크게 줄일 수 있습니다.- // 코드 하나당 전체 페이지를 조회 - .concatMap(this::fetchAllPagesFor) + // 코드 하나당 전체 페이지를 (최대 4개까지) 병렬 조회 + .flatMap(this::fetchAllPagesFor, /* concurrency */ 4)방화벽 / 쿼터 이슈가 없다면 병렬화를 적극 검토해 주세요.
64-70: 중복 필터 로직 재검토이미
categoryType.getKakaoCodes()를 반복하며 조회하기 때문에
filter(doc -> CATEGORIES.stream() …)로 카테고리 그룹 코드를 다시 확인하는 과정이 사실상 중복입니다.
이 부분을 제거하면 불필요한 스트림 연산을 한 단계 줄일 수 있습니다.
150-156: 블로킹 호출의 타임아웃 및 예외 처리 강화 제안
fetchPlaces().timeout(Duration.ofMinutes(2)).block();에서 타임아웃 발생 시 바로TimeoutException이 던져져
전체 트랜잭션이 롤백됩니다.
• 예외를 캐치해 재시도하거나 사용자 친화적인 메시지로 전환
•blockOptional().orElse(Collections.emptyList())와 같이 null-safety 확보
를 고려해 주세요.
192-206: N+1 저장 호출 → 일괄 저장으로 변경 권장동기 크롤링 루프마다
placeRepository.save(place)를 호출하면 DB 라운드트립이 수백 / 수천 번 발생합니다.
최종 결과를 리스트에 모아saveAll한 번으로 처리하거나 batch size 를 조절해 성능을 개선할 수 있습니다.
214-244: 메서드 주석과 구현 내용 불일치 & 트랜잭션 범위 검토
- 주석에 “네이버 크롤링” 이라고 적혀 있으나, 실제로는
KakaoCrawler를 사용하고 있습니다. 혼동을 방지하기 위해 주석을 수정해 주세요.placeRepository.saveAll(updatedPlaces)가 메서드 외부 트랜잭션 없이 호출됩니다. 대량 저장 시 실패 원자성을 확보하려면@Transactional추가를 고려해 주세요.- 본 메서드는 논블로킹을 취지로 작성되었지만 마지막에
.block()으로 다시 블로킹됩니다. 필요하다면 상위 호출부에서 block 하거나,subscribe()로 완전히 비동기로 전환하는 것이 더 일관됩니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
build.gradle(3 hunks)src/main/java/jombi/freemates/config/CorsConfig.java(0 hunks)src/main/java/jombi/freemates/config/TaskExecutorConfig.java(1 hunks)src/main/java/jombi/freemates/config/WebClientConfig.java(1 hunks)src/main/java/jombi/freemates/controller/AuthController.java(1 hunks)src/main/java/jombi/freemates/controller/PlaceController.java(1 hunks)src/main/java/jombi/freemates/model/constant/CategoryType.java(1 hunks)src/main/java/jombi/freemates/model/dto/KakaoCrawlDetail.java(1 hunks)src/main/java/jombi/freemates/model/dto/KakaoDocument.java(1 hunks)src/main/java/jombi/freemates/model/dto/KakaoResponse.java(1 hunks)src/main/java/jombi/freemates/model/postgres/Place.java(1 hunks)src/main/java/jombi/freemates/repository/PlaceRepository.java(1 hunks)src/main/java/jombi/freemates/service/PlaceService.java(1 hunks)src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java(1 hunks)src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/jombi/freemates/config/CorsConfig.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1)
src/main/java/jombi/freemates/service/PlaceService.java (1)
Slf4j(27-250)
src/main/java/jombi/freemates/model/dto/KakaoDocument.java (1)
src/main/java/jombi/freemates/model/dto/KakaoResponse.java (2)
JsonNaming(11-26)Getter(20-25)
src/main/java/jombi/freemates/service/PlaceService.java (2)
src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1)
Slf4j(11-32)src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
Service(29-154)
🔇 Additional comments (15)
src/main/java/jombi/freemates/controller/AuthController.java (1)
159-165: '동작' 섹션 설명 보강으로 이해도↑
soft delete와 hard delete 동작을 구분해 상세히 기술한 점이 좋습니다. 변경된 동작 설명은 명확하며 추가 검토 필요 없습니다.build.gradle (3)
32-34: WebDriverManager 의존성 추가
패키지 버전을 명시하여 안정적으로 WebDriverManager를 사용할 수 있도록 한 점이 적절합니다.
71-73: WebFlux 의존성 중복 확인 요청
Spring MVC(spring-boot-starter-web)와 함께 WebFlux를 추가하면 Netty 서버가 함께 구동될 수 있습니다. 의도한 설정인지 확인하세요.
126-128: macOS 전용 DNS 레졸버 검증 요청
현재 macOS용 네이티브 DNS 레졸버만 추가되어 있습니다. CI/CD 및 Linux 환경 지원 여부를 확인하시고, 필요 시 Linux 의존성도 함께 추가해주세요.src/main/java/jombi/freemates/repository/PlaceRepository.java (1)
1-8: JpaRepository 상속 인터페이스 생성
기본 CRUD와 페이징 기능을 제공하는 JpaRepository를 잘 상속하셨습니다. 추가 커스텀 쿼리가 필요할 경우 이 인터페이스에 선언하여 사용하세요.src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java (1)
11-16: 컴포넌트 생성자 주입 설계 👍의존성 주입이 잘 설계되어 있습니다.
@RequiredArgsConstructor와final필드를 사용한 생성자 주입 방식은 Spring에서 권장하는 방식입니다.src/main/java/jombi/freemates/model/dto/KakaoResponse.java (2)
11-16: DTO 클래스 설계 👍DTO 클래스 설계가 잘 되어 있습니다. Jackson의
@JsonNaming을 사용하여 snake_case JSON 속성을 자바 camelCase로 매핑하고, Lombok 어노테이션을 사용하여 보일러플레이트 코드를 줄였습니다.
17-18: Kakao API 응답 구조 매핑 구현 👍Kakao API 응답 구조에 맞게
meta와documents필드가 잘 정의되어 있습니다.src/main/java/jombi/freemates/controller/PlaceController.java (2)
13-19: Swagger 문서화 및 컨트롤러 설계 👍Swagger
@Tag를 사용하여 API 문서화를 적절히 구현하고, Spring MVC 컨트롤러 패턴이 잘 적용되어 있습니다.
20-21: 권한 설정 구현 필요주석으로 처리된
@PreAuthorize어노테이션이 있습니다. 이는 추후 권한을 설정하겠다는 의도로 보이는데, 프로덕션 환경에 배포하기 전에 반드시 권한 설정을 구현해야 합니다. 장소 데이터를 모두 삭제하고 새로고침하는 기능은 관리자만 사용할 수 있어야 합니다.주석을 제거하고 권한 설정을 활성화해주세요:
-//@PreAuthorize("hasRole('ROLE_ADMIN')") -//추후 권한 설정 +@PreAuthorize("hasRole('ROLE_ADMIN')")src/main/java/jombi/freemates/model/constant/CategoryType.java (4)
8-14: 열거형 설계 및 문서화 👍열거형(Enum) 클래스가 잘 설계되어 있고, Kakao API 문서 링크와 함께 적절히 문서화되어 있습니다.
@AllArgsConstructor와@Getter를 사용하여 코드가 간결합니다.
15-20: 카테고리 정의 구현 👍카테고리 타입과 해당 Kakao 코드가 명확하게 정의되어 있습니다. 여러 코드를 하나의 카테고리로 매핑하는
Set.of()사용은 좋은 접근 방식입니다.
26-28: 카테고리 매칭 로직 👍단순하고 효율적인 매칭 메서드 구현이 잘 되었습니다.
30-35:Details
✅ Verification successful
카테고리 조회 메서드 구현 검증
of메서드 구현은 좋지만, 중복된 Kakao 코드가 있을 경우 첫 번째 일치하는 카테고리만 반환하게 됩니다. 코드 값이 여러 카테고리에 중복되지 않는지 확인이 필요합니다.
🏁 Script executed:
#!/bin/bash # 카테고리 간 Kakao 코드 중복 여부 확인 echo "CategoryType의 Kakao 코드 중복 검사:" rg -A 1 "Set.of\\(" src/main/java/jombi/freemates/model/constant/CategoryType.javaLength of output: 344
카테고리 Kakao 코드 중복 검사 완료 – 중복 없음
- CategoryType.java 에 정의된 Kakao 코드(CE7, FD6, MT1, CS2, AT4, CT1, HP8)는 서로 고유합니다.
of(String kakaoCode)메서드는 첫 일치 항목을 반환하지만, 현재 중복 코드가 없어 안전하게 동작합니다.향후 새로운 카테고리 또는 Kakao 코드 추가 시 반드시 중복 여부를 확인해 주세요.
src/main/java/jombi/freemates/service/PlaceService.java (1)
118-136: CategoryType 매핑 실패 시 NPE 가능성 확인 요청
CategoryType.of()가 매핑에 실패하면null을 반환할 가능성이 있습니다.
해당 필드가 DB NOT NULL 제약이거나 이후 로직에서null사용을 가정하지 않는다면 NPE 가 발생할 수 있으니,
예외 처리 혹은UNKNOWN과 같은 기본값을 반환하도록 확인 부탁드립니다.
| ## 반환값 (ResponseEntity<Void>) | ||
| - **`ResponseEntity<Void>`** (상태코드 200 OK) | ||
| ## 에러 코드 | ||
| - `MEMBER_NOT_FOUND`: 회원을 찾을 수 없음 | ||
| """ | ||
| ## 에러코드 | ||
| - **`MEMBER_NOT_FOUND`**: 회원을 찾을 수 없음 | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
반환값 및 에러코드 보강 제안
MEMBER_NOT_FOUND 외에도 인증 실패 시 UNAUTHORIZED(401) 또는 권한 없음(FORBIDDEN 403)과 같은 에러 코드/설명 추가를 고려해주세요.
이로써 JWT 토큰 미제공·만료·잘못된 토큰 시 발생 가능한 시나리오를 문서화하면 클라이언트 사용성 및 안정성이 향상됩니다.
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/AuthController.java around lines 166
to 171, enhance the response documentation by adding error codes and
descriptions for authentication failures such as UNAUTHORIZED (401) for missing,
expired, or invalid JWT tokens, and FORBIDDEN (403) for insufficient
permissions. Update the comments or API documentation to include these scenarios
alongside MEMBER_NOT_FOUND to improve client understanding and error handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무시하죠
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cassiiopeia 네, 알겠습니다. 해당 제안은 무시하겠습니다.
| // Selenium | ||
| implementation 'org.seleniumhq.selenium:selenium-java' | ||
| implementation 'org.seleniumhq.selenium:selenium-chrome-driver' | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Selenium 버전 명시 요구
Selenium 의존성에 버전을 지정하지 않으면 호환성 관리가 어려워질 수 있습니다. 명시적 버전 지정을 권장합니다.
다음과 같이 버전을 추가해주세요:
- implementation 'org.seleniumhq.selenium:selenium-java'
- implementation 'org.seleniumhq.selenium:selenium-chrome-driver'
+ implementation 'org.seleniumhq.selenium:selenium-java:4.8.1'
+ implementation 'org.seleniumhq.selenium:selenium-chrome-driver:4.8.1'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Selenium | |
| implementation 'org.seleniumhq.selenium:selenium-java' | |
| implementation 'org.seleniumhq.selenium:selenium-chrome-driver' | |
| // Selenium | |
| implementation 'org.seleniumhq.selenium:selenium-java:4.8.1' | |
| implementation 'org.seleniumhq.selenium:selenium-chrome-driver:4.8.1' |
🤖 Prompt for AI Agents
In build.gradle around lines 116 to 119, the Selenium dependencies are declared
without specifying versions, which can cause compatibility issues. Fix this by
adding explicit version numbers to both 'selenium-java' and
'selenium-chrome-driver' dependencies to ensure consistent and manageable
builds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무시하죠
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cassiiopeia 네, 이해했습니다. 해당 제안을 무시하고 현재 Selenium 의존성 설정을 그대로 유지하겠습니다.
| @PostMapping("/refresh") | ||
| public ResponseEntity<Void> refreshPlaces() { | ||
| placeService.deleteAllAndRefresh(); | ||
| return ResponseEntity.ok().build(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
대량 데이터 조작에 대한 보호 메커니즘 추가
deleteAllAndRefresh() 메서드는 모든 장소 데이터를 삭제하고 다시 로드하는 리소스 집약적인 작업입니다. 이러한 작업은 실수로 여러 번 호출되면 시스템에 부담을 줄 수 있습니다.
동시 호출을 제한하거나, 작업 진행 상태를 확인하는 메커니즘을 추가하는 것이 좋겠습니다. 또한 이 작업이 현재 진행 중인지 확인하는 로직을 추가하고, 작업 완료 후 결과를 로깅하는 것을 권장합니다.
@PostMapping("/refresh")
public ResponseEntity<Void> refreshPlaces() {
+ log.info("장소 데이터 전체 새로고침 시작");
placeService.deleteAllAndRefresh();
+ log.info("장소 데이터 전체 새로고침 완료");
return ResponseEntity.ok().build();
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/controller/PlaceController.java around lines 25
to 29, the refreshPlaces method calls placeService.deleteAllAndRefresh(), which
is a resource-intensive operation that deletes and reloads all place data. To
protect against multiple concurrent calls, add a mechanism to check if the
refresh operation is already in progress and prevent overlapping executions.
Implement synchronization or a flag to track the operation status, return an
appropriate response if a refresh is ongoing, and add logging after the
operation completes to record the result and duration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무시하죠 이거 고장났네 이거
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cassiiopeia 네, 이해했습니다. 해당 제안을 무시하도록 하겠습니다. 필요한 경우 다른 도움이 필요하시면 말씀해 주세요.
| try (Response res = client.newCall(req).execute()) { | ||
|
|
||
| if (!res.isSuccessful()) { | ||
| throw new IOException("API 호출 실패: " + res.code()); | ||
| } | ||
| return JsonParser.parseString(res.body().string()).getAsJsonObject(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
응답 바디 null 가능성 미처리 → NPE 위험
Response.body() 가 null 이면 res.body().string() 에서 NPE 가 발생합니다.
isSuccessful() 이어도 바디가 없을 수 있으므로 안전장치를 추가해 주세요.
- return JsonParser.parseString(res.body().string()).getAsJsonObject();
+ if (res.body() == null) {
+ throw new IOException("API 응답에 본문이 없습니다.");
+ }
+ return JsonParser.parseString(res.body().string()).getAsJsonObject();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try (Response res = client.newCall(req).execute()) { | |
| if (!res.isSuccessful()) { | |
| throw new IOException("API 호출 실패: " + res.code()); | |
| } | |
| return JsonParser.parseString(res.body().string()).getAsJsonObject(); | |
| } | |
| try (Response res = client.newCall(req).execute()) { | |
| if (!res.isSuccessful()) { | |
| throw new IOException("API 호출 실패: " + res.code()); | |
| } | |
| if (res.body() == null) { | |
| throw new IOException("API 응답에 본문이 없습니다."); | |
| } | |
| return JsonParser.parseString(res.body().string()).getAsJsonObject(); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java around lines
100 to 106, the code does not handle the possibility that Response.body() can be
null, which can cause a NullPointerException when calling res.body().string().
To fix this, add a null check for res.body() before accessing its string
content, and handle the null case appropriately, such as throwing an IOException
or returning a default value, to ensure safe processing even when the response
body is absent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daye200 null point exception 날수있다는건데
그냥해도되는데 추가하려면 하슈
회사에서는 이거 nvl() 사용해서 방지하거든요?
실제 코드에 지금있음
CommonUItil.nvl(res.body(),"")
이렇게 사용하면 되거든요?
이러면 null값이라도 String "" 이렇게 반환되어서 나와요
return JsonParser.parseString(CommonUItil.nvl(res.body(),"")).string()).getAsJsonObject();
이렇게 쓰면됨
아무튼 null인값을 참조하면 코드에서 에러나니까
null말고 empty 문자열로 변환해서 사용하는겁니다
이러면 에러가 터지지않아요
안전장치를 항상 생각해야되긴해요
어떤 기능이 작동은 하는데 빈값으로 흘러가는거랑
어떤 기능이 에러가 터져버리는건
운영상황에서 엄청 큰 차이 이기때문에
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java
Outdated
Show resolved
Hide resolved
| // 실패 시 빈 response 로 대체 | ||
| .onErrorResume(Throwable.class, e -> { | ||
| log.warn("카테고리 {} 페이지 {} 호출 실패: {}", categoryCode, page, e.getMessage()); | ||
| return Mono.just(new KakaoResponse(null, List.of())); | ||
| }) | ||
| ) | ||
| // is_end=true 이면 그 페이지까지 수집 후 스트림 종료 | ||
| .takeUntil(resp -> resp.getMeta() != null && resp.getMeta().isEnd()) | ||
| // KaKaoResponse.documents 를 펼쳐서 Flux<KaKaoDocument> 로 반환 | ||
| .flatMapIterable(KakaoResponse::getDocuments); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 페이지 처리 시 무한 조회 가능성
onErrorResume 에서 meta == null 인 빈 응답을 리턴하면
takeUntil(resp.getMeta() != null && resp.getMeta().isEnd()) 조건이 false 로 평가되어
오류가 난 페이지 이후에도 MAX_PAGE 까지 계속 호출됩니다.
- .takeUntil(resp -> resp.getMeta() != null && resp.getMeta().isEnd())
+ // meta 가 없으면 바로 스트림 종료
+ .takeUntil(resp -> resp.getMeta() == null || resp.getMeta().isEnd())meta == null 상황에서도 조기에 종료하도록 수정해 과도한 불필요 호출을 막아 주세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 실패 시 빈 response 로 대체 | |
| .onErrorResume(Throwable.class, e -> { | |
| log.warn("카테고리 {} 페이지 {} 호출 실패: {}", categoryCode, page, e.getMessage()); | |
| return Mono.just(new KakaoResponse(null, List.of())); | |
| }) | |
| ) | |
| // is_end=true 이면 그 페이지까지 수집 후 스트림 종료 | |
| .takeUntil(resp -> resp.getMeta() != null && resp.getMeta().isEnd()) | |
| // KaKaoResponse.documents 를 펼쳐서 Flux<KaKaoDocument> 로 반환 | |
| .flatMapIterable(KakaoResponse::getDocuments); | |
| // 실패 시 빈 response 로 대체 | |
| .onErrorResume(Throwable.class, e -> { | |
| log.warn("카테고리 {} 페이지 {} 호출 실패: {}", categoryCode, page, e.getMessage()); | |
| return Mono.just(new KakaoResponse(null, List.of())); | |
| }) | |
| ) | |
| // is_end=true 이면 그 페이지까지 수집 후 스트림 종료 | |
| // meta 가 없으면 바로 스트림 종료 | |
| .takeUntil(resp -> resp.getMeta() == null || resp.getMeta().isEnd()) | |
| // KaKaoResponse.documents 를 펼쳐서 Flux<KaKaoDocument> 로 반환 | |
| .flatMapIterable(KakaoResponse::getDocuments); |
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceService.java around lines 101 to
110, the onErrorResume returns a KakaoResponse with meta set to null, causing
the takeUntil condition to never be true and resulting in infinite page
requests. Modify the onErrorResume to return a KakaoResponse where meta is
non-null and indicates the end condition (isEnd() == true) so that takeUntil
will stop the stream early and prevent unnecessary further calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 코드봐야될것같은데 코드오류일수도??
@daye200 확인 부탁드려요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
…장소db_구축' into 20250519_#80_기능추가_place_장소_관련_정보_네이버에서_크롤링2차_장소_db구축 # Conflicts: # src/main/java/jombi/freemates/service/PlaceService.java # src/main/java/jombi/freemates/util/init/PlaceDataInitiation.java
Cassiiopeia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구조 바꾸시고
수정할께 좀 많네요
고생하셨슈
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class KakaoCrawlDetail { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어떤거 crawlDetail인지 Class명 구체적으로 부탁드립니다
| // 상세설명 | ||
| private String description; | ||
| // 편의시설 | ||
| private String amenities; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List amenities 로해주세요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저런 기능을 하는 tag를 추가할거라 이름을 전반적으로 바꾸겠습니다!
introText한 줄소개
description 상세설명
tag는 우리끼리 정한 커스텀 태그
| @AllArgsConstructor | ||
| public class KakaoDocument { | ||
|
|
||
| private String id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이전처럼 String 바꿔주시고
다른 entity 참고해서 자동 UUID auto gernerate로해주세요
카카오에서 제공하는 id는 칼럼으로 unique=true 걸어주세요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dto인데도요...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아~ dto구나~ 취소~
|
|
||
| private String description; | ||
|
|
||
| private String amenities; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class PlaceService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금 내부로직이 좀 마음에 안듭니다
KaKaoCrawlingService 이런식으로 나눠서
그쪽에서 크롤링 로직넣고
NaverCrawlingService 해서 이런식으로 나눠서
그쪽에서 크롤링로직 넣고
PlaceService는 말 그대로
Place에 관련된 조절을 하는곳이잖아요?
여기에서는 Place를 추가하거나
Place를 삭제하거나
Place를 수정하는
CRUD 로직이 들어가는게 맞지않을까요?
Service단 분리 시켜야될것같아요
Place생성할때
각각 service 의존성가져와서 메도르로 사용하면서 Place객체 생성하고 그러는게 더 좋지않나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PlaceFetchService(api가져올때) 를 분리해 보도록 하겠습니다
kakaocrawler가 있어서 add는 place에 있어도 될거 같은데...
어떻게 생각하세요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어...일단 해보죠
PR 빌드 검증 결과: ✅ 빌드 성공빌드 검증이 완료되었습니다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (3)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (2)
40-44: ChromeDriver 매번 생성으로 인한 성능 저하 (이전 리뷰와 중복)
메서드 호출마다 ChromeDriver 인스턴스를 생성·파괴하면 CPU/메모리 소모가 급증합니다.
풀링 또는 원격 WebDriver 사용을 권장드립니다.
95-102:⚠️ Potential issue응답 바디 null 처리 누락 → NPE 가능성
res.body()가 null 일 때string()호출에서NullPointerException이 발생할 수 있습니다.- String payload = CommonUtil.nvl(res.body().string(), ""); + if (res.body() == null) { + throw new IOException("API 응답에 본문이 없습니다."); + } + String payload = CommonUtil.nvl(res.body().string(), "");src/main/java/jombi/freemates/service/PlaceService.java (1)
21-24: 서비스 분리에 대한 이전 피드백 재검토이전 리뷰에서 언급된 서비스 분리 제안이 타당합니다.
PlaceService가 현재 다음과 같은 다양한 책임을 가지고 있습니다:
- Place CRUD 작업
- 데이터 동기화
- 크롤링 오케스트레이션
PlaceFetchService분리는 이미 완료되었지만, 크롤링 로직도 별도 서비스로 분리하는 것을 고려해보세요:@Service public class PlaceCrawlingService { public void enrichPlacesWithCrawlData(List<Place> places) { ... } public void enrichPlaceWithCrawlData(Place place) { ... } }이렇게 하면
PlaceService는 순수한 도메인 로직에 집중할 수 있습니다.
🧹 Nitpick comments (5)
src/main/java/jombi/freemates/util/init/FreematesInitiation.java (2)
4-4: 불필요한 import 정리 필요
PlaceRepository는 현재 클래스에서 사용되지 않습니다. IDE-레벨에서는 경고로 끝나지만, CI 단계에서-Werror옵션이 켜져 있으면 컴파일 실패의 원인이 될 수 있으므로 제거를 권장드립니다.-import jombi.freemates.repository.PlaceRepository;
20-30: 초기화 시점 대용량 작업의 비동기 처리 부작용 검토
placeService.refreshPlacesIfEmpty()메서드는@Async로 선언되어 있어 즉시 반환됩니다.
초기 데이터가 존재하지 않을 때에는 다음과 같은 문제가 발생할 수 있습니다.
- 서버가 “준비 완료” 로그를 찍은 뒤에도 백그라운드에서 대량의 Kakao API 호출·DB 저장이 진행돼 초기 요청이 느려질 수 있음
- 비동기 스레드에서 예외가 발생해도 상위 스택으로 전파되지 않아 모니터링이 누락될 수 있음
운영 안정성을 위해
ApplicationRunner대신ApplicationReadyEvent이후 블로킹 처리, 혹은- 비동기 작업에
@Async+Future반환하여 결과 확인 및 로깅
등을 고려해 주세요.src/main/java/jombi/freemates/model/postgres/Place.java (1)
52-54: 좌표 필드의 데이터 타입 고려사항좌표 정보(
x,y)를String으로 저장하고 있습니다. 만약 향후 거리 계산이나 공간 쿼리가 필요하다면Double타입 사용을 고려해보세요.private Double x; // 경도 private Double y; // 위도다만 Kakao API 응답 형식과의 직접 매핑을 위해 의도적으로 String을 사용한 것이라면 현재 구조도 합리적입니다.
src/main/java/jombi/freemates/service/PlaceService.java (2)
36-38: Reactive 스트림 블로킹 사용 고려사항
placeFetchService.fetchPlaces().block()을 사용하여 Reactive 스트림을 블로킹하고 있습니다. 이는 Reactive의 이점을 감소시킵니다.비동기 처리를 유지하거나 서비스 계층에서 Reactive를 완전히 제거하는 것을 고려해보세요:
// Option 1: 완전 비동기 처리 @Async("applicationTaskExecutor") public CompletableFuture<Void> refreshPlacesAsync(boolean deleteFirst) { return placeFetchService.fetchPlaces() .timeout(Duration.ofMinutes(2)) .map(placeFetchService::buildPlaces) .doOnNext(places -> { if (deleteFirst) { placeRepository.deleteAll(); } placeRepository.saveAll(places); }) .then() .toFuture(); }
89-89: 로그 메시지의 일관성 문제로그에서 "네이버에서 크롤링한거"라고 하지만 실제로는 Kakao 크롤링을 수행하고 있습니다. 혼동을 방지하기 위해 정확한 설명으로 수정해주세요.
- log.debug("이미지 저장된 거 {} 네이버에서 크롤링한거{}", place.getImageUrl(), kakaoPlaceCrawlDetail.getImageUrl()); + log.debug("이미지 저장된 거 {} 카카오에서 크롤링한거{}", place.getImageUrl(), kakaoPlaceCrawlDetail.getImageUrl());
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
build.gradle(3 hunks)src/main/java/jombi/freemates/controller/AuthController.java(1 hunks)src/main/java/jombi/freemates/controller/PlaceController.java(1 hunks)src/main/java/jombi/freemates/model/constant/CategoryType.java(1 hunks)src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java(1 hunks)src/main/java/jombi/freemates/model/dto/KakaoPlaceDocumentResponse.java(1 hunks)src/main/java/jombi/freemates/model/dto/KakaoPlaceResponse.java(1 hunks)src/main/java/jombi/freemates/model/postgres/Place.java(1 hunks)src/main/java/jombi/freemates/service/PlaceFetchService.java(1 hunks)src/main/java/jombi/freemates/service/PlaceService.java(1 hunks)src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java(1 hunks)src/main/java/jombi/freemates/util/init/FreematesInitiation.java(3 hunks)
✅ Files skipped from review due to trivial changes (3)
- src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java
- src/main/java/jombi/freemates/model/dto/KakaoPlaceDocumentResponse.java
- src/main/java/jombi/freemates/model/dto/KakaoPlaceResponse.java
🚧 Files skipped from review as they are similar to previous changes (4)
- build.gradle
- src/main/java/jombi/freemates/controller/AuthController.java
- src/main/java/jombi/freemates/controller/PlaceController.java
- src/main/java/jombi/freemates/model/constant/CategoryType.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/jombi/freemates/service/PlaceFetchService.java (2)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
RequiredArgsConstructor(26-151)src/main/java/jombi/freemates/service/PlaceService.java (1)
Slf4j(18-104)
src/main/java/jombi/freemates/model/postgres/Place.java (2)
src/main/java/jombi/freemates/model/dto/KakaoPlaceCrawlDetail.java (1)
Getter(9-22)src/main/java/jombi/freemates/model/dto/KakaoPlaceResponse.java (1)
Getter(20-25)
src/main/java/jombi/freemates/service/PlaceService.java (3)
src/main/java/jombi/freemates/service/crawler/KakaoCrawler.java (1)
RequiredArgsConstructor(26-151)src/main/java/jombi/freemates/service/PlaceFetchService.java (1)
Slf4j(18-129)src/main/java/jombi/freemates/util/init/FreematesInitiation.java (1)
Slf4j(14-34)
🔇 Additional comments (1)
src/main/java/jombi/freemates/model/postgres/Place.java (1)
65-66: @Builder.Default 적용이 올바르게 되었습니다.이전 리뷰에서 지적된
@Builder.Default누락 문제가 해결되었습니다. 이제 빌더 패턴 사용 시likeCount와viewCount가 기본값 0으로 초기화됩니다.Also applies to: 68-69
| public List<Place> buildPlaces(List<KakaoPlaceDocumentResponse> docs) { | ||
| return docs.stream() | ||
| .map(doc -> Place.builder() | ||
| .kakaoPlaceId(doc.getId()) | ||
| .addressName(doc.getAddressName()) | ||
| .categoryGroupCode(doc.getCategoryGroupCode()) | ||
| .phone(doc.getPhone()) | ||
| .placeName(doc.getPlaceName()) | ||
| .placeUrl(doc.getPlaceUrl()) | ||
| .roadAddressName(doc.getRoadAddressName()) | ||
| .x(doc.getX()) | ||
| .y(doc.getY()) | ||
| .distance(doc.getDistance()) | ||
| .imageUrl(null) | ||
| .description(null) | ||
| .introText(null) | ||
| .likeCount(0L) | ||
| .viewCount(0L) | ||
| .categoryType(CategoryType.of(doc.getCategoryGroupCode())) | ||
| .build() | ||
| ) | ||
| .collect(Collectors.toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
CategoryType 매핑 실패 방어 로직 필요
CategoryType.of() 가 미등록 코드에 대해 IllegalArgumentException 을 던진다면 전체 빌드가 중단됩니다.
안전한 기본값(예: CategoryType.ETC) 을 주거나 Optional 로 감싸는 방식을 권장합니다.
- .categoryType(CategoryType.of(doc.getCategoryGroupCode()))
+ .categoryType(CategoryType.safeOf(doc.getCategoryGroupCode()))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public List<Place> buildPlaces(List<KakaoPlaceDocumentResponse> docs) { | |
| return docs.stream() | |
| .map(doc -> Place.builder() | |
| .kakaoPlaceId(doc.getId()) | |
| .addressName(doc.getAddressName()) | |
| .categoryGroupCode(doc.getCategoryGroupCode()) | |
| .phone(doc.getPhone()) | |
| .placeName(doc.getPlaceName()) | |
| .placeUrl(doc.getPlaceUrl()) | |
| .roadAddressName(doc.getRoadAddressName()) | |
| .x(doc.getX()) | |
| .y(doc.getY()) | |
| .distance(doc.getDistance()) | |
| .imageUrl(null) | |
| .description(null) | |
| .introText(null) | |
| .likeCount(0L) | |
| .viewCount(0L) | |
| .categoryType(CategoryType.of(doc.getCategoryGroupCode())) | |
| .build() | |
| ) | |
| .collect(Collectors.toList()); | |
| public List<Place> buildPlaces(List<KakaoPlaceDocumentResponse> docs) { | |
| return docs.stream() | |
| .map(doc -> Place.builder() | |
| .kakaoPlaceId(doc.getId()) | |
| .addressName(doc.getAddressName()) | |
| .categoryGroupCode(doc.getCategoryGroupCode()) | |
| .phone(doc.getPhone()) | |
| .placeName(doc.getPlaceName()) | |
| .placeUrl(doc.getPlaceUrl()) | |
| .roadAddressName(doc.getRoadAddressName()) | |
| .x(doc.getX()) | |
| .y(doc.getY()) | |
| .distance(doc.getDistance()) | |
| .imageUrl(null) | |
| .description(null) | |
| .introText(null) | |
| .likeCount(0L) | |
| .viewCount(0L) | |
| - .categoryType(CategoryType.of(doc.getCategoryGroupCode())) | |
| + .categoryType(CategoryType.safeOf(doc.getCategoryGroupCode())) | |
| .build() | |
| ) | |
| .collect(Collectors.toList()); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceFetchService.java around lines 105
to 126, the call to CategoryType.of(doc.getCategoryGroupCode()) can throw
IllegalArgumentException if the code is unregistered, causing the entire build
to fail. Modify this by adding defensive logic to catch this exception and
return a safe default value like CategoryType.ETC instead, ensuring the build
continues safely without interruption.
| .doOnError(WebClientResponseException.class, | ||
| e -> log.error("카카오 API 호출 실패: {}", e.getMessage())) | ||
| .doOnError(Throwable.class, | ||
| e -> log.error("카카오 API 호출 중 오류 발생: {}", e.getMessage())); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
에러 전파 방식 개선 제안
현재 doOnError 는 로그만 남기고 스트림을 그대로 종료시키므로, 상위에서 block() 시 예외가 그대로 전파됩니다.
서비스 기동 중 Kakao API 가 일시적으로 실패하면 전체 초기화가 중단될 위험이 있습니다.
- .doOnError(WebClientResponseException.class,
- e -> log.error("카카오 API 호출 실패: {}", e.getMessage()))
- .doOnError(Throwable.class,
- e -> log.error("카카오 API 호출 중 오류 발생: {}", e.getMessage()));
+ .onErrorResume(e -> {
+ log.error("카카오 API 호출 중단 – 빈 리스트로 대체: {}", e.getMessage());
+ return Mono.just(List.of());
+ });이렇게 하면 실패해도 최소한 서버 기동은 유지할 수 있습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .doOnError(WebClientResponseException.class, | |
| e -> log.error("카카오 API 호출 실패: {}", e.getMessage())) | |
| .doOnError(Throwable.class, | |
| e -> log.error("카카오 API 호출 중 오류 발생: {}", e.getMessage())); | |
| } | |
| .onErrorResume(e -> { | |
| log.error("카카오 API 호출 중단 – 빈 리스트로 대체: {}", e.getMessage()); | |
| return Mono.just(List.of()); | |
| }); |
🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceFetchService.java around lines 62
to 66, the current error handling with doOnError only logs errors but allows
exceptions to propagate and potentially stop the entire service initialization.
Modify the error handling to catch the exceptions and return a fallback or empty
result instead of propagating the error, ensuring that the server startup
continues even if the Kakao API call temporarily fails.
| @Transactional | ||
| public void addKakaoCrawlInfo(){ | ||
| List<Place> places = placeRepository.findAll(); | ||
| for (Place place : places) { | ||
| try { | ||
| // 카카오 크롤링 | ||
|
|
||
| KakaoPlaceCrawlDetail kakaoPlaceCrawlDetail = kakaoCrawler.crawlByPlaceId(place.getKakaoPlaceId()); | ||
| // Place 엔티티에 정보 업데이트 | ||
| place.setImageUrl(kakaoPlaceCrawlDetail.getImageUrl()); | ||
| place.setDescription(kakaoPlaceCrawlDetail.getDescription()); | ||
| place.setIntroText(kakaoPlaceCrawlDetail.getIntroText()); | ||
| placeRepository.save(place); | ||
| log.debug("이미지 저장된 거 {} 네이버에서 크롤링한거{}", place.getImageUrl(), kakaoPlaceCrawlDetail.getImageUrl()); | ||
| } catch (Exception e) { | ||
| log.error("카카오 크롤링 중 오류 발생: {}", e.getMessage(), e); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
성능 및 트랜잭션 범위 개선이 필요합니다.
현재 구현에는 다음과 같은 문제점들이 있습니다:
- 메모리 사용량: 모든 Place를 한 번에 메모리에 로드
- 트랜잭션 범위: 전체 크롤링 과정이 하나의 트랜잭션으로 처리되어 DB 연결이 오래 점유됨
- 에러 처리: 한 장소 크롤링 실패 시에도 다른 장소들은 계속 처리되지만, 트랜잭션 롤백 위험
다음과 같이 배치 처리로 개선할 것을 제안합니다:
@Transactional
public void addKakaoCrawlInfo() {
int batchSize = 50; // 배치 크기 설정
long totalCount = placeRepository.count();
for (int i = 0; i < totalCount; i += batchSize) {
Pageable pageable = PageRequest.of(i / batchSize, batchSize);
List<Place> places = placeRepository.findAll(pageable).getContent();
processBatch(places);
}
}
@Transactional
private void processBatch(List<Place> places) {
for (Place place : places) {
try {
KakaoPlaceCrawlDetail detail = kakaoCrawler.crawlByPlaceId(place.getKakaoPlaceId());
updatePlaceWithCrawlInfo(place, detail);
} catch (Exception e) {
log.error("장소 ID {} 크롤링 실패: {}", place.getKakaoPlaceId(), e.getMessage());
// 개별 실패는 전체 배치를 중단시키지 않음
}
}
}🤖 Prompt for AI Agents
In src/main/java/jombi/freemates/service/PlaceService.java around lines 76 to
94, the current addKakaoCrawlInfo method loads all Place entities at once and
processes them in a single transaction, causing high memory usage and long DB
connection holding. Refactor the method to process Places in batches using
pagination (e.g., batch size 50), and move the processing of each batch into a
separate @Transactional method. Inside this batch method, handle exceptions per
Place to log errors without rolling back the entire batch. This approach reduces
memory footprint, shortens transaction scope, and improves error isolation.
#80
Summary by CodeRabbit
신규 기능
문서화
버그 수정
기타