[기획 & PM & BE] BE:OUR 프로젝트

[BE:OUR] '공간 등록 폼' 데이터 저장 API - 주소를 이용해서 경위도값 구하기(KakaoMapAPI): RestTemplate과 JSON 라이브러리

SemInDev 2025. 5. 19. 09:46

기능 흐름: 호스트가 자신의 공간 등록 폼을 제출하면 DB에 저장

- 도로명 주소를 입력받고 해당 주소의 유효성을 확인

  (프론트에서 행정안전부 주소 API 사용하여 구현 - https://jiyumi00.tistory.com/32)

- 호스트가 공간 등록 폼 제출 버튼을 누르면 주소를 바탕으로 경위도 값을 생성

  (카카오맵 API 사용 - https://developers.kakao.com/docs/latest/ko/local/dev-guide#address-coord)

  한 후 DB에 저장한다.

 

 

이번엔 카카오맵API를 활용하여 주소로 경위도 값을 구하는 KakaoMapService을 구현하는 것이 목표!

+추가로 hostId로 User 엔티티를 조회하는 UserService.getUserById(Long userId) 메소드를 구현할 것이다.

(space 객체에 담을 (host) user 객체를 조회하기 위해!)


 

1. KakaoMapService

application.yml 설정

kakao:
  api:
    key: ${MY_KAKAO_REST_API_KEY}

 

 

 

KakaoMapService

: getCoordinatesByAddress(String address) 메서드는 주소 문자열을 받아서, 위도(latitude)와 경도(longitude) 값을 담은 Coordinate를 반환하는 기능입니다.

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;
import org.json.JSONArray;
import org.json.JSONObject;

@Slf4j
@Service
@RequiredArgsConstructor
public class KakaoMapService {

    private final RestTemplate restTemplate = new RestTemplate();

    @Value("${kakao.api.key}")
    private String kakaoApiKey;

    public double[] getLatLng(String address) {
        Coordinate coordinate = getCoordinatesByAddress(address);
        return new double[]{coordinate.getLatitude(), coordinate.getLongitude()};
    }

    public Coordinate getCoordinatesByAddress(String address) {
        String url = "https://dapi.kakao.com/v2/local/search/address.json?query=" + address;

        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "KakaoAK " + kakaoApiKey);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                String.class
        );

        if (response.getStatusCode() != HttpStatus.OK) {
            throw new RuntimeException("카카오맵 API 호출 실패: " + response.getStatusCode());
        }

        JSONObject json = new JSONObject(response.getBody());
        JSONArray documents = json.getJSONArray("documents");

        if (documents.isEmpty()) {
            throw new RuntimeException("주소로 좌표를 찾을 수 없습니다: " + address);
        }

        JSONObject location = documents.getJSONObject(0);
        double latitude = location.getDouble("y");
        double longitude = location.getDouble("x");

        return new Coordinate(latitude, longitude);
    }

    @Getter
    @AllArgsConstructor
    public static class Coordinate {
        private double latitude;
        private double longitude;
    }
}

 

*Lombok - Slf4j(Simple Logging Facade for Java): log라는 이름의 로거(logger) 객체가 자동으로 생성(아래와 같이 사용 가능)

log.info("좌표 응답 결과: {}", response);
log.error("에러 발생", e);

 

*Lombok - @RequiredArgsConstructor: final이나 @NonNull이 붙은 필드를 가진 생성자를 자동으로 생성.

아래의 필드에 대해

@RequiredArgsConstructor
public class KakaoMapService {
    private final RestTemplate restTemplate;
    private final String kakaoApiKey;
}

 

자동으로 아래 생성자를 생성함.

public KakaoMapService(RestTemplate restTemplate, String kakaoApiKey) {
    this.restTemplate = restTemplate;
    this.kakaoApiKey = kakaoApiKey;
}

 

 

 

1.1. 카카오맵 API 요청 URL 생성: 주소를 카카오맵 REST API에 넘기기 위한 URL 생성

String url = "<https://dapi.kakao.com/v2/local/search/address.json?query=>" + address;

 

 

 

1.2. HTTP 요청 헤더 설정

HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoApiKey);
HttpEntity<String> entity = new HttpEntity<>(headers);

  • Authorization 해더에 카카오 API 키를 넣어야 함.
  • HttpEntity는 요청 본문과 헤더 정보를 담는 객체.

 

 

1.3. API 호출 (GET 요청 보내기)

