의존성을 추가할 때, 반드시 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 타입으로 저장될 수 있도록 한다.
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));
}
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