Introduction à Hibernate Spatial

Introduction à Hibernate Spatial

1. introduction

Dans cet article, nous allons jeter un œil à l'extension spatiale d'Hibernate,hibernate-spatial.

À partir de la version 5,Hibernate Spatial provides a standard interface for working with geographic data.

2. Contexte sur Hibernate Spatial

Les données géographiques incluent la représentation d'entités comme unPoint, Line, Polygon. Ces types de données ne font pas partie de la spécification JDBC, c'est pourquoi lesJTS (JTS Topology Suite) sont devenus une norme pour représenter les types de données spatiales.

Outre JTS, Hibernate spatial prend également en chargeGeolatte-geom - une bibliothèque récente qui possède certaines fonctionnalités qui ne sont pas disponibles dans JTS.

Les deux bibliothèques sont déjà incluses dans le projet hibernate-spatial. Utiliser une bibliothèque plutôt qu'une autre est simplement une question de savoir à partir de quel fichier nous importons les types de données.

Bien qu'Hibernate Spatial prenne en charge différentes bases de données telles qu'Oracle, MySQL, PostgreSQLql / PostGIS et quelques autres, la prise en charge des fonctions spécifiques à la base de données n'est pas uniforme.

Il est préférable de se référer à la dernière documentation Hibernate pour vérifier la liste des fonctions pour lesquelles hibernate prend en charge une base de données donnée.

Dans cet article, nous utiliserons unMariadb4j en mémoire, qui conserve toutes les fonctionnalités de MySQL.

La configuration pour Mariadb4j et MySql est similaire, même la bibliothèque mysql-connector fonctionne pour ces deux bases de données.

3. Maven Dependencies

Jetons un coup d'œil aux dépendances Maven requises pour la mise en place d'un simple projet hibernate-spatial:


    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

La dépendancehibernate-spatial est celle qui fournira la prise en charge des types de données spatiales. Les dernières versions dehibernate-entitymanager,hibernate-spatial,mysql-connector-java etmariaDB4j peuvent être obtenues auprès de Maven Central.

4. Configuration d'Hibernate Spatial

La première étape consiste à créer unhibernate.properties dans le répertoireresources:

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

The only thing that is specific to hibernate-spatial is the MySQL56SpatialDialect dialect. Ce dialecte étend le dialecteMySQL55Dialect et fournit des fonctionnalités supplémentaires liées aux types de données spatiales.

Le code spécifique au chargement du fichier de propriétés, à la création d'unSessionFactory et à l'instanciation d'une instance Mariadb4j, est le même que dans un projet hibernate standard.

5. Understanding the Geometry Type

Geometry est le type de base pour tous les types spatiaux dans JTS. Cela signifie que d'autres types commePoint,Polygon et d'autres s'étendent à partir deGeometry. Le typeGeometry en java correspond également au typeGEOMETRY dans MySql.

En analysant une représentationString du type, nous obtenons une instance deGeometry. Une classe d'utilitéWKTReader fournie par JTS peut être utilisée pour convertir toute représentationwell-known text en un typeGeometry:

public Geometry wktToGeometry(String wellKnownText)
  throws ParseException {

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

Voyons maintenant cette méthode en action:

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

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

Comme nous pouvons le voir, même si le type de retour de la méthode estread() method estGeometry, l'instance réelle est celle d'unPoint.

6. Stocker un point dans la base de données

Maintenant que nous avons une bonne idée de ce qu'est un typeGeometry et comment obtenir unPoint à partir d'unString, jetons un œil auxPointEntity:

@Entity
public class PointEntity {

    @Id
    @GeneratedValue
    private Long id;

    private Point point;

    // standard getters and setters
}

Notez que l'entitéPointEntity contient un type spatialPoint. Comme démontré précédemment, unPoint est représenté par deux coordonnées:

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

La méthodeinsertPoint() accepte une représentation textuelle bien connue (WKT) d'unPoint, la convertit en une instancePoint et la sauvegarde dans la base de données.

Pour rappel, lesession n’est pas spécifique à hibernate-spatial et est créé de manière similaire à un autre projet hibernate.

Nous pouvons remarquer ici qu'une fois que nous avons créé une instance dePoint, le processus de stockage dePointEntity est similaire à n'importe quelle entité régulière.

Examinons quelques tests:

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

L'appel detoString() sur unPoint renvoie la représentation WKT d'unPoint. Ceci est dû au fait que la classeGeometry remplace la méthodetoString() et utilise en interneWKTWriter, une classe complémentaire àWKTReader que nous avons vue précédemment.

Une fois ce test exécuté, hibernate créera la tablePointEntity pour nous.

Jetons un œil à ce tableau:

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

Comme prévu, leType deFieldPoint estGEOMETRY. Pour cette raison, lors de l'extraction des données à l'aide de notre éditeur SQL (comme MySql Workbench), nous devons convertir ce type GEOMETRY en texte lisible par l'homme:

select id, astext(point) from PointEntity;

id      astext(point)
1       POINT(2 4)

Cependant, comme hibernate renvoie déjà la représentation WKT lorsque nous appelons la méthodetoString() surGeometry ou l'une de ses sous-classes, nous n'avons pas besoin de nous soucier de cette conversion.

7. Utilisation des fonctions spatiales

7.1. ST_WITHIN() Exemple

Nous allons maintenant examiner l'utilisation des fonctions de base de données qui fonctionnent avec les types de données spatiales.

Une de ces fonctions dans MySQL estST_WITHIN() qui indique si unGeometry est dans un autre. Un bon exemple ici serait de trouver tous les points dans un rayon donné.

Commençons par voir comment créer un cercle:

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

Un cercle est représenté par un ensemble fini de points spécifiés par la méthodesetNumPoints(). Leradius est doublé avant d'appeler la méthodesetSize() car nous devons dessiner le cercle autour du centre, dans les deux sens.

Voyons maintenant comment récupérer les points dans un rayon donné:

@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 mappe sa fonctionwithin() à la fonctionST_WITHIN() de MySql.

Une observation intéressante ici est que le point (3, 4) tombe exactement sur le cercle. Pourtant, la requête ne renvoie pas ce point. C'est parce quethe within() function returns true only if the given Geometry is completely within another Geometry.

7.2. ST_TOUCHES() Exemple

Ici, nous allons présenter un exemple qui insère un ensemble dePolygons dans la base de données et sélectionner lesPolygons qui sont adjacents à unPolygon donné. Jetons un coup d'œil à la classePolygonEntity:

@Entity
public class PolygonEntity {

    @Id
    @GeneratedValue
    private Long id;

    private Polygon polygon;

    // standard getters and setters
}

La seule chose différente ici desPointEntity précédents est que nous utilisons le typePolygon au lieu desPoint.

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

La méthodeinsertPolygon() est similaire à la méthodeinsertPoint() que nous avons vue précédemment. Le source contient l'implémentation complète de cette méthode.

Nous utilisons la fonctiontouches() pour trouver lesPolygon adjacents à unPolygon donné. Clairement, le troisièmePolygon n'est pas retourné dans le résultat car il n'y a pas d'arête touchant lesPolygon donnés.

8. Conclusion

Dans cet article, nous avons vu que hibernate-spatial rend le traitement des types de données spatiaux beaucoup plus simple car il prend en charge les détails de bas niveau.

Même si cet article utilise Mariadb4j, nous pouvons le remplacer par MySql sans modifier aucune configuration.

Comme toujours, le code source complet de cet article peut être trouvéover on GitHub.