ResponseEntity<String> response = restTemplate.exchange(
    url,
    HttpMethod.GET,
    entity,
    String.class
);

  • RestTemplate*을 사용해 HTTP GET 요청을 보냄.
  • 응답은 문자열 형태(JSON 텍스트)로 받아옴.
  • ResponseEntity*<String>은 HTTP 상태코드와 응답 본문을 함께 받는 객체.

 

1) HttpEntity: HttpHeaders와 body를 함께 묶어 전송하는 컨테이너 역할
HTTP 요청(request)을 만들 때 사용되는 Spring 클래스
- 요청의 본문(body)이나 헤더(headers)를 담고 전송할 때 필요

vs ResponseEntity: 응답 전체(본문 + 상태 코드 + 헤더) 담은 객체.

 

2) RestTemplate
- Spring에서 제공하는 HTTP 요청을 보내는 클라이언트 도구.
외부 API 서버(예: 카카오 API)와 통신할 때 사용.
- GET, POST, PUT, DELETE 같은 요청을 쉽게 보낼 수 있게 함

 : 내부적으로 HttpURLConnection 또는 HttpClient를 사용하지만, 그것보다 훨씬 간편하게 사용할 수 있게 도와주는 클래스이다.

 

 

 

1.4. 응답 코드 확인: 정상 응답(200)이 아니라면 예외 발생

if (response.getStatusCode() != HttpStatus.OK) {
    throw new RuntimeException("카카오맵 API 호출 실패: " + response.getStatusCode());
}

 

 

 

1.5. JSON 응답 파싱: JSON 라이브러리 사용

JSONObject json = new JSONObject(response.getBody());
JSONArray documents = json.getJSONArray("documents");

  • 응답 본문을 JSONObject로 파싱
  • "documents" 배열을 추출

 

JSON 라이브러리를 써야하는 이유

 

(예시 JSON 응답)

{
  "documents": [
    {
      "x": "127.123456",
      "y": "37.123456",
      "address_name": "서울시 강남구 ..."
    }
  ]
}

 

 

If 직접 문자열 파싱한다면..
직접 문자열을 split()하거나 정규식으로 파싱하면 매우 복잡하고 취약하다..

String json = "{...}";
String lat = json.split("y\\":")[1].split(",")[0];


- 유지보수가 어렵고
- JSON 구조가 바뀌면 바로 오류 발생
- 숫자/문자열 구분도 어려움
따라서, org.json 라이브러리의 JSONObject, JSONArray를 사용하는 것이 좋음!

 

 

If JSON라이브러리를 사용한다면..

JSONObject json = new JSONObject(response.getBody());
JSONArray documents = json.getJSONArray("documents");
JSONObject location = documents.getJSONObject(0);
double latitude = location.getDouble("y");
double longitude = location.getDouble("x");

 

*JSONObject: JSON 객체(중괄호 {}로 감싸진 구조)를 자바에서 다루기 위한 클래스.
*JSONArray: JSON 배열(대괄호 []로 감싸진 리스트 구조)을 자바에서 다루기 위한 클래스.

 

 

 

1.6. 주소 결과가 비었는지 확인

if (documents.isEmpty()) {
    throw new RuntimeException("주소로 좌표를 찾을 수 없습니다: " + address);
}

 

 

 

1.7. 첫 번째 주소 결과에서 위도/경도 추출: 첫 번째 결과를 꺼내서 y → 위도, x → 경도 값을 추출

JSONObject location = documents.getJSONObject(0);
double latitude = location.getDouble("y");
double longitude = location.getDouble("x");

 

 

 

1.8. Coordinate 객체로 반환: 좌표 값을 래핑해서 리턴

return new Coordinate(latitude, longitude);

 

 

 

 

*추후 필요에 따라 KakaoMapService에 대해 인터페이스 분리, 캐싱 등도 도입 예정.

 


2. UserService

: hostId로 User 엔티티를 조회하는 UserService.getUserById(Long userId) 메소드를 구현

package com.beour.user.service;

import com.beour.user.entity.User;
import com.beour.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public User getUserById(Long userId) {
        return userRepository.findById(userId)
            .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다. ID = " + userId));
    }
}

 

 

 

 


완성된 서비스 전체 흐름!

[사용자 입력 DTO]
   ↓
[User 조회] ← userService
   ↓
[주소 → 경위도 변환] ← kakaoMapService
   ↓
[Space 생성 및 저장]
   ↓
[Description  생성 및 저장]
[Tags  생성 및 저장]
[AvailableTimes  생성 및 저장]
[Images  생성 및 저장]
   ↓
[space.id 반환]