Einführung in Hibernate Spatial

1. Einleitung

In diesem Artikel werfen wir einen Blick auf die räumliche Erweiterung von Hibernate, Hibernate-Spatial.

Ab Version 5 bietet Hibernate Spatial eine Standardschnittstelle für die Arbeit mit geografischen Daten .

2. Hintergrundinformationen zu Hibernate Spatial

Geografische Daten umfassen die Darstellung von Objekten wie einem Punkt, einer Linie oder einem Polygon . Solche Datentypen sind nicht Teil der JDBC-Spezifikation, daher ist die JTS (JTS Topology Suite) zum Standard für die Darstellung von Geodatentypen geworden.

Neben JTS unterstützt Hibernate Spatial auch Geolatte-Geom - eine aktuelle Bibliothek mit einigen Funktionen, die in JTS nicht verfügbar sind.

Beide Bibliotheken sind bereits im räumlichen Ruhezustandsprojekt enthalten. Die Verwendung einer Bibliothek über der anderen ist lediglich eine Frage der JAR-Datei, aus der Datentypen importiert werden.

Obwohl Hibernate Spatial verschiedene Datenbanken wie Oracle, MySQL, PostgreSQLql / PostGIS und einige andere unterstützt, ist die Unterstützung für die datenbankspezifischen Funktionen nicht einheitlich.

Lesen Sie besser die neueste Hibernate-Dokumentation, um die Liste der Funktionen zu überprüfen, für die Hibernate Unterstützung für eine bestimmte Datenbank bietet.

In diesem Artikel verwenden wir ein In-Memory-Mariadb4j, das die volle Funktionalität von MySQL beibehält.

Die Konfiguration für Mariadb4j und MySql ist ähnlich, sogar die MySQL-Connector-Bibliothek funktioniert für beide Datenbanken.

3 . Maven-Abhängigkeiten

Werfen wir einen Blick auf die Maven-Abhängigkeiten, die zum Einrichten eines einfachen räumlichen Projekts im Ruhezustand erforderlich sind:

 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  

Die räumliche Abhängigkeit im Ruhezustand unterstützt die räumlichen Datentypen. Die neuesten Versionen von Hibernate-Core, Hibernate-Spatial, MySQL-Connector-Java und mariaDB4j sind bei Maven Central erhältlich.

4. Konfigurieren von Hibernate Spatial

Der erste Schritt besteht darin, eine hibernate.properties im Ressourcenverzeichnis zu erstellen :

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

Das einzige, was spezifisch für den räumlichen Ruhezustand ist, ist der MySQL56SpatialDialect- Dialekt . Dieser Dialekt erweitert den MySQL55Dialect- Dialekt und bietet zusätzliche Funktionen in Bezug auf die räumlichen Datentypen.

Der Code zum Laden der Eigenschaftendatei, Erstellen einer SessionFactory und Instanziieren einer Mariadb4j-Instanz ist der gleiche wie in einem Standardprojekt im Ruhezustand.

5 . Das Verständnis der Geometrie Art

Geometrie ist der Basistyp für alle räumlichen Typen in JTS. Dies bedeutet, dass andere Typen wie Punkt , Polygon und andere von Geometrie ausgehen . Der Geometrietyp in Java entspricht auch dem GEOMETRY- Typ in MySql.

Durch Parsen einer String- Darstellung des Typs erhalten wir eine Instanz von Geometry . Mit einem von JTS bereitgestellten WKTReader der Dienstprogrammklasse kann jede bekannte Textdarstellung in einen Geometrietyp konvertiert werden :

public Geometry wktToGeometry(String wellKnownText) throws ParseException { return new WKTReader().read(wellKnownText); }

Lassen Sie uns nun diese Methode in Aktion sehen:

@Test public void shouldConvertWktToGeometry() { Geometry geometry = wktToGeometry("POINT (2 5)"); assertEquals("Point", geometry.getGeometryType()); assertTrue(geometry instanceof Point); }

Wie wir, auch wenn der Rückgabetyp der Methode sehen können , wird gelesen () Methode ist Geometrie , die eigentliche Instanz des eines ist Punktes .

6. Speichern eines Punktes in der Datenbank

Nachdem wir nun eine gute Vorstellung davon haben, was ein Geometrietyp ist und wie man einen Punkt aus einem String herausholt, werfen wir einen Blick auf die PointEntity :

