데이터베이스에 숫자, 문자열이 아닌 점, 선, 면 등의 정보를 담아야 할 때, 주로 공간 데이터를 사용한다.
본 포스팅에서는 Spring + JPA 환경에서 PostgreSQL의 extension인 PostGIS를 통해 공간 데이터를 다루는 방법을 알아보겠다.
PostGIS의 도입 배경과 설치 과정은 아래 글을 참고하자.
2023.01.11 - [database] - [Postgresql] PostGIS 설치 - MySQL이 아닌 PostgreSQL을 사용하는 이유
라이브러리 추가
PostgreSQL 라이브러리와 하이버네이트가 공간 데이터를 다루기 위한 라이브러리 두 가지를 추가하자.
// postgres
implementation 'org.postgresql:postgresql'
// hibernate-spatial
implementation 'org.hibernate:hibernate-spatial:5.6.11.Final'
라이브러리를 추가하고 Point 타입을 사용하려 하면 여러 라이브러리의 Point 타입이 뜰 때가 있는데,
여기서는 jts.geom 라이브러리를 사용하니 참고하자.
jts는 공간 관련 기능을 제공하는 Java의 라이브러리이다. hibernate-spatial 라이브러리를 추가하면 import할 수 있다.
import org.locationtech.jts.geom.*;
yaml 파일 설정
기본 설정 파일은 다음과 같다.
특히 jpa.database-platform 부분에 Postgis 관련 dialect를 꼭 추가해주자.
spring:
datasource:
url: [서버url]
username: [아이디]
password: [비밀번호]
driver-class-name: org.postgresql.Driver
jpa:
database: postgresql
database-platform: org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect
공간 데이터 생성
이제 공간 데이터를 마음껏 다뤄볼 수 있다.
먼저 공간 데이터는 GeometryFactory를 통해 생성할 수 있다.
Point 생성
Point는 점을 나타내는 공간 데이터 타입이다.
간단하게 위도, 경도 좌표를 받아 Point 객체를 생성하는 함수를 만들었다.
private Point getPoint(double latitude, double longitude) {
GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);
Point point = geometryFactory.createPoint(new Coordinate(latitude, longitude));
return point;
}
new GeometryFactory()의 두 번째 인자인 4326은 SRID 값이다.
SRID는 좌표계를 나타내는 값으로, 자주 사용하는 값은 0(좌표평면)과 4326(위도-경도 좌표계)이다.
나는 위도-경도 좌표계를 사용하기 때문에 4326으로 지정했다.
PostGIS와 MySQL의 가장 큰 차이점은 바로 4326 좌표계에서도 다양한 함수를 사용할 수 있다는 것이다.
(MySQL은 4326으로 지정해도 결국 좌표평면 좌표계 기준으로 공간 데이터를 다룬다)
LineString 생성
LineString은 선분을 나타내는 공간 데이터 타입이다.
여러 점들을 한 번에 인자로 보내기 위해 Dto를 하나 만들었다.
@Data
public class CoordinateDto {
private double latitude;
private double longitude;
}
다음은 여러 좌표들을 담고 있는 list를 통해 LineString을 반환하는 함수다.
private LineString getLineString(List<CoordinateDto> coordinateDtos) {
GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);
Coordinate[] coordinates = new Coordinate[coordinateDtos.size()];
for (int i = 0; i < coordinateDtos.size(); i++) {
coordinates[i] = new Coordinate(coordinateDtos.get(i).getLatitude(), coordinateDtos.get(i).getLongitude());
}
LineString lineString = geometryFactory.createLineString(coordinates);
return lineString;
}
Point와 크게 다른 점은 없다. geometryFactory.createLineString 함수에 여러 점들의 집합인 Coordinate[]를 인자로 넣어주면 된다.
이 밖에 GeometryFactory는 createPolygon(도형) 등 다양한 공간 데이터를 생성할 수 있다.
JPA로 공간 데이터 저장
다음과 같이 공간 데이터를 필드로 갖는 엔티티를 만들어보자.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Space {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(columnDefinition = "geometry(Point, 4326)")
private Point position;
@Column(columnDefinition = "geometry(LineString, 4326)")
private LineString route;
}
+) DDL을 날릴 때, 4326 좌표로 저장하기 위해 columnDefinition 옵션을 넣어줬다.
이제 ddl-auto: create 등으로 DDL을 쏴보면 다음과 같이 쿼리가 나간다.
create table space (
id bigserial not null,
position geometry(Point, 4326),
route geometry(LineString, 4326),
primary key (id)
)
실제로 공간 데이터에 값을 넣고 값을 조회해보면 요상한 0101010010110 같은 이진수로 되어 있는데 이는 공간 데이터를 기본적으로 이진수로 저장하기 때문이다.
다만, DBeaver와 같은 GUI 툴을 사용하면 아래 예시처럼 데이터 값을 이쁘게 공간 데이터 형식으로 보여준다.
공간 연산 함수 소개
jts는 공간 데이터를 생성하는 것 뿐만 아니라 다양한 공간 연산 함수를 제공한다.
lineString.getNumPoints() // 점의 개수 조회
lineString.getPointN(i) // i번째 점 조회
lineString1.intersection(lineString2) // 두 선분의 교차점 조회
...
이 밖에 PostGIS에서 제공하는 대부분의 함수들을 Java 문법에 맞게 사용할 수 있기 때문에 여러가지 만져보면서 공간 데이터를 자유롭게 다루길 추천한다.
참고자료
https://velog.io/@_koiil/Spring-boot-JPA-Hibernate-Postgres-연동하기
'java > spring' 카테고리의 다른 글
[Spring] @ControllerAdvice, 특정 예외 발생 시 404에러가 발생하는 이슈 (0) | 2023.06.21 |
---|---|
[Spring] Spring AOP와 실무 응용 (0) | 2023.01.12 |
[Spring] Mockito when으로 repository save 리턴받기 (0) | 2022.11.21 |
[Spring] AWS S3 압축 파일 풀어서 업로드하기 - TransferManager (0) | 2022.08.05 |
[Spring] AWS S3 파일 압축해서 다운로드 - 여러가지 방법 비교분석 (2) | 2022.08.05 |