💬 Project
[이길어때] 위치 정보 관리를 위한 PostGIS 적용기
이길어때 라는 프로젝트를 진행을 하고 있습니다.
이 프로젝트의 주요 요구사항으로는 내가 갔던 여행의 코스들을 기록하는 것이 주 목적인데요.
결론적으로 이러한 위치 정보들을 DB에 잘 저장하고 관리할 수 있는 방향으로 할 수 있도록 DB를
postgreSQL
로 선정하고, postGIS
라는 extension
을 적용하였습니다.postGIS
를 스프링부트 프로젝트에 적용하고 어떻게 그 기능들을 활용할 수 있을지 굉장히 막막했는데요, 오늘은 프로젝트의 해당 스택을 적용하면서 생겼던 어려움들이 또 다시 일어나는 것을 방지하기 위해! 글로써 과정들을 남겨보도록 하겠습니다.Why postGIS
위에서도 잠깐 언급하였듯,
postGIS
는 postgreSQL
의 확장 기능으로 공간 데이터를 효율적으로 저장 및 관리할 수 있도록 다양한 공간함수와 같은 편의성 있는 기능을 제공합니다.지도 상의 특정 위치를 쿼리하거나, 다양한 지리적 형태와 패턴을 분석하는데에 복잡한 쿼리가 필요없이 간단하게 동작을 수행하도록 도와줍니다.
이러한 장점을 활용하면 복잡한 네이티브 쿼리 사용을 위해
MyBatis
와 같은 기술 스택을 고민해야 했던 문제를 말끔히 해결할 수 있었습니다.특히 저희 팀원들 중
MyBatis
에 대한 경험이 있는 사람이 저 밖에 없었기 때문에 간결한 쿼리를 작성하면서 공간데이터를 다룰 수 있다는 것은 아주 강력한 장점이었습니다.말로만 주저리 주저리 하는 것 보단 예시를 들어서 보여드리는게 좋겠죠?!
저희는 여행 경로들을 보여주는 앱이기 때문에, 지도에서 중앙 지점(좌표) 주변 반경에 있는 위치 데이터를 불러와야하는 일이 잦습니다.
이러한 기능 구현을 위해 특정 반경 내에 있는 모든 점을 찾는 쿼리를 작성해보도록 하겠습니다.
먼저,
postGIS
없이 작성했을 때 입니다.SELECT *, (6371 * acos( cos(radians(latitude)) * cos(radians(point.latitude)) * cos(radians(point.longitude) - radians(longitude)) + sin(radians(latitude)) * sin(radians(point.latitude)) )) AS distance FROM point HAVING distance < radius ORDER BY distance;
이 쿼리는 기준이 되는 좌표(latitude, longitude) 와 검색 반경(radius) 가 주어졌을 때, 지구의 반경(6371km)과 하버사인 공식을 활용해서 특정 거리로 부터의 거리를 계산해서 distance 로 표현하고, 이 distance에 따라 반경 안에 있는 point를 검색하는 쿼리입니다.
이렇게 작성을 매번 하게되면 이 쿼리를 본 다른 사람이 이 쿼리의 작성의도를 알기도 쉽지 않을 뿐더러 가독성이 굉장히 떨어지므로 유지보수성이 굉장히 떨어집니다.
이번에는
postGIS
를 활용해서 쿼리를 작성했을 때 입니다.SELECT * FROM point WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint(latitude, longitude), 4326), radius);
이렇게 같은 기능을 하는 함수도
postGIS
가 제공하는 여러 공간함수를 사용해서 간결하게 나타낼 수 있습니다.이러한 장점들을 활용해보고자
postgreSQL
과 postGIS
를 선정하게 되었습니다.SpringBoot 프로젝트에 postGIS 연동하기
프로젝트 정보
- java : 21
- spring-boot: 3.2.0
- postgreSQL: 16.1
- postGIS: 3.4.1
Docker로 postgreSQL 실행하기
docker run --name container-name -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres
Docker
등은 기본적으로 설치가 되어있다고 가정을 하겠습니다.터미널을 켜고, 이 명령어를 통해 postgreSQL 의 공식 이미지를 실행시킵니다.
이때 컨테이너의 이름은
container-name
루트 사용자의 비밀번호는 password
로 설정됩니다(이 부분은 개인 프로젝트에 맞춰 변경하시면 됩니다).그리고 호스트 머신의 5432 포트를 컨테이너의 5432 포트에 연결합니다. (postgreSQL 의 기본 포트번호가 5432 번이에요!)
docker exec -it container-name bash
도커 컨테이너의 정상 동작을 확인했다면, 해당 컨테이너를 실행시키고, 위 명령어를 통해 실행중인 컨테이너 환경에 접속합니다.
apt-get update apt-get install postgis postgresql-16-postgis-3
이 명령어를 입력해서
postgreSQL
을 최신 버전으로 업데이트하고, postGIS
를 설치합니다.psql -U postgres
이후 위 명령어를 통해 postgres 사용자(root)로 데이터베이스에 접속합니다.
이 때 위에서 설정한 password로 로그인 하는 과정이 있습니다.
CREATE DATABASE "db-name";
마지막으로
postgreSQL
안에 db-name
이라는 이름의 데이터베이스 스키마를 생성합니다.CREATE EXTENSION postgis;
마지막으로 Db에 Postgis 확장기능을 설치합니다.
이로써 저희는 DB 구성은 완료하였습니다!
SpringBoot와 DB 연동하기
먼저
build.gradle
파일 안에 관련 의존성을 추가해줍니다.dependencies { runtimeOnly 'org.postgresql:postgresql' implementation 'org.postgresql:postgresql' implementation 'org.hibernate:hibernate-core:6.4.0.Final' implementation 'org.hibernate:hibernate-spatial:6.4.0.Final' }
의존성 추가가 완료되었으면 resources 파일 하위에
application.yml
을 생성하고 관련 옵션을 추가합니다.spring: jpa: database: postgresql database-platform: org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect datasource: url: {DB주소} username: postgres password: password driver-class-name: org.postgresql.Driver
이렇게 설정을 해놓고 컨테이너를 실행 후 SpringBootApplication을 실행합니다.
정상적으로 동작한다면 성공입니다!
SpringBoot로 postGIS 활용해보기
spring-boot
환경에서 위치정보를 저장하려면, jts.geom
패키지에서 제공하는 geometry
객체를 활용하면 됩니다.예시로 Entity 를 작성해보겠습니다. (
lombok
을 함께 사용했습니다)@Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Spot { @Column(columnDefinition = "geometry(Point,4326)") private Point location; //그 외 속성들... }
이
Spot
엔티티가 가지고 있는 속성 중 하나인 Point
는 단일 좌표를 데이터베이스에 저장할 수 있는 속성입니다.hibernate
의 ddl-auto
옵션을 조절한 후 서버를 실행합니다.이제 데이터베이스에 해당 컬럼이 잘 생성이 되었는지 확인을 해보겠습니다.
잘 생성이 됨을 확인할 수 있었습니다!
postGIS 활용해보기
그러면
spring-data-JPA
로 연동을 대략적으로 확인했기 때문에, postGIS의 동작을 확인해보도록 하겠습니다.편의를 위해
DataGrip
에서 공간함수가 잘 작동하는지 알아보고, 스프링 부트에서 같은 방식으로 네이티브 쿼리를 사용하면 공간함수를 사용할 수 있겠죠?!SELECT ST_AsText( ST_MakeLine( ST_SetSRID(ST_MakePoint(126.9780, 37.5665), 4326), -- 서울의 좌표 ST_SetSRID(ST_MakePoint(129.0757, 35.1796), 4326) -- 부산의 좌표 ) ) AS line_seoul_to_busan;
위 쿼리는 서울에서 부산까지의 직선을 만들어 생성하는 쿼리입니다.
실행결과를 확인해보니 잘 동작함을 확인할 수 있었습니다.
다음 글에는 제대로 공간함수들을 활용해서 비즈니스 로직을 작성하는 법에 대해서도 추가하도록 하겠습니다 😊