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に載せて]をご覧ください。