Giới thiệu về Không gian ngủ đông

1. Giới thiệu

Trong bài viết này, chúng ta sẽ xem xét phần mở rộng không gian của Hibernate, hibernate-spatial.

Bắt đầu từ phiên bản 5, Hibernate Spatial cung cấp một giao diện tiêu chuẩn để làm việc với dữ liệu địa lý .

2. Nền tảng về Không gian ngủ đông

Dữ liệu địa lý bao gồm đại diện của các thực thể như Điểm, Đường, Đa giác . Các kiểu dữ liệu như vậy không phải là một phần của đặc tả JDBC, do đó JTS (JTS Topology Suite) đã trở thành một tiêu chuẩn để đại diện cho các kiểu dữ liệu không gian.

Ngoài JTS, Hibernate spatial cũng hỗ trợ Geolatte-geom - một thư viện gần đây có một số tính năng không có trong JTS.

Cả hai thư viện đều đã được đưa vào dự án không gian-ngủ đông. Việc sử dụng thư viện này hơn thư viện khác chỉ đơn giản là câu hỏi chúng ta đang nhập các kiểu dữ liệu từ jar nào.

Mặc dù không gian Hibernate hỗ trợ các cơ sở dữ liệu khác nhau như Oracle, MySQL, PostgreSQLql / PostGIS và một số cơ sở dữ liệu khác, sự hỗ trợ cho các chức năng cụ thể của cơ sở dữ liệu không đồng nhất.

Tốt hơn hết bạn nên tham khảo tài liệu Hibernate mới nhất để kiểm tra danh sách các chức năng mà hibernate cung cấp hỗ trợ cho một cơ sở dữ liệu nhất định.

Trong bài viết này, chúng tôi sẽ sử dụng Mariadb4j trong bộ nhớ - duy trì chức năng đầy đủ của MySQL.

Cấu hình cho Mariadb4j và MySql tương tự nhau, ngay cả thư viện mysql-connector cũng hoạt động cho cả hai cơ sở dữ liệu này.

3 . Maven phụ thuộc

Chúng ta hãy xem xét các phụ thuộc Maven cần thiết để thiết lập một dự án không gian-ngủ đông đơn giản:

 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  

Sự phụ thuộc không gian-ngủ đông là sự phụ thuộc sẽ cung cấp hỗ trợ cho các kiểu dữ liệu không gian. Bạn có thể lấy phiên bản mới nhất của hibernate-core, hibernate-spatial, mysql-connector-java và mariaDB4j từ Maven Central.

4. Định cấu hình không gian ngủ đông

Bước đầu tiên là tạo một hibernate.properties trong thư mục tài nguyên :

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

Điều duy nhất dành riêng cho không gian ngủ đông là phương ngữ MySQL56SpatialDialect . Phương ngữ này mở rộng phương ngữ MySQL55Dialect và cung cấp chức năng bổ sung liên quan đến các kiểu dữ liệu không gian.

Mã cụ thể để tải tệp thuộc tính, tạo SessionFactory và khởi tạo phiên bản Mariadb4j, giống như trong một dự án ngủ đông tiêu chuẩn.

5 . Hiểu Geometry Loại

Hình học là kiểu cơ sở cho tất cả các kiểu không gian trong JTS. Điều này có nghĩa là các kiểu khác như Điểm , Đa giác , và những kiểu khác mở rộng từ Hình học . Kiểu Geometry trong java cũng tương ứng với kiểu GEOMETRY trong MySql.

Bằng cách phân tích cú pháp biểu diễn kiểu chuỗi , chúng ta nhận được một thể hiện của Geometry . Một lớp tiện ích WKTReader do JTS cung cấp có thể được sử dụng để chuyển đổi bất kỳ biểu diễn văn bản nổi tiếng nào sang kiểu Hình học :

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

Bây giờ, hãy xem phương pháp này hoạt động:

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

Như chúng ta có thể thấy, ngay cả khi kiểu trả về của phương thức read ()Geometry , thì trường hợp thực tế là của một Point .

6. Lưu trữ một điểm trong DB

