1. введение
В этой статье мы рассмотрим пространственное расширение Hibernate, hibernate-spatial .
Начиная с версии 5, Hibernate Spatial предоставляет стандартный интерфейс для работы с географическими данными .
2. Фон в режиме гибернации
Географические данные включают представление таких объектов, как Точка, линия, полигон . Такие типы данных не являются частью спецификации JDBC, поэтому JTS (JTS Topology Suite) стал стандартом для представления пространственных типов данных.
Помимо JTS, Hibernate spatial также поддерживает Geolatte-geom – недавнюю библиотеку, которая имеет некоторые функции, недоступные в JTS.
Обе библиотеки уже включены в проект hibernate-spatial. Использование одной библиотеки над другой-это просто вопрос того, из какой jar мы импортируем типы данных.
Хотя Hibernate spatial поддерживает различные базы данных, такие как Oracle, MySQL, Postgresql/PostGIS и некоторые другие, поддержка конкретных функций базы данных неоднородна.
Лучше обратиться к последней документации по Hibernate, чтобы проверить список функций, для которых hibernate предоставляет поддержку для данной базы данных.
В этой статье мы будем использовать в памяти Mariadb4j -который поддерживает полную функциональность MySQL.
Конфигурация для Mariadb и MySQL аналогична, даже библиотека mysql-connector работает для обеих этих баз данных.
3. Зависимости Maven
Давайте посмотрим на зависимости Maven, необходимые для настройки простого пространственного проекта hibernate:
org.hibernate hibernate-core 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-core , hibernate-spatial , mysql-connector-java и MariaDB4j можно получить в Maven Central.
4. Настройка пространственного режима гибернации
Первым шагом является создание файла hibernate.properties в каталоге resources :
hibernate.dialect=org.hibernate.spatial.dialect.mysql.MySQL56SpatialDialect // ...
Единственное, что характерно для hibernate-spatial, – это пространственный диалект MySQL56 диалект . Этот диалект расширяет диалект MySQL55 и предоставляет дополнительные функции, связанные с типами пространственных данных.
Код, специфичный для загрузки файла свойств , создания SessionFactory и создания экземпляра Mariadb4j, такой же, как и в стандартном проекте hibernate.
5. Понимание типа геометрии
Геометрия является базовым типом для всех пространственных типов в JTS. Это означает, что другие типы , такие как Point , Polygon и другие, простираются от Geometry . Тип Geometry в java также соответствует типу GEOMETRY в MySQL.
Анализируя представление типа String , мы получаем экземпляр Geometry . Служебный класс WKTReader , предоставляемый JTS, может использоваться для преобразования любого хорошо известного текста представления в Геометрию тип:
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. Сохранение точки в БД
Теперь , когда у нас есть хорошее представление о том, что такое Геометрия тип и как получить Точку из Строки , давайте посмотрим на точечность :
@Entity public class PointEntity { @Id @GeneratedValue private Long id; private Point point; // standard getters and setters }
Обратите внимание, что сущность Точечная сущность содержит пространственный тип Точка . Как было показано ранее, точка | представлена двумя координатами:
public void insertPoint(String point) { PointEntity entity = new PointEntity(); entity.setPoint((Point) wktToGeometry(point)); session.persist(entity); }
Метод insert Point() принимает хорошо известное текстовое (WKT) представление Point , преобразует его в экземпляр Point и сохраняет в БД.
Напомним, что session не является специфичным для hibernate-spatial и создается аналогично другому проекту hibernate.
Здесь мы можем заметить, что после создания экземпляра Point процесс хранения Point Entity аналогичен любому обычному объекту.
Давайте рассмотрим некоторые тесты:
@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() на точке возвращает представление WKT Точки . Это связано с тем, что класс Geometry переопределяет метод toString() и внутренне использует WKTWriter, дополнительный класс к WKTReader , который мы видели ранее.
Как только мы запустим этот тест, hibernate создаст для нас Точечную сущность таблицу.
Давайте взглянем на этот стол:
desc PointEntity; Field Type Null Key id bigint(20) NO PRI point geometry YES
Как и ожидалось, Тип поля |/Точки является ГЕОМЕТРИЕЙ . Из-за этого при извлечении данных с помощью нашего редактора SQL (например, MySQL workbench) нам необходимо преобразовать этот тип ГЕОМЕТРИИ в удобочитаемый текст:
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(); }
Окружность представлена конечным набором точек, заданных методом Setnump () . 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) падает точно на окружность. Тем не менее, запрос не возвращает эту точку. Это происходит потому, что функция within() возвращает true только в том случае, если данная Геометрия полностью находится в другой Геометрии .
7.2. Пример ST_TOUCHES()
Здесь мы приведем пример, который вставляет набор Polygon s в базу данных и выбирает Polygon s, которые примыкают к заданному Polygon . Давайте быстро взглянем на объект Polygon класс:
@Entity public class PolygonEntity { @Id @GeneratedValue private Long id; private Polygon polygon; // standard getters and setters }
Единственное, что здесь отличается от предыдущей сущности Point , это то, что мы используем тип 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))"); }
Метод insert Polygon() аналогичен методу insert Point () , который мы видели ранее. Источник содержит полную реализацию этого метода.
Мы используем функцию touch() , чтобы найти Полигон s, прилегающий к заданному Полигону . Очевидно, что третий Полигон не возвращается в результате, так как нет ребра, соприкасающегося с данным полигоном .
8. Заключение
В этой статье мы видели, что hibernate-spatial значительно упрощает работу с пространственными типами данных, поскольку он заботится о деталях низкого уровня.
Несмотря на то, что в этой статье используется Mariadb4j, мы можем заменить его MySQL без изменения какой-либо конфигурации.
Как всегда, полный исходный код этой статьи можно найти на GitHub .