[SpringBoot] Kakao 주소 API 적용하기

Donggwon·2023년 6월 4일

JPA

목록 보기
1/1

개요

대학교 캡스톤디자인 중 배송 주소를 입력받고 데이터베이승에 저장하기 위해 외부 API를 찾아보던 중 Kakao 주소 API를 적용해보기로 하였다.

HTML

//address.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/submit/new" method="post" enctype="application/json">
    <input type="text" id="sample6_postcode" name="postcode" placeholder="우편번호">
    <input type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기"><br>
    <input type="text" id="sample6_address" name="postAddress" placeholder="주소"><br>
    <input type="text" id="sample6_detailAddress" name="detailAddress" placeholder="상세주소">
    <input type="text" id="sample6_extraAddress" name="extraAddress" placeholder="참고항목">
    <input type="submit" value="전송">
</form>


<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
    function sample6_execDaumPostcode() {
        new daum.Postcode({
            oncomplete: function (data) {
                // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                // 각 주소의 노출 규칙에 따라 주소를 조합한다.
                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                let addr = ''; // 주소 변수
                let extraAddr = ''; // 참고항목 변수

                //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
                if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
                    addr = data.roadAddress;
                } else { // 사용자가 지번 주소를 선택했을 경우(J)
                    addr = data.jibunAddress;
                }

                // 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
                if (data.userSelectedType === 'R') {
                    // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                    // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                    if (data.bname !== '' && /[||]$/g.test(data.bname)) {
                        extraAddr += data.bname;
                    }
                    // 건물명이 있고, 공동주택일 경우 추가한다.
                    if (data.buildingName !== '' && data.apartment === 'Y') {
                        extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                    }
                    // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                    if (extraAddr !== '') {
                        extraAddr = ' (' + extraAddr + ')';
                    }
                    // 조합된 참고항목을 해당 필드에 넣는다.
                    document.getElementById("sample6_extraAddress").value = extraAddr;

                } else {
                    document.getElementById("sample6_extraAddress").value = '';
                }

                // 우편번호와 주소 정보를 해당 필드에 넣는다.
                document.getElementById('sample6_postcode').value = data.zonecode;
                document.getElementById("sample6_address").value = addr;
                // 커서를 상세주소 필드로 이동한다.
                document.getElementById("sample6_detailAddress").focus();
            }
        }).open();
    }
</script>
</body>
</html>
  • <form> 태그를 사용해서 action 속성에 POST: /summit/new 로 입력된 값들을 서버에 전송합니다.

Entity

// address.java

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

import lombok.Setter;
import org.apache.commons.lang3.StringUtils;


@Entity
@Getter
@Setter
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "postcode")
    private String postcode;

    @Column(name = "post_address")
    private String postAddress;

    @Column(name = "detail_address")
    private String detailAddress;

    @Column(name = "extra_address")
    private String extraAddress;


    @Builder
    public static Address createAddress(
            final String postcode,
            final String postAddress,
            final String detailAddress,
            final String extraAddress
    ) {
        Address address = new Address();
        address.postcode = postcode;
        address.postAddress = postAddress;
        address.detailAddress = detailAddress;
        address.extraAddress = extraAddress;

        return address;
    }

}
  • 입력받은 값을 전달받아 저장하기 위해 위 코드와 같이 Entity를 작성합니다.

Controller


import com.example.PostcodeTutorial.domain.Address.application.AddressService;
import com.example.PostcodeTutorial.domain.Address.web.dto.CreateAddressRequest;
import com.example.PostcodeTutorial.domain.Address.web.dto.CreateAddressResponse;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
public class AddressController {

    private final AddressService addressService;

    public AddressController(AddressService addressService) {
        this.addressService = addressService;
    }


    @GetMapping("/address")
    public String showAddressSearchForm() {
        return "address";
    }

    @GetMapping("/submit")
    public String showSubmitForm() {
        return "submit";
    }

    @PostMapping("/submit/new")
    public String submitForm(@RequestBody @Valid CreateAddressRequest request) {
        Long id = addressService.join(request);
        return "redirect:/submit";
    }
}
  • Web에서 보낸 요청을 처리하기 위해 @PostMapping("/submit/new")으로 매핑합니다.

DTO

import jakarta.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.RequestParam;

public record CreateAddressRequest(
        @NotBlank String postcode,
        @NotBlank String postAddress,
        @NotBlank String detailAddress,
        String extraAddress,
        Long id
) { }
  • 요청받은 값을 DTO로 전달하기 위해서 위 코드를 선언합니다.
  • 도로명 주소가 아닌 지번주소를 입력할 때는 extraAddress가 공백으로 전달되기 때문에 @NotBlank 로 검증하지 않았습니다.

Repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface AddressRepository extends JpaRepository<Address, Long> {
}
  • Address 엔티티를 데이터베이스에서 생성, 조회, 업데이트, 삭제 등의 작업을 수행하기 위해 사용하기 위해 AddressRepository를 작성합니다.

Service

import com.example.PostcodeTutorial.domain.Address.persistence.Address;
import com.example.PostcodeTutorial.domain.Address.persistence.AddressRepository;
import com.example.PostcodeTutorial.domain.Address.web.dto.CreateAddressRequest;
import com.example.PostcodeTutorial.global.exception.service.PostNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Array;

import static com.example.PostcodeTutorial.global.exception.ErrorCode.NOT_FOUND_POST;

@Service
public class AddressService {
    private final AddressRepository addressRepository;


    public AddressService(AddressRepository addressRepository) {
        this.addressRepository = addressRepository;
    }

    @Transactional
    public Long join(final CreateAddressRequest request) {


        Address address = Address.builder()
                .postcode(request.postcode())
                .postAddress(request.postAddress())
                .detailAddress(request.detailAddress())
                .extraAddress(request.extraAddress())
                .build();

        addressRepository.save(address);
        return address.getId();
    }

}
  • Service에서 DTO를 전달받아 H2 Database에 저장하는 코드를 작성합니다.

문제 원인

서버를 실행하여 주소 값을 입력하여 보냈는데 [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/x-www-form-urlencoded;charset=UTF-8' is not supported] 해당 에러가 발생하였다.

  • 원인은 <form>에서 enctype="application/json"을 사용하여 JSON 형식으로 전송하고 있으나 실제로는 application/x-www-form-urlencoded를 사용하여 요청이 전송되어 문제가 발생했던 것이다.

문제 해결

  @PostMapping("/submit/new")
    public String submitForm(@ModelAttribute @Valid CreateAddressRequest request) {
        Long id = addressService.join(request);
        return "redirect:/submit";
    }
  1. 임시적인 방안이지만 application/x-www-form-urlencoded 으로 요청이 전송되기에 Controller 에서 CreateAddressRequest에 있는 @RequestBody 어노테이션을 @ModelAttribute 을 사용하였더니 해결되었다.

  2. JavaScript를 사용하여 AJAX 요청을 수행하면 JSON 형식의 요청을 처리할 수 있다고 한다. 해당 내용을 좀 더 공부하고 코드를 수정해보고자 한다 !


결과

  • HTML로 입력받는 사진

  • H2 Database에 저장된 것을 확인한 사진

참고

https://postcode.map.daum.net/guide

profile
I will definitely be a backend developer 🔥

0개의 댓글