Spring JPA MySql 위치 정보(위도, 경도) 저장 및 Json으로 응답

하스레·2022년 7월 23일
1

build.gradle과 application.yml 설정

의존성을 추가할 때, 반드시 hibernate 버전을 확인하고 그에 맞게 추가해야한다.

// hibernate 버전 확인
System.out.println(org.hibernate.Version.getVersionString());

build.gradle의 dependencies에 다음 한 줄을 추가한다.

// 방금 확인한 버전을 넣어줌
implementation group: 'org.hibernate', name: 'hibernate-spatial', version: '5.6.9.Final'

application.yml파일의 jpa database-platform을 다음과 같이 변경한다.

database-platform: org.hibernate.spatial.dialect.mysql.MySQL56InnoDBSpatialDialect

엔티티에 위치정보 컬럼 추가

위치정보가 필요한 엔티티에 Point 타입의 geography 속성을 추가했다. 이때, org.locationtech.jts.geom.Point 패키지의 Point 타입을 사용해야 한다.

@Entity
@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Cafe {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;                    

    @Column(length = 20)
    private String cafeName;            

    @Column(nullable = false, columnDefinition = "GEOMETRY")
    private Point geography;
}

이때, 위와 같이 geography의 타입을 columnDefinition = "GEOMETRY"으로 설정하여 MySql에 저장시 MySql의 GEOMETRY 타입으로 저장될 수 있도록 한다.

MySql에 저장

import.sql을 통한 초기 데이터 삽입시엔 다음과 같은 sql문을 사용하여 위치데이터를 넣어준다.

INSERT INTO cafe(cafe_name, geography) VALUES ('카페명', POINT(127.011803, 38.478694));

JPA를 통해 MySql에 저장하도록, 컨트롤러와 서비스 등을 수정해보자.

컨트롤러 단에서 @ModelAttribute를 통해 다음과 같이 CafeParams를 받는다.

CafeParams

@Data
public class CafeParams {
    private String cafeName;

    private Double longitude;
    
    private Double latitude;
}

CafeController

@PostMapping(value="/cafes")
    public CafeDto.Response createCafe(@ModelAttribute CafeParams params) throws ParseException {
        return cafeService.createCafe(params);
    }

서비스 단에서 다음과 같이 WKT(Well-Known Text)를 MySql에 저장할 수 있는 실제 타입으로 변환해준다.

CafeService

public CafeDto.Response createCafe(CafeParams params) throws ParseException {
        
        // WKTReader를 통해 WKT -> 실제 타입으로 변환
        String pointWKT = String.format("POINT(%s %s)", params.getLongitude(), params.getLatitude());
        Point point = (Point) new WKTReader().read(pointWKT);

        Cafe cafe = Cafe.builder()
                .cafeName(params.getCafeName())
                .geography(point)
                .build();

        return new CafeDto.Response(cafeJpaRepository.save(cafe));
    }

CafeDto 사용 이유

DTO를 사용하지 않고, Cafe 객체를 바로 반환했더니 json으로 변환되는 과정에서 오류가 발생했다. 이는 다음과 같은 이유 때문이다.

Because Point has a getX() and a getY() method, the default serialized JSON for it is "point": { "x": 51.61100420, "y": -0.10213410 }, but deserializing will not work by default for that JSON text, because there are no setter methods. With JSON text "point": "51.61100420, -0.10213410", the default deserializer needs a constructor taking a String parameter, and Point doesn't have that. You need to provide custom (de)serializers.

따라서 여러가지 방안을 찾아보다, DTO를 사용하여 변환하는 방법이 가장 간단하다고 생각되어 다음과 같이 DTO를 생성하여 반환했다.

CafeDto

@Getter @Setter
public class CafeDto {

    @Getter @Setter
    public static class Response {
        private Long id;
        private String cafeName;
        private PointDto geography;

        public Response(Cafe entity) {
            this.id = entity.getId();
            this.cafeName = entity.getCafeName();
            this.geography = new PointDto(entity.getGeography());
        }
    }

    @Getter @NoArgsConstructor
    private static class PointDto {
        private Double longitude;	// 경도
        private Double latitude;	// 위도

        public PointDto(Point entity) {
            this.longitude = entity.getX();
            this.latitude = entity.getY();
        }
    }
}

참고
https://wooody92.github.io/project/JPA%EC%99%80-MySQL%EB%A1%9C-%EC%9C%84%EC%B9%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%8B%A4%EB%A3%A8%EA%B8%B0/
https://guswns1659.github.io/spring/Spring)-%EC%9C%84%EC%B9%98-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%8B%A4%EB%A3%A8%EA%B8%B0/
https://stackoverflow.com/questions/61712110/what-is-the-json-format-to-pass-point-data-type-values

profile
Software Developer

0개의 댓글