Introduzione a Hibernate Spatial

1. Introduzione

In questo articolo daremo uno sguardo all'estensione spaziale di Hibernate, hibernate-spatial.

A partire dalla versione 5, Hibernate Spatial fornisce un'interfaccia standard per lavorare con i dati geografici .

2. Background su Hibernate Spatial

I dati geografici includono la rappresentazione di entità come un punto, una linea, un poligono . Tali tipi di dati non fanno parte della specifica JDBC, quindi JTS (JTS Topology Suite) è diventato uno standard per rappresentare i tipi di dati spaziali.

Oltre a JTS, Hibernate spatial supporta anche Geolatte-geom, una libreria recente che ha alcune funzionalità che non sono disponibili in JTS.

Entrambe le librerie sono già incluse nel progetto hibernate-spatial. Usare una libreria rispetto ad un'altra è semplicemente una questione di quale jar stiamo importando i tipi di dati.

Sebbene Hibernate spatial supporti diversi database come Oracle, MySQL, PostgreSQLql / PostGIS e pochi altri, il supporto per le funzioni specifiche del database non è uniforme.

È meglio fare riferimento alla documentazione più recente di Hibernate per controllare l'elenco delle funzioni per le quali Hibernate fornisce supporto per un determinato database.

In questo articolo, utilizzeremo un Mariadb4j in memoria, che mantiene la piena funzionalità di MySQL.

La configurazione per Mariadb4j e MySql è simile, anche la libreria mysql-connector funziona per entrambi questi database.

3 . Dipendenze di Maven

Diamo uno sguardo alle dipendenze Maven richieste per impostare un semplice progetto di ibernazione spaziale:

 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 dipendenza ibernazione-spaziale è quella che fornirà il supporto per i tipi di dati spaziali. Le ultime versioni di hibernate-core, hibernate-spatial, mysql-connector-java e mariaDB4j possono essere ottenute da Maven Central.

4. Configurazione di Hibernate Spatial

Il primo passo è creare un hibernate.properties nella directory delle risorse :

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

L'unica cosa specifica per hibernate-spatial è il dialetto MySQL56SpatialDialect . Questo dialetto estende il dialetto MySQL55Dialect e fornisce funzionalità aggiuntive relative ai tipi di dati spaziali.

Il codice specifico per caricare il file delle proprietà, creare una SessionFactory e istanziare un'istanza Mariadb4j, è lo stesso di un progetto standard di ibernazione.

5 . Comprensione del tipo di geometria

La geometria è il tipo di base per tutti i tipi spaziali in JTS. Ciò significa che altri tipi come Point , Polygon e altri si estendono da Geometry . Il tipo Geometry in java corrisponde anche al tipo GEOMETRY in MySql.

Analizzando una rappresentazione String del tipo, otteniamo un'istanza di Geometry . Una classe di utilità WKTReader fornita da JTS può essere utilizzata per convertire qualsiasi rappresentazione di testo nota in un tipo di geometria :

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

Vediamo ora questo metodo in azione:

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

Come possiamo vedere, anche se il tipo restituito dal metodo è read () method è Geometry , l'istanza effettiva è quella di un Point .

6. Memorizzazione di un punto in DB

Ora che abbiamo una buona idea di cosa sia un tipo Geometry e di come ottenere un punto da una stringa , diamo un'occhiata a PointEntity :

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

Notare che l'entità PointEntity contiene un tipo spaziale Point . Come dimostrato in precedenza, un punto è rappresentato da due coordinate:

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

Il metodo insertPoint () accetta una rappresentazione WKT (well-known text) di un punto , la converte in un'istanza di Point e la salva nel DB.

Come promemoria, la sessione non è specifica per l'ibernazione spaziale e viene creata in un modo simile a un altro progetto di ibernazione.

Possiamo notare qui che una volta creata un'istanza di Point , il processo di memorizzazione di PointEntity è simile a qualsiasi entità regolare.

Diamo un'occhiata ad alcuni test:

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

La chiamata a toString () su un punto restituisce la rappresentazione WKT di un punto . Questo perché la classe Geometry sovrascrive il metodo toString () e utilizza internamente WKTWriter, una classe complementare a WKTReader che abbiamo visto in precedenza.

Una volta eseguito questo test, hibernate creerà per noi la tabella PointEntity .

Diamo un'occhiata a quella tabella:

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

Come previsto, il tipo di punto di campo è GEOMETRIA . Per questo motivo, durante il recupero dei dati utilizzando il nostro editor SQL (come il workbench MySql), dobbiamo convertire questo tipo GEOMETRY in testo leggibile dall'uomo:

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

Tuttavia, poiché hibernate restituisce già la rappresentazione WKT quando chiamiamo il metodo toString () su Geometry o su una qualsiasi delle sue sottoclassi, non dobbiamo preoccuparci di questa conversione.

7. Utilizzo delle funzioni spaziali

7.1. ST_WITHIN () Esempio

Daremo ora uno sguardo all'uso delle funzioni di database che funzionano con i tipi di dati spaziali.

Una di queste funzioni in MySQL è ST_WITHIN () che indica se una geometria è all'interno di un'altra. Un buon esempio qui sarebbe scoprire tutti i punti entro un dato raggio.

Cominciamo osservando come creare un cerchio:

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 cerchio è rappresentato da un insieme finito di punti specificato dal metodo setNumPoints () . Il raggio viene raddoppiato prima di chiamare il metodo setSize () poiché dobbiamo disegnare il cerchio attorno al centro, in entrambe le direzioni.

Andiamo ora avanti e vediamo come recuperare i punti entro un dato raggio:

@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 mappa i suoi all'interno () funzione al ST_Within () la funzione di MySql.

Un'osservazione interessante qui è che il Punto (3, 4) cade esattamente sul cerchio. Tuttavia, la query non restituisce questo punto. Questo perché la funzione within () restituisce true solo se la geometria data è completamente all'interno di un'altra geometria .

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.

Stiamo usando la funzione touches () per trovare i Polygon adiacenti a un dato Polygon . Chiaramente, il terzo Polygon non viene restituito nel risultato in quanto non c'è un bordo che tocca il Polygon dato .

8. Conclusione

In questo articolo, abbiamo visto che ibernazione-spaziale rende la gestione dei tipi di dati spaziali molto più semplice in quanto si prende cura dei dettagli di basso livello.

Anche se questo articolo utilizza Mariadb4j, possiamo sostituirlo con MySql senza modificare alcuna configurazione.

Come sempre, il codice sorgente completo di questo articolo può essere trovato su GitHub.