Введение в Hibernate Spatial

Введение в Hibernate Spatial

1. Вступление

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

Начиная с версии 5Hibernate Spatial provides a standard interface for working with geographic data.

2. Фон на Hibernate Пространственный

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

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

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

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

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

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

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

3. Maven Dependenciesс

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


    org.hibernate
    hibernate-entitymanager
    5.2.12.Final


    org.hibernate
    hibernate-spatial
    5.2.12.Final


    mysql
    mysql-connector-java
    6.0.6


    ch.vorburger.mariaDB4j
    mariaDB4j
    2.2.3

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

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

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

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

The only thing that is specific to hibernate-spatial is the MySQL56SpatialDialect dialect. Этот диалект расширяет диалектMySQL55Dialect и предоставляет дополнительные функции, связанные с типами пространственных данных.

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

5. Understanding the Geometry Typeс

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

Анализируя представление типаString, мы получаем экземплярGeometry. Служебный классWKTReader, предоставляемый JTS, может использоваться для преобразования любого представленияwell-known text в тип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. Как было показано ранее, aPoint представлен двумя координатами:

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

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

Напоминаем, чтоsession не специфичен для пространственной гибернации и создается аналогично другому проекту гибернации.

Здесь можно заметить, что после создания экземпляра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

Как и ожидалось,TypeFieldPoint равноGEOMETRY. Из-за этого при получении данных с помощью нашего редактора SQL (например, MySql workbench) нам необходимо преобразовать этот тип 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(), которая сообщает, находится ли одинGeometry внутри другого. Хорошим примером здесь было бы выяснить все точки в данном радиусе.

Давайте начнем с того, как создать круг:

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(). radius удваивается перед вызовом метода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 сопоставляет свою функциюwithin() с функциейST_WITHIN() MySql.

Интересное наблюдение здесь состоит в том, что точка (3, 4) падает точно на окружность. Тем не менее, запрос не возвращает эту точку. Это потому, чтоthe within() function returns true only if the given Geometry is completely within another Geometry.

7.2. ST_TOUCHES() Пример

Здесь мы представим пример, который вставляет наборPolygons в базу данных и выбираетPolygons, которые смежны с данным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(), чтобы найтиPolygons, смежный с заданнымPolygon. Ясно, что третийPolygon не возвращается в результате, поскольку нет края, касающегося данногоPolygon.

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

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

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

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