Pengantar Hibernate Spasial

1. Perkenalan

Pada artikel ini, kita akan melihat ekstensi spasial dari Hibernate, hibernate-spatial.

Dimulai dengan versi 5, Hibernate Spatial menyediakan antarmuka standar untuk bekerja dengan data geografis .

2. Latar Belakang Tata Ruang Hibernasi

Data geografis mencakup representasi entitas seperti Titik, Garis, Poligon . Tipe data tersebut bukan merupakan bagian dari spesifikasi JDBC, oleh karena itu JTS (JTS Topology Suite) telah menjadi standar untuk merepresentasikan tipe data spasial.

Selain JTS, Hibernate spatial juga mendukung Geolatte-geom - pustaka terbaru yang memiliki beberapa fitur yang tidak tersedia di JTS.

Kedua perpustakaan tersebut sudah termasuk dalam proyek hibernasi-spasial. Menggunakan satu pustaka di atas pustaka lainnya hanyalah pertanyaan dari jar mana kami mengimpor tipe data.

Meskipun Hibernate spatial mendukung database yang berbeda seperti Oracle, MySQL, PostgreSQLql / PostGIS, dan beberapa lainnya, dukungan untuk fungsi spesifik database tidak seragam.

Lebih baik merujuk ke dokumentasi Hibernate terbaru untuk memeriksa daftar fungsi yang hibernate berikan dukungannya untuk database tertentu.

Pada artikel ini, kita akan menggunakan Mariadb4j dalam memori - yang mempertahankan fungsionalitas penuh MySQL.

Konfigurasi untuk Mariadb4j dan MySql serupa, bahkan pustaka konektor mysql berfungsi untuk kedua database ini.

3 . Dependensi Maven

Mari kita lihat dependensi Maven yang diperlukan untuk menyiapkan proyek spasial-hibernasi 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 hibernate-spasial ketergantungan adalah salah satu yang akan memberikan dukungan untuk jenis data spasial. Versi terbaru dari hibernate-core, hibernate-spatial, mysql-connector-java, dan mariaDB4j dapat diperoleh dari Maven Central.

4. Konfigurasi Spasial Hibernasi

Langkah pertama adalah membuat hibernate.properties di direktori sumber daya :

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

Satu-satunya hal yang spesifik untuk hibernasi-spasial adalah dialek MySQL56SpatialDialect . Dialek ini memperluas dialek MySQL55Dialect dan menyediakan fungsionalitas tambahan yang terkait dengan tipe data spasial.

Kode khusus untuk memuat file properti, membuat SessionFactory , dan membuat instance Mariadb4j, sama seperti dalam proyek hibernasi standar.

5 . Memahami Jenis Geometri

Geometri adalah tipe dasar untuk semua tipe spasial di JTS. Ini berarti bahwa tipe lain seperti Titik , Poligon , dan lainnya diperluas dari Geometri . The Geometry ketik berkorespondensi java ke GEOMETRI ketik MySql juga.

Dengan mem-parsing representasi String dari tipe tersebut, kita mendapatkan instance Geometry . Kelas utilitas WKTReader yang disediakan oleh JTS dapat digunakan untuk mengonversi representasi teks terkenal ke jenis Geometri :

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

Sekarang, mari kita lihat metode ini beraksi:

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

Seperti yang bisa kita lihat, bahkan jika tipe kembalian metode read () method adalah Geometri , instance sebenarnya adalah Point .

6. Menyimpan Point di DB

Sekarang kita memiliki ide bagus tentang apa itu tipe Geometri dan bagaimana cara mendapatkan Point dari sebuah String , mari kita lihat PointEntity :

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

Perhatikan bahwa entitas PointEntity berisi Point tipe spasial . 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); }

Metode insertPoint () menerima representasi teks terkenal (WKT) dari sebuah Titik , mengubahnya menjadi instans Titik , dan menyimpannya di DB.

Sebagai pengingat, sesi ini tidak khusus untuk hibernasi-spasial dan dibuat dengan cara yang mirip dengan proyek hibernasi lainnya.

Kita dapat melihat di sini bahwa setelah kita membuat instance Point , proses penyimpanan PointEntity mirip dengan entitas biasa.

Mari kita lihat beberapa tes:

@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 toString () pada suatu Titik mengembalikan representasi WKT dari suatu Titik . Ini karena kelas Geometri menimpa metode toString () dan secara internal menggunakan WKTWriter, kelas pelengkap untuk WKTReader yang kita lihat sebelumnya.

Setelah kita menjalankan tes ini, hibernate akan membuat tabel PointEntity untuk kita.

Mari kita lihat tabel itu:

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

Seperti yang diharapkan, Jenis dari Lapangan Titik adalah GEOMETRI . Karena itu, saat mengambil data menggunakan editor SQL kami (seperti meja kerja MySql), kami perlu mengonversi jenis GEOMETRI ini menjadi teks yang dapat dibaca manusia:

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

Namun, karena hibernate sudah mengembalikan representasi WKT ketika kita memanggil metode toString () pada Geometri atau subkelasnya, kita tidak perlu repot-repot tentang konversi ini.

7. Menggunakan Fungsi Spasial

7.1. Contoh ST_WITHIN ()

Sekarang kita akan melihat penggunaan fungsi database yang bekerja dengan tipe data spasial.

Salah satu fungsi di MySQL adalah ST_WITHIN () yang memberitahukan apakah satu Geometri ada di dalam geometri lainnya. Contoh yang bagus di sini adalah menemukan semua titik dalam radius tertentu.

Mari kita mulai dengan melihat cara membuat lingkaran:

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

Sebuah lingkaran diwakili oleh sekumpulan titik terbatas yang ditentukan oleh metode setNumPoints () . Jari - jari digandakan sebelum memanggil metode setSize () karena kita perlu menggambar lingkaran di sekitar pusat, di kedua arah.

Sekarang mari kita bergerak maju dan melihat cara mengambil 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 peta nya dalam () fungsi untuk ST_WITHIN () fungsi MySql.

Pengamatan yang menarik di sini adalah bahwa Titik (3, 4) jatuh tepat pada lingkaran. Namun, kueri tidak mengembalikan poin ini. Ini karena fungsi within () mengembalikan nilai true hanya jika Geometri yang diberikan sepenuhnya 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 touches () untuk menemukan Poligon bersebelahan dengan Poligon tertentu . Jelas, Poligon ketiga tidak dikembalikan dalam hasil karena tidak ada tepi yang menyentuh Poligon yang diberikan .

8. Kesimpulan

Dalam artikel ini, kita telah melihat bahwa hibernate-spatial membuat berurusan dengan tipe data spasial jauh lebih sederhana karena menangani detail tingkat rendah.

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

Seperti biasa, kode sumber lengkap untuk artikel ini dapat ditemukan di GitHub.