Hibernate Spatial은 지리 데이터 작업을 위한 표준 인터페이스를 제공한다 .
지리적 데이터에는 점, 선, 다각형 과 같은 엔터티의 표현이 포함됨 .
Hibernate Spatial은 기하학 모델로 JTS(Java Topology Suite)를 사용
Geometry 는 JTS의 모든 공간 유형에 대한 기본 유형.
ex) Point , Polygon 및 기타 유형과 같은 다른 유형이 Geometry 에서 확장됨을 의미합니다 .
Java의 Geometry 유형은 MySql 의 GEOMETRY 유형에도 해당됨
유형의 String 표현을 구문 분석하여 Geometry 인스턴스를 얻는다.
JTS에서 제공하는 유틸리티 클래스 WKTReader를 사용하여 텍스트를 Geometry 유형으로 변환할 수 있다 .
public Geometry wktToGeometry(String wellKnownText)
throws ParseException {
return new WKTReader().read(wellKnownText);
}
실제로 동작하는 것을 살펴보자
@Test
public void shouldConvertWktToGeometry() {
Geometry geometry = wktToGeometry("POINT (2 5)");
assertEquals("Point", geometry.getGeometryType());
assertTrue(geometry instanceof Point);
}
보다시피 메소드의 반환 유형이 read() 메소드 인 경우에도 실제 인스턴스는 Point 의 인스턴스임
Point는 두 개의 좌표로 표시
public void insertPoint(String point) {
PointEntity entity = new PointEntity();
entity.setPoint((Point) wktToGeometry(point));
session.persist(entity);
}
insertPoint() 메서드는 Point 의 텍스트(WKT) 표현을 받아 이를 Point 인스턴스 로 변환 하고 DB에 저장
Point 에서 toString() 을 호출하면 Point 의 WKT 표현이 반환
@Test
public void shouldInsertAndSelectPoints() {
PointEntity entity = new PointEntity();
entity.setPoint((Point) wktToGeometry("POINT (1 1)"));
session.persist(entity);
PointEntity fromDb = session
.find(PointEntity.class, entity.getId());
assertEquals("POINT (1 1)", fromDb.getPoint().toString());
assertTrue(geometry instanceof Point);
}
Geometry 클래스가 toString() 메서드를 재정의 하고 내부적으로 이전에 본 WKTReader 의 보완 클래스인 WKTWriter를 사용하기 때문
공간함수 활용
MySQL의 이러한 함수 중 하나는 한 Geometry가 다른 Geometry 내에 있는지 여부를 알려주는 ST_WITHIN()
주어진 반경 내의 모든 점을 찾아보자
원을 만드는 방법부터 보자
public Geometry createCircle(double x, double y, double radius) {
GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
shapeFactory.setNumPoints(32);
shapeFactory.setCentre(new Coordinate(x, y));
shapeFactory.setSize(radius * 2);
return shapeFactory.createCircle();
}
원은 setNumPoints() 메서드로 지정된 유한한 점 집합으로 표현된다 .
setSize() 메서드를 호출할때 *2를 하는 이유는
중심 주위에 원을 두 방향으로 그려야 하므로 반지름의 두 배가 된다.
이제 주어진 반경 내에서 포인트를 가져오는 방법을 보자
@Test
public void shouldSelectAllPointsWithinRadius() throws ParseException {
insertPoint("POINT (1 1)");
insertPoint("POINT (1 2)");
insertPoint("POINT (3 4)");
insertPoint("POINT (5 6)");
Query query = session.createQuery("select p from PointEntity p where
within(p.point, :circle) = true", PointEntity.class);
query.setParameter("circle", createCircle(0.0, 0.0, 5));
assertThat(query.getResultList().stream()
.map(p -> ((PointEntity) p).getPoint().toString()))
.containsOnly("POINT (1 1)", "POINT (1 2)");
}
Hibernate는 자신의 within() 함수를 MySql의 ST_WITHIN() 함수 에 매핑
within () 함수는 주어진 Geometry가 다른 Geometry 내에 완전히 있는 경우에만 true를 반환
따라서 원의 경계면에 있는 점(3 4)는 반환하지 않음
이번에는 DB에 point대신 polygon을 삽입하고 폴리곤에 인접한 폴리곤을 가져와보자
@Entity
public class PolygonEntity {
@Id
@GeneratedValue
private Long id;
private Polygon polygon;
// standard getters and setters
}
@Test
public void shouldSelectAdjacentPolygons() throws ParseException {
insertPolygon("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))");
insertPolygon("POLYGON ((3 0, 3 5, 8 5, 8 0, 3 0))");
insertPolygon("POLYGON ((2 2, 3 1, 2 5, 4 3, 3 3, 2 2))");
Query query = session.createQuery("select p from PolygonEntity p
where touches(p.polygon, :polygon) = true", PolygonEntity.class);
query.setParameter("polygon", wktToGeometry("POLYGON ((5 5, 5 10, 10 10, 10 5, 5 5))"));
assertThat(query.getResultList().stream()
.map(p -> ((PolygonEntity) p).getPolygon().toString())).containsOnly(
"POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))", "POLYGON ((3 0, 3 5, 8 5, 8 0, 3 0))");
}
touches() 함수를 통해 주어진 폴리곤에 인접한(닿아 있는) 폴리곤을 찾는다.
여기서 3번째 폴리곤은 주어진 폴리곤에 닿지 않기 때문에 결화에 반환되지 않는다.
참고
'Spring' 카테고리의 다른 글
Google Oauth2 (0) | 2024.04.02 |
---|---|
Pagination 조회 시의 Query tuning , 그리고 N+1 (2) | 2023.09.13 |
[프로젝트]알림 기능 구현(SSE, Spring AOP) (4) | 2023.07.03 |
[프로젝트] Spring AOP로 로그 구현 (0) | 2023.06.29 |
[프로젝트] Custom Annotation으로 알림 기능 Spring AOP 적용 (7) | 2023.06.29 |