Skip to content

[refactor/#131-refactor-safety-calculator] 안전 점수 계산 최적화#132

Open
HUIJAEKO wants to merge 1 commit into
devfrom
refactor/#131-refactor-safety-calculator
Open

[refactor/#131-refactor-safety-calculator] 안전 점수 계산 최적화#132
HUIJAEKO wants to merge 1 commit into
devfrom
refactor/#131-refactor-safety-calculator

Conversation

@HUIJAEKO

@HUIJAEKO HUIJAEKO commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

🔍️ 이 PR을 통해 해결하려는 문제가 무엇인가요?

✨ 이 PR에서 핵심적으로 변경된 사항은 무엇일까요?

문제를 해결하면서 주요하게 변경된 사항들을 적어 주세요

  • safety, crime_prone_area, police_office 인덱스 생성
  • 주변 인프라 시설 및 교차구역 쿼리 수정

🙏 Reviewer 분들이 이런 부분을 신경써서 봐 주시면 좋겠어요

개발 과정에서 다른 분들의 의견은 어떠한지 궁금했거나 크로스 체크가 필요하다고 느껴진 코드가 있다면 남겨주세요

🩺 이 PR에서 테스트 혹은 검증이 필요한 부분이 있을까요?

테스트가 필요한 항목이나 테스트 코드가 추가되었다면 함께 적어주세요

📌 PR 진행 시 이러한 점들을 참고해 주세요

  • Reviewer 분들은 코드 리뷰 시 좋은 코드의 방향을 제시하되, 코드 수정을 강제하지 말아 주세요.
  • Reviewer 분들은 좋은 코드를 발견한 경우, 칭찬과 격려를 아끼지 말아 주세요.
  • Comment 작성 시 Prefix로 P1, P2, P3 를 적어 주시면 Assignee가 보다 명확하게 Comment에 대해 대응할 수 있어요
    • P1 : 꼭 반영해 주세요 - 이슈가 발생하거나 취약점이 발견되는 케이스 등
    • P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요
    • P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다

Summary by CodeRabbit

릴리스 노트

  • 기능 변경

    • 안전성 평가에 대한 AI 설명 생성 기능이 제거되었습니다.
  • 성능 개선

    • 공간 데이터 쿼리 최적화로 인근 안전 정보 검색 성능이 향상되었습니다.

@coderabbitai

coderabbitai Bot commented Mar 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

개요

세 가지 주요 구성 요소를 최적화합니다: Kakao WebClient에서 baseUrl 매개변수를 제거하고, SafetyExplanationService에서 AI 설명 생성 흐름을 삭제하며, HomeSafetyRepository의 공간 쿼리를 WITH 부쿼리와 사전 계산된 지오메트리 필드를 사용하도록 재작성합니다.

변경 사항

구성 / 파일(들) 요약
Kakao 설정
src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java
kaKaoWebClient() 메서드에서 baseUrl 매개변수 제거. apiKey만 주입받도록 변경되었으며, WebClient 빌더에서 baseUrl 사용이 제거됨.
안전 설명 서비스
src/main/java/com/example/Dingle/property/service/openAI/SafetyExplanationService.java
evaluateAndDescribe() 메서드에서 AI 설명 생성 로직 제거. SafetyEvaluationResult 빌드, JSON 직렬화, OpenAI 호출, PropertyDescription 업데이트 제거. 최종 안전 점수 업데이트 후 종료.
주택 안전 저장소
src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java
3개의 SQL 쿼리를 WITH target 부쿼리로 재작성: findInsideMaxRiskLevel은 ST_Contains 사용, existsCrimeWithin300m은 ST_DWithin으로 최적화, findNearestPoliceDistanceMeter는 <-> 연산자로 개선. CCTV/안전등 50m 쿼리를 사전 계산된 geom_5179 필드 활용으로 단순화.

예상 코드 리뷰 시간

🎯 3 (보통) | ⏱️ ~25분

🐰 배치를 다듬고 SQL을 춤추게 하며,
불필요한 설명을 제거하니 빠르구나!
공간 인덱스의 마법으로 우리의 안전이
번개처럼 빠르게 계산되리라.
최적화의 길을 나아가며 성능도 함께 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 제공된 템플릿 구조를 따르고 있으며, 문제 해결(closed #131)과 핵심 변경 사항(인덱스 생성 및 쿼리 수정)을 포함하고 있습니다.
Linked Issues check ✅ Passed 변경 사항들이 이슈 #131의 목표인 '공간 인덱싱을 사용한 안전 점수 측정 최적화'와 일치합니다. 쿼리 최적화, 설정 변경, 그리고 비즈니스 로직 간소화를 통해 성능 개선을 달성하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항(KaKaoWebClientConfig 간소화, SafetyExplanationService 제거, HomeSafetyRepository 쿼리 최적화)이 이슈 #131의 안전 점수 계산 최적화 목표와 직접 관련이 있습니다.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The PR title accurately summarizes the main objective of optimizing safety score calculations through database query refactoring and spatial indexing improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#131-refactor-safety-calculator

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@HUIJAEKO HUIJAEKO changed the title refactor: 안전 점수 계산 최적화 [refactor/#131-refactor-safety-calculator] 안전 점수 계산 최적화 Mar 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java (1)

13-17: P3: 주석 처리된 설정 코드는 정리하는 것을 권장합니다.

Line 13, Line 17의 주석 코드는 현재 동작과 분리되어 있어 설정 의도를 흐립니다. 유지할 이유가 없다면 제거해 주세요.

정리 예시(diff)
-//    `@Value`("${kakao.base-url}") String baseUrl
     `@Bean`
     public WebClient kaKaoWebClient(`@Value`("${kakao.api-key}") String apiKey) {
         return WebClient.builder()
-//                .baseUrl(baseUrl)
                 .defaultHeader(HttpHeaders.AUTHORIZATION, "KakaoAK " + apiKey)
                 .build();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java`
around lines 13 - 17, Remove the stale commented configuration lines in
KaKaoWebClientConfig to avoid confusion: delete the commented
`@Value`("${kakao.base-url}") declaration and the commented .baseUrl(baseUrl) call
inside the kaKaoWebClient method, keeping only the active
`@Value`("${kakao.api-key}") injection and the WebClient.builder() configuration;
ensure no leftover commented code remains in the kaKaoWebClient method so the
intent is clear.
src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java (1)

75-86: P3: CCTV/보안등 카운트 쿼리는 공통 메서드로 묶는 리팩터링을 권장합니다.

Line 75-112는 infra_type만 달라 중복 유지비가 큽니다. 단일 내부 메서드로 합치면 수정 포인트를 줄일 수 있습니다.

리팩터링 예시
+    private int countInfraWithin50m(String infraType, double lat, double lon) {
+        String sql = """
+        WITH target AS (
+          SELECT ST_Transform(ST_SetSRID(ST_MakePoint(?2, ?3), 4326), 5179) AS g
+        )
+        SELECT COUNT(*)
+        FROM safety s, target
+        WHERE s.infra_type = ?1
+          AND s.geom_5179 IS NOT NULL
+          AND ST_DWithin(s.geom_5179, target.g, 50)
+    """;
+
+        return ((Number) em.createNativeQuery(sql)
+                .setParameter(1, infraType)
+                .setParameter(2, lon)
+                .setParameter(3, lat)
+                .getSingleResult()).intValue();
+    }
+
     public int countCctvWithin50m(double lat, double lon) {
-        String sql = """
-        SELECT COUNT(*)
-        FROM safety s
-        WHERE s.infra_type = 'CCTV'
-          AND s.geom_5179 IS NOT NULL
-          AND ST_DWithin(
-                s.geom_5179,
-                ST_Transform(ST_SetSRID(ST_MakePoint(?1, ?2), 4326), 5179),
-                50
-          )
-    """;
-
-        return ((Number) em.createNativeQuery(sql)
-                .setParameter(1, lon)
-                .setParameter(2, lat)
-                .getSingleResult()).intValue();
+        return countInfraWithin50m("CCTV", lat, lon);
     }

     public int countSafetyLightWithin50m(double lat, double lon) {
-        String sql = """
-        SELECT COUNT(*)
-        FROM safety s
-        WHERE s.infra_type = 'SAFETY_LIGHT'
-          AND s.geom_5179 IS NOT NULL
-          AND ST_DWithin(
-                s.geom_5179,
-                ST_Transform(ST_SetSRID(ST_MakePoint(?1, ?2), 4326), 5179),
-                50
-          )
-    """;
-
-        return ((Number) em.createNativeQuery(sql)
-                .setParameter(1, lon)
-                .setParameter(2, lat)
-                .getSingleResult()).intValue();
+        return countInfraWithin50m("SAFETY_LIGHT", lat, lon);
     }

Also applies to: 95-106

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java`
around lines 75 - 86, Extract the duplicated SQL/count logic into a single
private helper (e.g., countInfraWithinDistance) that accepts infraType, lat, lon
and distance, move the shared SQL string and JDBC/query parameter binding there,
and update public methods like countCctvWithin50m and the analogous
lamp/streetlight method to call this helper with infraType = "CCTV" (or the
other infra value) and distance = 50; ensure the helper preserves the
ST_Transform/ST_SetSRID/ST_MakePoint parameter ordering and returns the int
count so callers remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java`:
- Around line 15-19: The kaKaoWebClient factory removed the baseUrl causing
relative-path requests in KaKaoGeocodingService.getGeoPoint() to fail at
runtime; restore a baseUrl parameter on the kaKaoWebClient method (e.g., add a
String baseUrl `@Value`("${kakao.base-url}") parameter) and re-enable
.baseUrl(baseUrl) on the WebClient.builder(), and ensure application config
contains kakao.base-url (https://dapi.kakao.com) along with kakao.api-key
entries in application.properties or application.yml so the injected values
resolve.

In
`@src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java`:
- Around line 56-67: The query can return zero rows (empty police_office or all
geom_5179 NULL), causing getSingleResult() in HomeSafetyRepository to throw
NoResultException; change the SQL so the COALESCE wraps the entire subquery
result (e.g. SELECT COALESCE((SELECT ... FROM police_office p, target WHERE
p.geom_5179 IS NOT NULL ORDER BY p.geom_5179 <-> target.g LIMIT 1), 999999) AS
distance) so the call in the repository method (the code that calls
getSingleResult() for the nearest police distance) always returns a numeric
default when no row exists; alternatively adjust the repository method to use
getResultList() and return 999999 when the list is empty.

---

Nitpick comments:
In `@src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java`:
- Around line 13-17: Remove the stale commented configuration lines in
KaKaoWebClientConfig to avoid confusion: delete the commented
`@Value`("${kakao.base-url}") declaration and the commented .baseUrl(baseUrl) call
inside the kaKaoWebClient method, keeping only the active
`@Value`("${kakao.api-key}") injection and the WebClient.builder() configuration;
ensure no leftover commented code remains in the kaKaoWebClient method so the
intent is clear.

In
`@src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java`:
- Around line 75-86: Extract the duplicated SQL/count logic into a single
private helper (e.g., countInfraWithinDistance) that accepts infraType, lat, lon
and distance, move the shared SQL string and JDBC/query parameter binding there,
and update public methods like countCctvWithin50m and the analogous
lamp/streetlight method to call this helper with infraType = "CCTV" (or the
other infra value) and distance = 50; ensure the helper preserves the
ST_Transform/ST_SetSRID/ST_MakePoint parameter ordering and returns the int
count so callers remain unchanged.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 103acaa and 05c6550.

📒 Files selected for processing (3)
  • src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java
  • src/main/java/com/example/Dingle/property/service/openAI/SafetyExplanationService.java
  • src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java
💤 Files with no reviewable changes (1)
  • src/main/java/com/example/Dingle/property/service/openAI/SafetyExplanationService.java

Comment on lines +15 to 19
public WebClient kaKaoWebClient(@Value("${kakao.api-key}") String apiKey) {
return WebClient.builder()
.baseUrl(baseUrl)
// .baseUrl(baseUrl)
.defaultHeader(HttpHeaders.AUTHORIZATION, "KakaoAK " + apiKey)
.build();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) WebClient 사용 지점 및 주입 지점 확인
echo "=== kaKaoWebClient 사용 지점 ==="
rg -nP --type=java -C3 'kaKaoWebClient'

echo ""
echo "=== 상대 경로 URI 사용 지점 ==="
rg -nP --type=java -C2 '\.uri\s*\(\s*"/[^"]*"'

echo ""
echo "=== 절대 URL URI 사용 지점 ==="
rg -nP --type=java -C2 '\.uri\s*\(\s*"https?://[^"]*"'

Repository: D-ingle/backend

Length of output: 1684


🏁 Script executed:

#!/bin/bash
# KaKaoGeocodingService 파일 전체 내용 확인
cat src/main/java/com/example/Dingle/user/service/KaKaoGeocodingService.java

Repository: D-ingle/backend

Length of output: 1621


🏁 Script executed:

#!/bin/bash
# KaKaoWebClientConfig 전체 파일 확인
echo "=== KaKaoWebClientConfig.java ==="
cat src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java

echo ""
echo "=== application properties 파일에서 kakao 설정 확인 ==="
fd --type f -e properties -e yml -e yaml | xargs rg -l 'kakao' 2>/dev/null || echo "No kakao config found"

echo ""
echo "=== kakao 관련 설정값 ==="
fd --type f -e properties -e yml -e yaml -x cat {} \; | rg -C2 'kakao' || echo "No kakao properties found"

Repository: D-ingle/backend

Length of output: 915


baseUrl 제거로 인한 런타임 오류 발생 - 반드시 복구 필요

상대 경로 /v2/local/search/address.json를 사용하는 KaKaoGeocodingService.getGeoPoint()가 이 WebClient를 주입받아 사용하고 있습니다. baseUrl이 없으면 상대 경로를 절대 URL로 변환할 수 없어 런타임에 실패합니다.

baseUrl 파라미터와 .baseUrl(baseUrl) 설정을 복구하고, 해당 설정값을 application.properties 또는 application.yml에 추가해야 합니다:

kakao.base-url=https://dapi.kakao.com
kakao.api-key=your-api-key

또는 yml 형식:

kakao:
  base-url: https://dapi.kakao.com
  api-key: your-api-key
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/example/Dingle/global/config/KaKaoWebClientConfig.java`
around lines 15 - 19, The kaKaoWebClient factory removed the baseUrl causing
relative-path requests in KaKaoGeocodingService.getGeoPoint() to fail at
runtime; restore a baseUrl parameter on the kaKaoWebClient method (e.g., add a
String baseUrl `@Value`("${kakao.base-url}") parameter) and re-enable
.baseUrl(baseUrl) on the WebClient.builder(), and ensure application config
contains kakao.base-url (https://dapi.kakao.com) along with kakao.api-key
entries in application.properties or application.yml so the injected values
resolve.

Comment on lines +56 to +67
WITH target AS (
SELECT ST_Transform(ST_SetSRID(ST_MakePoint(?1, ?2), 4326), 5179) AS g
)
SELECT COALESCE(
ST_Distance(p.geom_5179, target.g),
999999
)
FROM police_office p, target
WHERE p.geom_5179 IS NOT NULL
ORDER BY p.geom_5179 <-> target.g
LIMIT 1
""";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "HomeSafetyRepository.java" -type f 2>/dev/null | head -20

Repository: D-ingle/backend

Length of output: 137


🏁 Script executed:

cat -n ./src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java | sed -n '50,80p'

Repository: D-ingle/backend

Length of output: 1147


🏁 Script executed:

cat -n ./src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java | head -20

Repository: D-ingle/backend

Length of output: 806


🏁 Script executed:

rg "findNearestPoliceDistanceMeter" --type java -A 5 -B 5

Repository: D-ingle/backend

Length of output: 2691


🏁 Script executed:

cat -n ./src/main/java/com/example/Dingle/property/service/openAI/SafetyExplanationService.java | grep -A 30 "findNearestPoliceDistanceMeter" | head -40

Repository: D-ingle/backend

Length of output: 1712


🏁 Script executed:

cat -n ./src/main/java/com/example/Dingle/property/service/openAI/SafetyExplanationService.java | sed -n '1,80p'

Repository: D-ingle/backend

Length of output: 4196


경찰서 최근접 거리 쿼리에서 빈 결과 처리 누락으로 인한 NoResultException 발생 위험

현재 쿼리 구조에서 police_office 테이블이 비어있거나 모든 행의 geom_5179가 NULL인 경우, WHERE 절과 LIMIT 1로 인해 0건의 행이 반환되고, 72번 줄의 getSingleResult()에서 NoResultException이 발생합니다. 현재의 COALESCE는 ST_Distance의 NULL 값만 처리하며, 결과 행 자체가 없는 경우는 처리하지 못합니다.

제안된 수정처럼 COALESCE를 서브쿼리 전체를 감싸도록 변경하여, 0건 결과인 경우도 기본값(999999)으로 반환되도록 해야 합니다:

수정 예시
         String sql = """
         WITH target AS (
           SELECT ST_Transform(ST_SetSRID(ST_MakePoint(?1, ?2), 4326), 5179) AS g
         )
-        SELECT COALESCE(
-          ST_Distance(p.geom_5179, target.g),
-          999999
-        )
-        FROM police_office p, target
-        WHERE p.geom_5179 IS NOT NULL
-        ORDER BY p.geom_5179 <-> target.g
-        LIMIT 1
+        SELECT COALESCE((
+          SELECT ST_Distance(p.geom_5179, target.g)
+          FROM police_office p, target
+          WHERE p.geom_5179 IS NOT NULL
+          ORDER BY p.geom_5179 <-> target.g
+          LIMIT 1
+        ), 999999)
     """;

police_office 테이블 또는 geom_5179가 전부 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.

Suggested change
WITH target AS (
SELECT ST_Transform(ST_SetSRID(ST_MakePoint(?1, ?2), 4326), 5179) AS g
)
SELECT COALESCE(
ST_Distance(p.geom_5179, target.g),
999999
)
FROM police_office p, target
WHERE p.geom_5179 IS NOT NULL
ORDER BY p.geom_5179 <-> target.g
LIMIT 1
""";
WITH target AS (
SELECT ST_Transform(ST_SetSRID(ST_MakePoint(?1, ?2), 4326), 5179) AS g
)
SELECT COALESCE((
SELECT ST_Distance(p.geom_5179, target.g)
FROM police_office p, target
WHERE p.geom_5179 IS NOT NULL
ORDER BY p.geom_5179 <-> target.g
LIMIT 1
), 999999)
""";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/example/Dingle/safety/repository/HomeSafetyRepository.java`
around lines 56 - 67, The query can return zero rows (empty police_office or all
geom_5179 NULL), causing getSingleResult() in HomeSafetyRepository to throw
NoResultException; change the SQL so the COALESCE wraps the entire subquery
result (e.g. SELECT COALESCE((SELECT ... FROM police_office p, target WHERE
p.geom_5179 IS NOT NULL ORDER BY p.geom_5179 <-> target.g LIMIT 1), 999999) AS
distance) so the call in the repository method (the code that calls
getSingleResult() for the nearest police distance) always returns a numeric
default when no row exists; alternatively adjust the repository method to use
getResultList() and return 999999 when the list is empty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] 안전 점수 측정 최적화

1 participant