Einführung in den Ruhezustand von Spatial

Einführung in Hibernate Spatial

1. Einführung

In diesem Artikel sehen wir uns die räumliche Erweiterung von Hibernate,hibernate-spatial an.

Ab Version 5Hibernate Spatial provides a standard interface for working with geographic data.

2. Hintergrund zu Hibernate Spatial

Geografische Daten umfassen die Darstellung von Entitäten wiePoint, Line, Polygon. Solche Datentypen sind nicht Teil der JDBC-Spezifikation, daher istJTS (JTS Topology Suite) ein Standard für die Darstellung von Geodatentypen geworden.

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

Beide Bibliotheken sind bereits im Projekt für den Ruhezustand enthalten. Die Verwendung einer Bibliothek über einer anderen ist lediglich eine Frage der Jar, aus der wir Datentypen importieren.

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-MemoryMariadb4j, das die volle Funktionalität von MySQL beibehält.

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

3. Maven Dependencies

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


    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

Die Abhängigkeit vonhibernate-spatialist diejenige, die die Unterstützung für die räumlichen Datentypen bietet. Die neuesten Versionen vonhibernate-entitymanager,hibernate-spatial,mysql-connector-java undmariaDB4j sind bei Maven Central erhältlich.

4. Ruhezustand konfigurieren

Der erste Schritt besteht darin, einhibernate.properties im Verzeichnisresources zu erstellen:

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

The only thing that is specific to hibernate-spatial is the MySQL56SpatialDialect dialect. Dieser Dialekt erweitert den Dialekt vonMySQL55Dialectund bietet zusätzliche Funktionen in Bezug auf die räumlichen Datentypen.

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

5. Understanding the Geometry Type

Geometry ist der Basistyp für alle räumlichen Typen in JTS. Dies bedeutet, dass andere Typen wiePoint,Polygon und andere vonGeometry reichen. DerGeometry-Typ in Java entspricht auch demGEOMETRY-Typ in MySql.

Durch Parsen einerString-Darstellung des Typs erhalten wir eine Instanz vonGeometry. Eine von JTS bereitgestellte DienstprogrammklasseWKTReader kann verwendet werden, um eine beliebigewell-known text-Darstellung in einenGeometry-Typ zu konvertieren:

public Geometry wktToGeometry(String wellKnownText)
  throws ParseException {

    return new WKTReader().read(wellKnownText);
}

Lassen Sie uns diese Methode nun in Aktion sehen:

@Test
public void shouldConvertWktToGeometry() {
    Geometry geometry = wktToGeometry("POINT (2 5)");

    assertEquals("Point", geometry.getGeometryType());
    assertTrue(geometry instanceof Point);
}

Wie wir sehen können, ist die tatsächliche Instanz die vonPoint, selbst wenn der Rückgabetyp der Methoderead()Geometry ist.

6. Einen Punkt in der DB speichern

Nachdem wir nun eine gute Vorstellung davon haben, was einGeometry-Typ ist und wie man aus einemString einPoint macht, werfen wir einen Blick auf diePointEntity:

@Entity
public class PointEntity {

    @Id
    @GeneratedValue
    private Long id;

    private Point point;

    // standard getters and setters
}

Beachten Sie, dass die EntitätPointEntity einen räumlichen TypPoint enthält. Wie bereits gezeigt, wird einPoint durch zwei Koordinaten dargestellt:

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

Die MethodeinsertPoint() akzeptiert eine bekannte Textdarstellung (WKT) vonPoint, konvertiert sie in einePoint-Instanz und speichert sie in der Datenbank.

Zur Erinnerung:session ist nicht spezifisch für den räumlichen Ruhezustand und wird auf ähnliche Weise wie ein anderes Ruhezustandsprojekt erstellt.

Wir können hier feststellen, dass nach dem Erstellen einer Instanz vonPoint das Speichern vonPointEntity jeder regulären Entität ähnlich 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 SietoString() aufPoint aufrufen, wird die WKT-Darstellung vonPoint zurückgegeben. Dies liegt daran, dass die KlasseGeometrydie MethodetoString()überschreibt und internWKTWriter, als ergänzende Klasse zuWKTReader verwendet, die wir zuvor gesehen haben.

Sobald wir diesen Test ausführen, erstellt der Ruhezustand diePointEntity-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 beträgtType vonFieldPointGEOMETRY. Aus diesem Grund müssen wir beim Abrufen der Daten mit unserem SQL-Editor (wie 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 MethodetoString()fürGeometry oder eine ihrer Unterklassen aufrufen, müssen wir uns nicht um diese Konvertierung kümmern.

7. Raumfunktionen verwenden

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 istST_WITHIN(), die angibt, ob sich einGeometry in einem anderen befindet. Ein gutes Beispiel wäre hier, alle Punkte innerhalb eines gegebenen 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 mit der MethodesetNumPoints()angegeben werden. Dasradius wird verdoppelt, bevor diesetSize()-Methode aufgerufen wird, da wir den Kreis um den Mittelpunkt in beide Richtungen zeichnen müssen.

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

Der Ruhezustand ordnet seinewithin()-Funktion derST_WITHIN()-Funktion von MySql zu.

Eine interessante Beobachtung ist hier, dass der Punkt (3, 4) genau auf den Kreis fällt. Die Abfrage gibt diesen Punkt jedoch nicht zurück. Dies liegt daran, dassthe within() function returns true only if the given Geometry is completely within another Geometry.

7.2. ST_TOUCHES() Beispiel

Hier präsentieren wir ein Beispiel, das eine Menge vonPolygons in die Datenbank einfügt und diePolygons auswählt, die an ein bestimmtesPolygon angrenzen. Werfen wir einen kurzen Blick auf die KlassePolygonEntity:

@Entity
public class PolygonEntity {

    @Id
    @GeneratedValue
    private Long id;

    private Polygon polygon;

    // standard getters and setters
}

Das einzige, was sich hier von den vorherigenPointEntityunterscheidet, ist, dass wir den TypPolygon anstelle vonPoint verwenden.

Gehen wir nun zum Test über:

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

Die MethodeinsertPolygon() ähnelt der MethodeinsertPoint(), die wir zuvor gesehen haben. Die Quelle enthält die vollständige Implementierung dieser Methode.

Wir verwenden die Funktiontouches(), um diePolygons neben einem bestimmtenPolygon zu finden. Es ist klar, dass das drittePolygon im Ergebnis nicht zurückgegeben wird, da keine Kante die angegebenenPolygon 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 in diesem Artikel Mariadb4j verwendet wird, können wir es durch MySql ersetzen, ohne die Konfiguration zu ändern.

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