@Entity public class PointEntity { @Id @GeneratedValue private Long id; private Point point; // standard getters and setters }

Beachten Sie, dass die Entität PointEntity einen räumlichen Typ Point enthält . Wie bereits gezeigt, wird ein Punkt durch zwei Koordinaten dargestellt:

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

Das Verfahren insertPoint () nimmt eine bekannter Text (WKT) Darstellung eines Punktes , wandelt es in einer Punkt - Instanz und speichert in der DB.

Zur Erinnerung: Die Sitzung ist nicht spezifisch für den räumlichen Ruhezustand und wird auf ähnliche Weise wie ein anderes Projekt im Ruhezustand erstellt.

Wir können hier feststellen, dass das Speichern von PointEntity nach dem Erstellen einer Instanz von Point mit jeder regulären Entität vergleichbar ist.

Schauen wir uns einige Tests an:

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

Wenn Sie toString () für einen Punkt aufrufen, wird die WKT-Darstellung eines Punkts zurückgegeben . Dies liegt daran, dass die Geometry- Klasse die toString () -Methode überschreibt und intern WKTWriter verwendet, eine kostenlose Klasse für WKTReader , die wir zuvor gesehen haben.

Sobald wir diesen Test ausführen, erstellt der Ruhezustand eine PointEntity- Tabelle für uns.

Schauen wir uns diese Tabelle an:

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

Wie erwartet, ist der Typ von Feldpunkt ist GEOMETRY . Aus diesem Grund müssen wir beim Abrufen der Daten mit unserem SQL-Editor (wie der MySql-Workbench) diesen GEOMETRY-Typ in lesbaren Text konvertieren:

select id, astext(point) from PointEntity; id astext(point) 1 POINT(2 4)

Da der Ruhezustand jedoch bereits die WKT-Darstellung zurückgibt, wenn wir die toString () -Methode für Geometry oder eine ihrer Unterklassen aufrufen , müssen wir uns nicht um diese Konvertierung kümmern.

7. Verwenden von Raumfunktionen

7.1. ST_WITHIN () Beispiel

Wir werden uns nun die Verwendung von Datenbankfunktionen ansehen, die mit räumlichen Datentypen arbeiten.

Eine solche Funktion in MySQL ist ST_WITHIN () , die angibt , ob sich eine Geometrie in einer anderen befindet. Ein gutes Beispiel wäre hier, alle Punkte innerhalb eines bestimmten Radius herauszufinden.

Schauen wir uns zunächst an, wie Sie einen Kreis erstellen:

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

Ein Kreis wird durch eine endliche Menge von Punkten dargestellt, die durch die Methode setNumPoints () angegeben werden. Der Radius wird vor dem Aufrufen der setSize () -Methode verdoppelt, da der Kreis in beiden Richtungen um den Mittelpunkt gezeichnet werden muss.

Lassen Sie uns nun vorwärts gehen und sehen, wie die Punkte innerhalb eines bestimmten Radius abgerufen werden:

@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 bildet seine in () Funktion zum ST_Within () Funktion von MySql.

Eine interessante Beobachtung hier ist, dass der Punkt (3, 4) genau auf den Kreis fällt. Die Abfrage gibt diesen Punkt jedoch nicht zurück. Dies liegt daran, dass die Funktion inside () nur dann true zurückgibt, wenn sich die angegebene Geometrie vollständig in einer anderen Geometrie befindet .

7.2. ST_TOUCHES() Example

Here, we'll present an example that inserts a set of Polygons in the database and select the Polygons that are adjacent to a given Polygon. Let's have a quick look at the PolygonEntity class:

@Entity public class PolygonEntity { @Id @GeneratedValue private Long id; private Polygon polygon; // standard getters and setters }

The only thing different here from the previous PointEntity is that we're using the type Polygon instead of the Point.

Let's now move towards the test:

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

The insertPolygon() method is similar to the insertPoint() method that we saw earlier. The source contains the full implementation of this method.

Wir verwenden die Funktion touch () , um die Polygone neben einem bestimmten Polygon zu finden . Es ist klar, dass das dritte Polygon im Ergebnis nicht zurückgegeben wird, da keine Kante das angegebene Polygon berührt .

8. Fazit

In diesem Artikel haben wir gesehen, dass der räumliche Ruhezustand den Umgang mit räumlichen Datentypen erheblich vereinfacht, da die Details auf niedriger Ebene berücksichtigt werden.

Obwohl dieser Artikel Mariadb4j verwendet, können wir ihn durch MySQL ersetzen, ohne die Konfiguration zu ändern.

Wie immer finden Sie den vollständigen Quellcode für diesen Artikel auf GitHub.