Перейти к основному содержимому

Введение в Hibernate Spatial

· 7 мин. чтения

1. Введение

В этой статье мы рассмотрим пространственное расширение Hibernate, hibernate-spatial .

Начиная с версии 5, Hibernate Spatial предоставляет стандартный интерфейс для работы с географическими данными .

2. Справочная информация о Hibernate Spatial

Географические данные включают представление таких объектов, как точка, линия, многоугольник . Такие типы данных не являются частью спецификации JDBC, поэтому JTS (JTS Topology Suite) стал стандартом для представления пространственных типов данных.

Помимо JTS, пространственный Hibernate также поддерживает Geolatte-geom — недавнюю библиотеку, в которой есть некоторые функции, недоступные в JTS.

Обе библиотеки уже включены в проект hibernate-spatial. Использование одной библиотеки над другой — это просто вопрос того, из какой банки мы импортируем типы данных.

Хотя пространственные Hibernate поддерживает различные базы данных, такие как Oracle, MySQL, PostgreSQLql/PostGIS и некоторые другие, поддержка конкретных функций базы данных неодинакова.

Лучше обратиться к последней документации Hibernate, чтобы проверить список функций, для которых hibernate обеспечивает поддержку данной базы данных.

В этой статье мы будем использовать Mariadb4j в памяти, который поддерживает полную функциональность MySQL.

Конфигурация для Mariadb4j и MySql аналогична, даже библиотека mysql-connector работает для обеих этих баз данных.

3 . Зависимости Maven

Давайте посмотрим на зависимости Maven, необходимые для настройки простого проекта hibernate-spatial:

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.12.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
<version>5.2.12.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>ch.vorburger.mariaDB4j</groupId>
<artifactId>mariaDB4j</artifactId>
<version>2.2.3</version>
</dependency>

Зависимость hibernate-spatial обеспечивает поддержку пространственных типов данных. Последние версии hibernate-core , hibernate-spatial , mysql-connector-java и mariaDB4j можно получить в Maven Central.

4. Настройка Hibernate Spatial

Первый шаг — создать файл hibernate.properties в каталоге ресурсов :

hibernate.dialect=org.hibernate.spatial.dialect.mysql.MySQL56SpatialDialect
// ...

Единственное, что специфично для hibernate-spatial, — это диалект MySQL56SpatialDialect . Этот диалект расширяет диалект MySQL55Dialect и предоставляет дополнительную функциональность, связанную с типами пространственных данных.

Код, специфичный для загрузки файла свойств, создания SessionFactory и создания экземпляра Mariadb4j, такой же, как и в стандартном проекте гибернации.

5 . Понимание типа геометрии

Геометрия является базовым типом для всех пространственных типов в JTS. Это означает, что другие типы, такие как Point , Polygon и другие, являются производными от Geometry . Тип Geometry в java также соответствует типу GEOMETRY в MySql.

Анализируя строковое представление типа, мы получаем экземпляр Geometry . Вспомогательный класс WKTReader , предоставляемый JTS, можно использовать для преобразования любого известного текстового представления в тип 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()Geometry , фактическим экземпляром является экземпляр Point .

6. Сохранение точки в БД

Теперь, когда у нас есть хорошее представление о том, что такое тип Geometry и как получить Point из String , давайте посмотрим на PointEntity :

@Entity
public class PointEntity {

@Id
@GeneratedValue
private Long id;

private Point point;

// standard getters and setters
}

Обратите внимание, что сущность PointEntity содержит пространственный тип Point . Как было показано ранее, точка представлена двумя координатами:

public void insertPoint(String point) {
PointEntity entity = new PointEntity();
entity.setPoint((Point) wktToGeometry(point));
session.persist(entity);
}

Метод insertPoint() принимает общеизвестное текстовое (WKT) представление Point , преобразует его в экземпляр Point и сохраняет в БД.

Напоминаем, что сеанс не является специфичным для hibernate-spatial и создается аналогично другому проекту hibernate.

Здесь мы можем заметить, что после создания экземпляра Point процесс сохранения PointEntity подобен любому обычному объекту.

Давайте посмотрим на некоторые тесты:

@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);
}

Вызов toString() для Point возвращает WKT-представление Point . Это связано с тем, что класс Geometry переопределяет метод toString() и внутренне использует WKTWriter, дополнительный класс для WKTReader , который мы видели ранее.

Как только мы запустим этот тест, hibernate создаст для нас таблицу PointEntity .

Давайте посмотрим на эту таблицу:

desc PointEntity;
Field Type Null Key
id bigint(20) NO PRI
point geometry YES

Как и ожидалось , тип точки поляGEOMETRY . Из-за этого при извлечении данных с помощью нашего редактора SQL (например, рабочей среды MySql) нам нужно преобразовать этот тип GEOMETRY в удобочитаемый текст:

select id, astext(point) from PointEntity;

id astext(point)
1 POINT(2 4)

Однако, поскольку hibernate уже возвращает WKT-представление, когда мы вызываем метод toString() для Geometry или любого из его подклассов, нам не нужно беспокоиться об этом преобразовании.

7. Использование пространственных функций

7.1. ST_WITHIN() Пример

Теперь мы рассмотрим использование функций базы данных, которые работают с пространственными типами данных.

Одной из таких функций в MySQL является 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 () , так как нам нужно нарисовать круг вокруг центра в обоих направлениях.

Давайте теперь двинемся вперед и посмотрим, как получить точки в заданном радиусе:

@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 сопоставляет свою функцию inside() с функцией ST_WITHIN () MySql.

Интересное наблюдение здесь состоит в том, что точка (3, 4) попадает точно на окружность. Тем не менее, запрос не возвращает эту точку. Это связано с тем, что функция inside () возвращает значение true только в том случае, если данная геометрия полностью находится внутри другой геометрии .

7.2. ST_TOUCHES() Пример

Здесь мы представим пример, который вставляет набор Polygon s в базу данных и выбирает Polygon s, смежные с данным Polygon . Давайте быстро взглянем на класс PolygonEntity :

@Entity
public class PolygonEntity {

@Id
@GeneratedValue
private Long id;

private Polygon polygon;

// standard getters and setters
}

Единственное, что здесь отличается от предыдущего PointEntity , это то, что мы используем тип Polygon вместо Point .

Теперь перейдем к тесту:

@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))");
}

Метод insertPolygon() похож на метод insertPoint() , который мы видели ранее. Исходник содержит полную реализацию этого метода.

Мы используем функцию touches() , чтобы найти Polygon , смежный с заданным Polygon . Очевидно, что третий Polygon не возвращается в результате, так как ребро не касается данного Polygon .

8. Заключение

В этой статье мы увидели, что hibernate-spatial значительно упрощает работу с пространственными типами данных, поскольку заботится о низкоуровневых деталях.

Несмотря на то, что в этой статье используется Mariadb4j, мы можем заменить его на MySql без изменения какой-либо конфигурации.

Как всегда, полный исходный код этой статьи можно найти на GitHub .