Hibernate Spatial
원본 URL: https://www.baeldung.com/hibernate-spatial
1. 개요 (Overview)
이 튜토리얼에서는 Hibernate Spatial에 대해 알아본다. 이 라이브러리는 지리 공간 데이터(geographic spatial data)를 다루기 위한 Hibernate ORM의 확장 기능이다.
먼저, Hibernate Spatial의 기능과 설정 방법을 살펴본다. 그런 다음, 데이터베이스에 위치 정보를 저장하고 공간 쿼리(spatial query)를 실행하는 간단한 예제를 만들어 본다.
2. Hibernate Spatial이란 무엇인가? (What Is Hibernate Spatial?)
Hibernate Spatial은 표준화된 방식으로 지리 공간 데이터를 관리하고 쿼리할 수 있게 해주는 라이브러리다. Java 애플리케이션에서 위치 기반 데이터를 처리해야 할 때 유용하다.
이 라이브러리는 Open Geospatial Consortium (OGC)에서 정의한 표준을 준수한다. OGC는 지리 공간 데이터 처리를 위한 잘 정의된 데이터 타입과 기능 집합을 명시한다. 예를 들어, Point, LineString, Polygon과 같은 지오메트리(geometry) 타입과 intersects, within, distance와 같은 공간 관계 함수들이 있다.
Hibernate Spatial은 이러한 표준을 구현하여, 우리가 사용하는 데이터베이스가 무엇이든 일관된 API로 공간 데이터를 다룰 수 있게 해준다.
아래 다이어그램은 Hibernate Spatial이 애플리케이션, Hibernate Core, 그리고 공간 데이터베이스 사이에서 어떻게 작동하는지 보여준다.
3. 설정 (Setup)
Hibernate Spatial을 사용하려면 몇 가지 설정이 필요하다.
3.1. 의존성 추가 (Dependencies)
먼저, 프로젝트에 hibernate-spatial 의존성을 추가해야 한다.
또한, JTS (Java Topology Suite) 라이브러리도 필요한데, 이는 hibernate-spatial의 전이 의존성(transitive dependency)으로 포함되므로 별도로 추가할 필요는 없다. JTS는 Point, Polygon과 같은 공간 데이터 타입을 Java 코드로 표현하는 데 사용된다.
마지막으로, 사용하는 공간 데이터베이스에 맞는 JDBC 드라이버가 필요하다. 예를 들어 PostgreSQL/PostGIS를 사용한다면 PostGIS JDBC 드라이버가 필요하다.
build.gradle 파일에 다음 의존성을 추가한다:
dependencies { // Hibernate Spatial 의존성 implementation 'org.hibernate.orm:hibernate-spatial:6.5.2.Final' // 사용하는 DB에 맞는 드라이버 (예: MySQL) runtimeOnly 'com.mysql:mysql-connector-j' // 만약 PostGIS를 사용한다면 아래 의존성을 추가한다. // implementation 'net.postgis:postgis-jdbc:2.5.1' } |
3.2. 데이터베이스 설정 (Database Configuration)
Hibernate Spatial은 Oracle, MySQL, PostgreSQL (PostGIS 확장 기능 포함), MS SQL Server 등 다양한 데이터베이스를 지원한다.
이 예제에서는 MySQL을 사용한다고 가정한다. MySQL 8.0 이상은 공간 데이터 타입을 네이티브하게 지원하므로 별도의 확장 기능 설치 없이 사용할 수 있다.
application.yml 파일에서 Hibernate가 올바른 SQL 방언(dialect)을 사용하도록 설정해야 한다.
spring: jpa: database-platform: org.hibernate.dialect.MySQLSpatialDialect hibernate: ddl-auto: update datasource: url: jdbc:mysql://localhost:3306/spatial_db username: user password: password |
MySQLSpatialDialect를 사용하면 Hibernate는 MySQL의 공간 함수에 맞는 SQL 쿼리를 생성할 수 있다.
4. 공간 데이터 매핑 및 저장(Mapping and Saving Spatial Data)
4.1. 지오메트리 타입 (Geometry Types)
JTS는 OGC 표준에 따른 다양한 지오메트리 타입을 클래스로 제공한다. 주요 타입은 다음과 같다.
- `Point`: 단일 지점 (예: 위도, 경도 좌표)
- `LineString`: 두 개 이상의 점으로 구성된 선 (예: 도로, 경로)
- `Polygon`: 닫힌 `LineString`으로 정의된 영역 (예: 도시 경계, 공원 구역)
- `MultiPoint`, `MultiLineString`, `MultiPolygon`: 여러 개의 동일한 타입 지오메트리 집합
- `Geometry`: 모든 지오메트리 타입의 상위 클래스
4.2. 엔티티 매핑 (Entity Mapping)
이제 엔티티 클래스에 공간 데이터를 매핑해 보자. 다양한 지오메트리 타입을 저장할 수 있도록 `Geometry` 타입 필드를 가진 `Location` 엔티티를 만든다.
import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import org.locationtech.jts.geom.Geometry; @Entity public class Location { @Id @GeneratedValue private Long id; private String name; // 'geometry' 컬럼에 다양한 공간 데이터를 저장한다. // 데이터베이스는 이 컬럼을 GEOMETRY 타입으로 처리한다. private Geometry geometry; // Constructors, Getters and Setters public Location() {} public Location(String name, Geometry geometry) { this.name = name; this.geometry = geometry; } // ... getters and setters } |
4.3. 데이터 저장 예제 (Data Saving Example)
다양한 타입의 지오메트리 객체를 생성하고 저장하려면 GeometryFactory를 사용한다.
import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; import org.springframework.stereotype.Service; @Service public class LocationService { private final LocationRepository locationRepository; private final GeometryFactory geometryFactory = new GeometryFactory(); public LocationService(LocationRepository locationRepository) { this.locationRepository = locationRepository; } public void addLocations() { // Point 생성 및 저장 Point point = geometryFactory.createPoint(new Coordinate(1, 1)); locationRepository.save(new Location("Point 1", point)); // LineString 생성 및 저장 LineString line = geometryFactory.createLineString(new Coordinate[]{ new Coordinate(1, 1), new Coordinate(2, 2), new Coordinate(3, 3) }); locationRepository.save(new Location("Line 1", line)); // Polygon 생성 및 저장 (사각형) Polygon polygon = geometryFactory.createPolygon(new Coordinate[]{ new Coordinate(0, 0), new Coordinate(0, 5), new Coordinate(5, 5), new Coordinate(5, 0), new Coordinate(0, 0) // 닫힌 도형을 위해 시작점으로 돌아옴 }); locationRepository.save(new Location("Polygon 1", polygon)); } } |
5. 공간 쿼리 (Spatial Queries)
Hibernate Spatial은 JPQL에서 다양한 공간 함수를 지원하여 데이터베이스에 강력한 쿼리를 실행할 수 있게 한다.
5.1. Repository 쿼리 정의 (Defining Repository Queries)
Spring Data JPA Repository를 사용하여 쿼리를 정의해 보자.
import org.locationtech.jts.geom.Geometry; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface LocationRepository extends JpaRepository<Location, Long> { // within 쿼리: 특정 Geometry 내에 완전히 포함되는 Location을 찾는다. @Query("SELECT l FROM Location l WHERE within(l.geometry, :filter) = true") List<Location> findWithin(@Param("filter") Geometry filter); // touches 쿼리: 특정 Geometry와 경계가 닿는 Location을 찾는다. @Query("SELECT l FROM Location l WHERE touches(l.geometry, :filter) = true") List<Location> findTouches(@Param("filter") Geometry filter); // dwithin 쿼리: 특정 Geometry로부터 일정 거리 내에 있는 Location을 찾는다. @Query("SELECT l FROM Location l WHERE dwithin(l.geometry, :filter, :distance) = true") List<Location> findDWithin(@Param("filter") Geometry filter, @Param("distance") double distance); // intersects 쿼리: 특정 Geometry와 교차하는(겹치는) Location을 찾는다. @Query("SELECT l FROM Location l WHERE intersects(l.geometry, :filter) = true") List<Location> findIntersects(@Param("filter") Geometry filter); // distance 함수: 두 Geometry 간의 거리를 계산한다. (이 예제는 모든 Location과의 거리를 계산) @Query("SELECT distance(l.geometry, :filter) FROM Location l") List<Double> findDistances(@Param("filter") Geometry filter); } |
5.2. 쿼리 실행 예제 (Query Execution Example)
위에서 정의한 Repository 메서드를 사용하는 서비스 예제이다.
@Service public class SpatialQueryService { private final LocationRepository locationRepository; private final GeometryFactory geometryFactory = new GeometryFactory(); public SpatialQueryService(LocationRepository locationRepository) { this.locationRepository = locationRepository; } public void runQueries() { // 'within' 쿼리 테스트용 필터 Polygon Polygon filterPolygon = geometryFactory.createPolygon(new Coordinate[]{ new Coordinate(0, 0), new Coordinate(0, 10), new Coordinate(10, 10), new Coordinate(10, 0), new Coordinate(0, 0) }); List<Location> withinLocations = locationRepository.findWithin(filterPolygon); System.out.println("Within locations: " + withinLocations.size()); // 예상: 3 // 'touches' 쿼리 테스트용 필터 Point Point filterPoint = geometryFactory.createPoint(new Coordinate(0, 0)); List<Location> touchesLocations = locationRepository.findTouches(filterPoint); System.out.println("Touches locations: " + touchesLocations.size()); // 예상: 1 (Polygon 1) // 'dwithin' 쿼리 테스트 Point centerPoint = geometryFactory.createPoint(new Coordinate(0, 0)); List<Location> dwithinLocations = locationRepository.findDWithin(centerPoint, 2.0); System.out.println("DWithin (distance < 2) locations: " + dwithinLocations.size()); // 예상: 2 (Point 1, Polygon 1) } } |
6. 결론 (Conclusion)
Hibernate Spatial은 Java 애플리케이션에서 관계형 데이터베이스를 사용하여 공간 데이터를 처리하는 강력하고 표준화된 방법을 제공한다. Hibernate ORM과 완벽하게 통합되므로, 개발자는 익숙한 JPA와 JPQL을 사용하면서 위치 기반 기능을 쉽게 구현할 수 있다. JTS 라이브러리를 통해 다양한 지오메트리 타입을 객체로 다룰 수 있으며, within, touches, dwithin 등 OGC 표준 공간 함수를 활용하여 복잡한 공간 관계를 손쉽게 쿼리할 수 있다.