Hibernate Spatialの紹介
1前書き
この記事では、Hibernateの空間的拡張について調べます。http://www.hibernatespatial.org[hibernate-spatial]。
バージョン5から、Hibernate Spatialは地理データを扱うための標準的なインターフェースを提供します。
2. Hibernate Spatialの背景
地理データには、Point、Line、Polygonなどのエンティティの表現が含まれます。このようなデータ型はJDBC仕様の一部ではないため、http://www.tsusiatsoftware.net/jts/main.html[JTS(JTS Topology Suite)]が空間データ型を表すための標準になりました。
JTSとは別に、Hibernate spatialはhttps://github.com/GeoLatte/geolatte-geom[Geolatte-geom]もサポートしています – これはJTSでは利用できない機能がいくつかある最近のライブラリです。
両方のライブラリーは既にHibernate-spatialプロジェクトに含まれています。
あるライブラリを他のライブラリよりも使用するのは、どのjarからデータ型をインポートするのかという問題です。
Hibernate Spatialは、Oracle、MySQL、PostgreSQLql/PostGISなどのさまざまなデータベースをサポートしますが、データベース固有の機能のサポートは一様ではありません。
最新のHibernateのドキュメントを参照して、Hibernateが特定のデータベースをサポートしている関数のリストを確認することをお勧めします。
この記事では、インメモリhttps://github.com/vorburger/MariaDB4j[Mariadb4j]を使用します。これはMySQLの全機能を維持します。
Mariadb4jとMySqlの設定は似ていますが、mysql-connectorライブラリでもこれら両方のデータベースで動作します。
3
Mavenの依存関係
単純な休止状態空間プロジェクトを設定するために必要なMavenの依存関係を見てみましょう。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.2.12.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
<version>5.2.12.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>ch.vorburger.mariaDB4j</groupId>
<artifactId>mariaDB4j</artifactId>
<version>2.2.3</version>
</dependency>
hibernate-spatial
依存関係は、空間データ型をサポートするものです。
hibernate-entitymanager
の最新バージョンhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.hibernate%22%20AND%20a%3A%22hibernate-spatial%22[hibernate-spatial]、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22mysql%22%20AND%20a%3A%22mysql-connector-java%22[mysql-connector-java]、およびhttps://検索.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22ch.vorburger.mariaDB4j%22%20AND%20a%3A%22mariaDB4j%22[mariaDB4j]はMaven Centralから入手できます。
4. Hibernate Spatialの設定
最初のステップは、
resources
ディレクトリに
hibernate.properties
を作成することです。
hibernate.dialect=org.hibernate.spatial.dialect.mysql.MySQL56SpatialDialect//...
-
hibernate-spatialに特有のものは
MySQL56SpatialDialect
方言** だけです。この方言は
MySQL55Dialect
方言を拡張し、空間データ型に関連する追加機能を提供します。
プロパティファイルのロード、
SessionFactory
の作成、Mariadb4jインスタンスのインスタンス化に固有のコードは、標準のHibernateプロジェクトと同じです。
5
Geometry
タイプについて
Geometry
は、JTSのすべての空間タイプの基本タイプです。これは、
Point
、
Polygon
などの他の型が
Geometry
から継承されることを意味します。 javaの
Geometry
タイプは、MySqlの
GEOMETRY
タイプにも対応しています。
型の
String
表現を解析することによって、
Geometry
のインスタンスを取得します。 JTSが提供するユーティリティクラス
WKTReader
は、https://en.wikipedia.org/wiki/Well-known
text[well-known text]表現を
Geometry__タイプに変換するために使用できます。
public Geometry wktToGeometry(String wellKnownText)
throws ParseException {
return new WKTReader().read(wellKnownText);
}
それでは、このメソッドの動作を見てみましょう。
@Test
public void shouldConvertWktToGeometry() {
Geometry geometry = wktToGeometry("POINT (2 5)");
assertEquals("Point", geometry.getGeometryType());
assertTrue(geometry instanceof Point);
}
ご覧のとおり、メソッドの戻り型が
read()
methodが
Geometry
の場合でも、実際のインスタンスは
Point
のインスタンスです。
6. DBにポイントを保存する
Geometry
タイプとは何か、そして
String
から
Point
を取得する方法についてはよくわかったので、次に
PointEntity
を見てみましょう。
@Entity
public class PointEntity {
@Id
@GeneratedValue
private Long id;
private Point point;
//standard getters and setters
}
エンティティ
PointEntity
には、空間タイプ
Point
が含まれています。前述のように、
Point
は2つの座標で表されます。
public void insertPoint(String point) {
PointEntity entity = new PointEntity();
entity.setPoint((Point) wktToGeometry(point));
session.persist(entity);
}
メソッド
insertPoint()
は、
Point
のWell-Known Text(WKT)表現を受け入れ、それを
Point
インスタンスに変換してDBに保存します。
ちなみに、
session
はHibernate-spatialに固有のものではなく、他のHibernateプロジェクトと同じように作成されます。
Point
のインスタンスを作成したら、
PointEntity
を格納するプロセスは通常のエンティティと似ていることがわかります。
いくつかのテストを見てみましょう。
@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);
}
Point
で
toString()
を呼び出すと、
Point
のWKT表現が返されます。これは、
Geometry
クラスが
toString()メソッドをオーバーライドし、内部で
WKTWriter、
WKTReader
の補足クラスを使用したためです。
このテストを実行すると、Hibernateは
PointEntity
テーブルを作成します。
その表を見てみましょう。
desc PointEntity;
Field Type Null Key
id bigint(20) NO PRI
point geometry YES
予想通り、
Field
Point
の
Type
は
GEOMETRY__です。このため、(MySqlワークベンチのような)SQLエディタを使ってデータを取得している間、このGEOMETRY型を人間が読めるテキストに変換する必要があります。
select id, astext(point) from PointEntity;
id astext(point)
1 POINT(2 4)
ただし、
Geometry
またはそのサブクラスの
toString()
メソッドを呼び出すと、HibernateはすでにWKT表現を返すので、この変換について気にする必要はありません。
** 7. 空間関数の使い方
7.1.
ST
WITHIN()__例
空間データ型を扱うデータベース関数の使い方を見てみましょう。
MySQLでのそのような関数の1つは
ST
WITHIN()
で、これは1つの
Geometry__が別のもの内にあるかどうかを伝えます。ここでの良い例は、与えられた半径内のすべての点を見つけることです。
円の作り方を見てみましょう。
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();
}
円は
setNumPoints()
メソッドで指定された有限個の点集合で表されます。
setius()
メソッドを呼び出す前に
radius
を2倍して、中心の周りに両方向に円を描く必要があります。
それでは先に進み、与えられた半径内で点を取得する方法を見てみましょう:
@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は、その
within()
関数をMySqlの
ST
WITHIN()__関数にマップします。
ここでの興味深い観察は、Point(3、4)が正確に円の上にあることです。それでも、クエリはこの点を返しません。これは
指定された
Geometry
が別の
Geometry
内に完全に収まる場合にのみ
within()
関数がtrueを返すからです。
7.2.
ST
TOUCHES()__例
ここでは、データベースに
__Polygon
のセットを挿入し、特定の
Polygon
に隣接する
Polygon
を選択する例を示します。
PolygonEntity__クラスを簡単に見てみましょう。
@Entity
public class PolygonEntity {
@Id
@GeneratedValue
private Long id;
private Polygon polygon;
//standard getters and setters
}
ここで前の
PointEntity
と異なるのは、
Point
ではなく
Polygon
型を使用していることだけです。
それではテストに進みましょう。
@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))");
}
insertPolygon()
メソッドは、前に見た
insertPoint()
メソッドと似ています。ソースにはこのメソッドの完全な実装が含まれています。
touches()
関数を使用して、特定の
Polygon
に隣接する
__Polygon
を見つけます。明らかに、3番目の
Polygon
は与えられた
Polygon__に接触する辺がないので結果には返されません。
8.まとめ
この記事では、Hibernate-spatialが低レベルの詳細を処理するため、空間データ型の扱いがはるかに簡単になることを確認しました。
この記事ではMariadb4jを使用していますが、設定を変更することなくMySqlに置き換えることができます。
いつものように、この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate5[GitHubに載せて]をご覧ください。