Introduction à Hibernate Spatial

1. Introduction

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

Depuis la version 5, Hibernate Spatial fournit une interface standard pour travailler avec des données géographiques .

2. Informations générales sur Hibernate Spatial

Les données géographiques incluent la représentation d'entités comme un point, une ligne, un polygone . Ces types de données ne font pas partie de la spécification JDBC, c'est pourquoi le JTS (JTS Topology Suite) est devenu un standard pour représenter les types de données spatiales.

Outre JTS, Hibernate spatial prend également en charge Geolatte-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 des types de données.

Bien qu'Hibernate spatial prenne en charge différentes bases de données comme 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 un Mariadb4j en mémoire - qui maintient toutes les fonctionnalités de MySQL.

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

3 . Dépendances de Maven

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

 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  

La dépendance hibernation-spatiale est celle qui prendra en charge les types de données spatiales. Les dernières versions de hibernate-core, hibernate-spatial, mysql-connector-java et mariaDB4j peuvent être obtenues auprès de Maven Central.

4. Configuration de Hibernate Spatial

La première étape consiste à créer un hibernate.properties dans le répertoire des ressources :

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

La seule chose qui est spécifique à hibernate-spatial est le dialecte MySQL56SpatialDialect . Ce dialecte étend le dialecte MySQL55Dialect 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'une SessionFactory et à l'instanciation d'une instance Mariadb4j est le même que dans un projet de mise en veille prolongée standard.

5 . Comprendre le type de géométrie

La géométrie est le type de base pour tous les types spatiaux dans JTS. Cela signifie que d'autres types tels que Point , Polygon et d'autres s'étendent de Geometry . Le type Geometry en java correspond également au type GEOMETRY dans MySql.

En analysant une représentation String du type, nous obtenons une instance de Geometry . Une classe utilitaire WKTReader fournie par JTS peut être utilisée pour convertir toute représentation textuelle bien connue en un type Geometry :

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

Maintenant, voyons 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 est la méthode read () est Geometry , l'instance réelle est celle d'un Point .

6. Stockage d'un point dans DB

Maintenant que nous avons une bonne idée de ce qu'est un type Geometry et comment extraire un point d'une chaîne , jetons un coup d'œil à PointEntity :

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

Notez que l'entité PointEntity contient un point de type spatial . Comme démontré précédemment, un point 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éthode insertPoint () accepte une représentation textuelle bien connue (WKT) d'un point , la convertit en une instance de point et l'enregistre dans la base de données.

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

Nous pouvons remarquer ici qu'une fois qu'une instance de Point a été créée, le processus de stockage de PointEntity est similaire à n'importe quelle entité régulière.

Regardons 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 de toString () sur un point renvoie la représentation WKT d'un point . C'est parce que la classe Geometry remplace la méthode toString () et utilise en interne WKTWriter, une classe complémentaire à WKTReader que nous avons vue précédemment.

Une fois ce test exécuté, hibernate créera une table PointEntity pour nous.

Jetons un œil à ce tableau:

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

Comme prévu, le type de point de champ est la GEOMETRIE . Pour cette raison, lors de la récupération 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)

However, as hibernate already returns WKT representation when we call toString() method on Geometry or any of its subclasses, we don't need to bother about this conversion.

7. Using Spatial Functions

7.1. ST_WITHIN() Example

We'll now have a look at the usage of database functions that work with spatial data types.

One of such function in MySQL is ST_WITHIN() that tells whether one Geometry is within another. A good example here would be to find out all the points within a given radius.

Let's start by looking at how to create a circle:

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

A circle is represented by a finite set of points specified by the setNumPoints() method. The radius is doubled before calling the setSize() method as we need to draw the circle around the center, in both the directions.

Let's now move forward and see how to fetch the points within a given radius:

@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 maps its within() function to the ST_WITHIN() function of MySql.

An interesting observation here is that the Point (3, 4) falls exactly on the circle. Still, the query doesn't return this point. This is because the within() function returns true only if the given Geometry is completely within another Geometry.

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.

Nous utilisons la fonction touches () pour trouver les Polygones adjacents à un Polygone donné . De toute évidence, le troisième polygone n'est pas renvoyé dans le résultat car il n'y a pas d'arête touchant le polygone donné .

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 est disponible sur GitHub.