Pengenalan Hibernate Spatial

1. Pengenalan

Dalam artikel ini, kita akan melihat lanjutan ruang Hibernate, hibernate-spatial.

Bermula dengan versi 5, Hibernate Spatial menyediakan antara muka standard untuk bekerja dengan data geografi .

2. Latar Belakang mengenai Hibernate Spatial

Data geografi merangkumi perwakilan entiti seperti Titik, Garis, Poligon . Jenis data seperti itu bukan sebahagian dari spesifikasi JDBC, oleh itu JTS (JTS Topology Suite) telah menjadi standard untuk mewakili jenis data spasial.

Selain JTS, Hibernate spatial juga menyokong Geolatte-geom - perpustakaan baru-baru ini yang mempunyai beberapa ciri yang tidak terdapat di JTS.

Kedua-dua perpustakaan sudah termasuk dalam projek hibernate-spatial. Menggunakan satu perpustakaan berbanding yang lain hanyalah persoalan dari balang mana yang kita mengimport jenis data.

Walaupun Hibernate spatial menyokong pangkalan data yang berbeza seperti Oracle, MySQL, PostgreSQLql / PostGIS, dan beberapa yang lain, sokongan untuk fungsi pangkalan data khusus tidak seragam.

Lebih baik merujuk kepada dokumentasi Hibernate terkini untuk memeriksa senarai fungsi yang hibernate memberikan sokongan untuk pangkalan data tertentu.

Dalam artikel ini, kita akan menggunakan Mariadb4j dalam memori - yang mengekalkan fungsi penuh MySQL.

Konfigurasi untuk Mariadb4j dan MySql serupa, malah perpustakaan penyambung mysql berfungsi untuk kedua-dua pangkalan data ini.

3 . Ketergantungan Maven

Mari lihat kebergantungan Maven yang diperlukan untuk menyiapkan projek hibernate-spatial sederhana:

 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  

The pendam-spatial pergantungan adalah salah satu yang akan memberi sokongan kepada jenis data spatial. Versi terbaru hibernate-core, hibernate-spatial, mysql-connector-java, dan mariaDB4j boleh didapati dari Maven Central.

4. Mengkonfigurasi Hibernate Spatial

Langkah pertama adalah membuat hibernate.properties dalam direktori sumber :

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

Satu-satunya perkara yang khusus untuk hibernate-spatial adalah dialek MySQL56SpatialDialect . Dialek ini meluaskan dialek MySQL55Dialect dan memberikan fungsi tambahan yang berkaitan dengan jenis data spatial.

Kod khusus untuk memuatkan fail harta tanah, membuat SessionFactory , dan membuat contoh Mariadb4j, sama seperti dalam projek hibernate standard.

5 . Memahami Jenis Geometri

Geometri adalah jenis asas untuk semua jenis ruang di JTS. Ini bermaksud bahawa jenis lain seperti Titik , Poligon , dan lain-lain merangkumi Geometri . The Geometry taip sepadan java kepada GEOMETRI taip MySql juga.

Dengan menguraikan representasi String dari jenis tersebut, kita mendapat contoh Geometri . WKTReader kelas utiliti yang disediakan oleh JTS boleh digunakan untuk menukar setiap perwakilan teks yang terkenal menjadi jenis Geometri :

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

Sekarang, mari kita lihat kaedah ini dalam tindakan:

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

Seperti yang kita lihat, walaupun jenis pengembalian metode dibaca () adalah Geometri , contoh sebenarnya adalah Titik .

6. Menyimpan Titik di DB

Sekarang kita mempunyai idea yang baik tentang jenis Geometri dan bagaimana mendapatkan Titik dari Rentetan , mari kita lihat PointEntity :

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

Perhatikan bahawa entiti PointEntity mengandungi jenis spatial Point . Seperti yang ditunjukkan sebelumnya, Titik diwakili oleh dua koordinat:

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

Kaedah insertPoint () menerima representasi teks terkenal (WKT) Titik , mengubahnya menjadi contoh Titik , dan menyimpan di DB.

Sebagai peringatan, sesi ini tidak khusus untuk hibernate-spatial dan dibuat dengan cara yang serupa dengan projek hibernate yang lain.

Kita dapat perhatikan di sini bahawa setelah kita membuat contoh Point dibuat, proses menyimpan PointEntity serupa dengan entiti biasa mana pun.

Mari lihat beberapa ujian:

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

Memanggil keString () pada Titik mengembalikan perwakilan Titik WKT . Ini kerana kelas Geometri mengganti kaedah toString () dan secara dalaman menggunakan WKTWriter, kelas percuma untuk WKTReader yang kita lihat sebelumnya.

Setelah kami menjalankan ujian ini, hibernate akan membuat jadual PointEntity untuk kami.

Mari lihat jadual itu:

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

Seperti yang dijangka, Jenis of Field Point adalah GEOMETRI . Oleh kerana itu, semasa mengambil data menggunakan editor SQL kami (seperti meja kerja MySql), kami perlu menukar jenis GEOMETRI ini menjadi teks yang dapat dibaca manusia:

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

Namun, kerana hibernate sudah mengembalikan representasi WKT ketika kita memanggil kaedah toString () pada Geometri atau subkelasnya, kita tidak perlu repot mengenai penukaran ini.

7. Menggunakan Fungsi Spatial

7.1. ST_WITHIN () Contoh

Kita sekarang akan melihat penggunaan fungsi pangkalan data yang berfungsi dengan jenis data spasial.

Salah satu fungsi tersebut di MySQL adalah ST_WITHIN () yang memberitahu sama ada satu Geometri berada di dalam yang lain. Contoh yang baik di sini adalah untuk mengetahui semua titik dalam radius tertentu.

Mari mulakan dengan melihat cara membuat bulatan:

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

Lingkaran diwakili oleh sekumpulan titik terhingga yang ditentukan oleh kaedah setNumPoints () . The radius adalah dua kali ganda sebelum memanggil setSize () kaedah kerana kami perlu melukis bulatan di sekeliling pusat, dalam kedua-dua arah.

Sekarang mari kita maju dan melihat cara mendapatkan titik dalam radius tertentu:

@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 memetakan fungsi dalam () ke fungsi ST_WITHIN () MySql.

Pemerhatian yang menarik di sini ialah Titik (3, 4) jatuh tepat pada bulatan. Namun, pertanyaan tidak mengembalikan titik ini. Ini kerana fungsi dalam () kembali benar hanya jika Geometri yang diberikan sepenuhnya berada dalam Geometri lain .

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.

Kami menggunakan fungsi sentuhan () untuk mencari Poligon yang berdekatan dengan Poligon tertentu . Jelas, Poligon ketiga tidak dikembalikan dalam hasilnya kerana tidak ada tepi menyentuh Poligon yang diberikan .

8. Kesimpulannya

Dalam artikel ini, kami telah melihat bahawa hibernate-spatial menjadikan urusan dengan jenis data spatial menjadi lebih mudah kerana mengurus perincian tahap rendah.

Walaupun artikel ini menggunakan Mariadb4j, kami dapat menggantinya dengan MySql tanpa mengubah konfigurasi apa pun.

Seperti biasa, kod sumber penuh untuk artikel ini boleh didapati di GitHub.