[Querydsl] MySQL 공간 데이터(Point)의 반경 검색 (ST_CONTAINS)

2024. 7. 3. 00:42·Querydsl

이번 포스팅에선 숙소 예약 프로젝트를 진행하며 Querydsl로 공간 데이터를 조건으로하는 표현식(Expression)에 대해 알아보겠습니다.

기본 환경 및 의존성

기본 환경과 의존성은 다음과 같습니다.

- Java 17

- Spring Boot 3.3.0

- DB: MySQL 8.0

- ORM: querydsl 5.1.0

- 공간 데이터 의존성: hibernate-spatial 6.5.2

Entity

@Entity
public class Stay {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "STAY_ID")
    private Long id;

    @Column(name = "POINT", columnDefinition = "POINT SRID 4326", nullable = false)
    private Point point;

    // 그 외 필드들...
}
  • MySQL에선 공간 데이터를 Point 라는 타입으로 지원합니다.
  • ORM에서 공간 데이터임을 알려주기 위해 `hibernate-spatial` 에서 지원하는 `Point 타입`으로 `위도`와 `경도`를 표현해야하며, 이를 위해 데이터베이스의 컬럼 속성으로 `SRID 4326` 을 지정해야 합니다. (설정하지 않으면 SRID 값은 0 이 되며 이는 `위도`, `경도`가 아닌 `직교좌표`로 표현됩니다.)
  • 만약 처음부터 `SRID 4326` 으로 설정되지 않았다면 컬럼을 수정할 수 없으니 주의해야 합니다.
  • (포스팅과 상관없는 내용이지만, 추후 공간 인덱스(R-Tree)를 적용하기 위해 `Not Null` 조건이 되야하므로 `nullable=false` 도 추가했습니다.)

공간 데이터를 검색하는 방법

MySQL에서 공간 데이터를 검색하는 방법은 MySQL에서 지원하는 공간 함수를 이용하는 것입니다.

이번 포스팅에서 사용하는 공간 함수는 다음과 같습니다.

  • `ST_CONTAINS(g1, g2)` : g1(지오메트리)가 g2(지오메트리)를 완전히 포함하면 `true`, 그렇지 않으면 `false` 를 반환
  • `ST_BUFFER(중심점, 반지름(단위: 미터))` : 전달받은 `중심점(지오메트리)`을 기준으로 반지름 크기(단위: 미터)만큼의 `원형`을 그리는 함수
  • `ST_GeomFromText(WKT)` : 텍스트 문자열(`WKT`)로 지오메트리 데이터를 생성하는 함수
    • `WKT(Well-Known-Text)` 포맷 : `POINT(35.000000, 127.000000)` 과 같은 지오메트리 형식으로 작성된 포맷을 말합니다.

 

이 함수들을 이용해 SQL 의 WHERE 조건문으로 다음과 같이 표현할 수 있습니다.

SQL WHERE 문

WHERE ST_CONTAINS(ST_BUFFER(ST_GeomFromText('POINT(35, 127)', 4326), 5000), `Point_타입_컬럼_이름`)

조건문의 가장 내부부터 의미를 하나씩 보면,

1. `ST_GeomFromText('POINT(35, 127)', 4326)` : `SRID 4326` 의 위도와 경도를 갖는 `중심점`을 생성합니다.

2. `ST_BUFFER(중심점, 5000)` : 위의 좌표를 중심으로 반지름이 5km (=5000m)인 `원형`을 생성합니다.

3. `ST_CONTAINS(원형, Point_타입_컬럼_이름)` : `생성된 원형`에 `완전히 포함`되는 `Point 타입의 컬럼`의 레코드들을 조건으로 찾습니다.

 

이제 SQL 문을 만들기 위해 Querydsl 로 어떻게 작성해야 할까요?

Querydsl 표현식 만들기

먼저, Querydsl 로 작성한 전체적인 쿼리는 다음과 같습니다.

List<Stay> stays = jpaQueryFactory
                .select(stay)
                .from(stay)
                .where(
                  // 조건문
                )
                .fetch();

querydsl 에 where 조건문으로 사용하기 위해선 `BooleanExpression` 을 사용할 수 있습니다.

원하는 공간 함수 표현을 `BooleanExpression` 으로 표현하는게 먼저겠죠?

저는 다음과 같이 `BooleanExpression` 을 반환하는 함수를 만들었습니다.

private BooleanTemplate getContainsBooleanExpression(Double latitude, Double longitude, Integer radius) {
    String target = "Point(%f %f)".formatted(latitude, longitude);
    String geoFunction = "ST_CONTAINS(ST_BUFFER(ST_GeomFromText('%s', 4326), {0}), point)";
    String expression = String.format(geoFunction, target);

    return Expressions.booleanTemplate(expression, radius);
}

이 함수에서 눈여겨 봐야할 곳은, 중심점을 만드는 검색 대상의 좌표(`target`)입니다.

 

SQL 에선 Point(latitude, longitude) 와 같이 위도와 경도 사이에 쉼표(,)가 들어가지만, querydsl 표현식에선 사용하지 않습니다.

 

추가로, 변수 `geoFunction` 부분에서 {0} 부분에 들어가는건 `radius` 이며, `point`는 데이터베이스 테이블의 `Point 타입(SRID 4326)`으로 좌표 정보에 해당하는 컬럼 이름입니다.

 

이렇게 반환된 `BooleanExpression`을 where() 내부에 사용하시면 중심 좌표를 기준으로 `반지름 x 미터에 있는 숙소`를 찾을 수 있습니다.

 

공간 인덱스가 있다면 더욱 빠르게 찾을 수도 있으니 아래 사이트를 참고하시면 도움이 될 것 같습니다.

 

이번 포스팅은 여기서 마치겠습니다.

도움이 되셨길 바라며 읽어주셔서 감사합니다.

참고

테코블 포스팅 : 공간 데이터 개념부터 적용까지

'Querydsl' 카테고리의 다른 글

OpenFeign Querydsl 6 버전 도입과 QClass 생성 문제  (0) 2024.12.29
'Querydsl' 카테고리의 다른 글
  • OpenFeign Querydsl 6 버전 도입과 QClass 생성 문제
옐리yelly
옐리yelly
  • 옐리yelly
    개발 갤러리
    옐리yelly
  • 전체
    오늘
    어제
    • 모든 글 보기 (85)
      • Project (22)
      • Java (4)
      • Spring (8)
      • Kubernetes (6)
      • Docker (2)
      • JPA (3)
      • Querydsl (2)
      • MySQL (9)
      • ElasticSearch (7)
      • DevOps (4)
      • Message Broker (3)
      • Git & GitHub (2)
      • Svelte (1)
      • Python (8)
        • Python Distilled (4)
        • Anaconda (1)
        • Django (0)
        • pandas (3)
      • Algorithm (1)
      • Computer Science (0)
      • 내 생각 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    mybatis
    예약 시스템
    k8s
    docker
    데드락
    리팩토링
    argocd
    JPA
    devops
    pandas
    ncloud
    비사이드
    svelte
    gitops
    RabbitMQ
    blue-green 배포
    pymysql
    포텐데이
    Python
    nks
    커넥션 풀
    Message Broker
    성능 테스트
    elasticsearch
    OOP
    Project
    Spring
    MySQL
    querydsl
    프로젝트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
옐리yelly
[Querydsl] MySQL 공간 데이터(Point)의 반경 검색 (ST_CONTAINS)
상단으로

티스토리툴바