Bây giờ chúng ta đã có một ý tưởng tốt về kiểu Geometry là gì và cách lấy một Điểm ra khỏi Chuỗi , hãy cùng xem qua PointEntity :

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

Lưu ý rằng thực thể PointEntity chứa Point kiểu không gian . Như đã trình bày trước đó, một Điểm được biểu diễn bằng hai tọa độ:

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

Phương thức insertPoint () chấp nhận một biểu diễn văn bản (WKT) nổi tiếng của một Điểm , chuyển đổi nó thành một cá thể Point và lưu trong DB.

Xin nhắc lại, phiên này không dành riêng cho không gian ngủ đông và được tạo theo cách tương tự như một dự án ngủ đông khác.

Chúng ta có thể nhận thấy ở đây rằng khi chúng ta đã tạo một thể hiện của Point , quá trình lưu trữ PointEntity cũng tương tự như bất kỳ thực thể thông thường nào.

Hãy xem một số thử nghiệm:

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

Gọi toString () trên một Điểm trả về biểu diễn WKT của một Điểm . Điều này là do lớp Geometry ghi đè phương thức toString () và sử dụng nội bộ WKTWriter, một lớp bổ sung cho WKTReader mà chúng ta đã thấy trước đó.

Khi chúng tôi chạy thử nghiệm này, hibernate sẽ tạo bảng PointEntity cho chúng tôi.

Hãy xem bảng đó:

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

Đúng như dự đoán, các loại của Dòng PointGEOMETRY . Do đó, trong khi tìm nạp dữ liệu bằng trình soạn thảo SQL của chúng tôi (như bàn làm việc MySql), chúng tôi cần chuyển đổi kiểu GEOMETRY này thành văn bản mà con người có thể đọc được:

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

Tuy nhiên, vì hibernate đã trả về biểu diễn WKT khi chúng ta gọi phương thức toString () trên Geometry hoặc bất kỳ lớp con nào của nó, chúng ta không cần phải bận tâm về việc chuyển đổi này.

7. Sử dụng các chức năng không gian

7.1. Ví dụ ST_WITHIN ()

Bây giờ chúng ta sẽ xem xét việc sử dụng các hàm cơ sở dữ liệu hoạt động với các kiểu dữ liệu không gian.

Một trong những hàm như vậy trong MySQL là ST_WITHIN () cho biết liệu một Hình học có nằm trong một Hình học khác hay không. Một ví dụ điển hình ở đây là tìm ra tất cả các điểm trong một bán kính nhất định.

Hãy bắt đầu bằng cách xem cách tạo một vòng kết nối:

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

Một vòng tròn được biểu diễn bởi một tập hợp hữu hạn các điểm được chỉ định bởi phương thức setNumPoints () . Các bán kính tăng gấp đôi trước khi gọi các setSize () phương pháp như chúng ta cần phải vẽ vòng tròn xung quanh trung tâm, trong cả hai hướng.

Bây giờ chúng ta hãy tiếp tục và xem cách lấy các điểm trong bán kính nhất định:

@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 ánh xạ hàm trong () của nó với hàm ST_WITHIN () của MySql.

Một quan sát thú vị ở đây là Điểm (3, 4) rơi chính xác trên đường tròn. Tuy nhiên, truy vấn không trả về điểm này. Điều này là do hàm trong () chỉ trả về true nếu Hình học đã cho hoàn toàn nằm trong Hình học khác .

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.

Chúng tôi đang sử dụng hàm touch () để tìm Đa giác liền kề với một Đa giác đã cho . Rõ ràng, Đa giác thứ ba không được trả về trong kết quả vì không có cạnh nào chạm vào Đa giác đã cho .

8. Kết luận

Trong bài viết này, chúng ta đã thấy rằng không gian ngủ đông làm cho việc xử lý các kiểu dữ liệu không gian đơn giản hơn rất nhiều vì nó xử lý các chi tiết cấp thấp.

Mặc dù bài viết này sử dụng Mariadb4j, chúng tôi có thể thay thế nó bằng MySql mà không cần sửa đổi bất kỳ cấu hình nào.

Như mọi khi, mã nguồn đầy đủ cho bài viết này có thể được tìm thấy trên GitHub.