2. Hibernate Spatial 背景
Hibernate Spatial 是 Hibernate 的空间扩展,从版本5开始,它提供了操作地理空间数据的标准化接口。
地理空间数据包含点(Point)、线(Line)、面(Polygon)等实体的表示。这些数据类型不在 JDBC 规范范围内,因此 JTS (JTS Topology Suite) 成为表示空间数据的事实标准。
除 JTS 外,Hibernate Spatial 还支持 Geolatte-geom —— 一个提供 JTS 缺失功能的新兴库。
两个库已集成在 hibernate-spatial 项目中,选择哪个库仅取决于导入数据类型的来源。
尽管 Hibernate Spatial 支持 Oracle、MySQL、PostgreSQL/PostGIS 等数据库,但对特定数据库函数的支持并不统一。建议查阅最新版 Hibernate 文档确认各数据库支持的函数列表。
本文使用内存数据库 Mariadb4j(保留 MySQL 全功能),其配置与 MySQL 兼容,甚至可直接使用 mysql-connector 驱动。
3. Maven 依赖
搭建基础 hibernate-spatial 项目需添加以下 Maven 依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-spatial</artifactId>
<version>6.4.2.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>ch.vorburger.mariaDB4j</groupId>
<artifactId>mariaDB4j</artifactId>
<version>2.6.0</version>
</dependency>
关键依赖 hibernate-spatial 提供空间数据类型支持。最新版本可通过 Maven Central 获取。
4. 配置 Hibernate Spatial
在 resources 目录创建 hibernate.properties:
hibernate.dialect=org.hibernate.dialect.H2Dialect
// ...
直接使用 H2 数据库的方言 H2Dialect,该方言默认包含空间数据类型支持。
属性文件加载、SessionFactory 创建和 Mariadb4j 实例化与标准 Hibernate 项目完全一致。
5. 理解 Geometry 类型
Geometry 是 JTS 所有空间类型的基类,Point、Polygon 等类型均继承自它。Java 中的 Geometry 对应 MySQL 的 GEOMETRY 类型。
通过解析字符串表示可获取 Geometry 实例,JTS 提供的 WKTReader 工具类能将 well-known text(WKT)转换为 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() 返回类型为 Geometry,但实际实例是具体子类(如 Point)。
6. 存储点到数据库
了解 Geometry 类型后,我们定义 PointEntity:
@Entity
public class PointEntity {
@Id
@GeneratedValue
private Long id;
private Point point;
// 标准getter/setter
}
实体包含空间类型 Point,由两个坐标表示:
public void insertPoint(String point) {
PointEntity entity = new PointEntity();
entity.setPoint((Point) wktToGeometry(point));
session.persist(entity);
}
insertPoint() 接收 WKT 格式的点,转换后存入数据库。
✅ 关键:Point 实例化后,存储流程与常规实体完全一致。
测试用例:
@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);
}
调用 toString() 返回 WKT 表示,因 Geometry 类内部使用 WKTWriter(与 WKTReader 配对)。
测试执行后自动创建的表结构:
desc PointEntity;
Field Type Null Key
id bigint(20) NO PRI
point geometry YES
✅ 验证:point 字段类型为 GEOMETRY。在 SQL 客户端查询时需转换格式:
select id, astext(point) from PointEntity;
id astext(point)
1 POINT(2 4)
但 Hibernate 的 toString() 已处理转换,无需手动干预。
7. 使用空间函数
7.1. ST_WITHIN() 示例
演示空间函数用法,以 MySQL 的 ST_WITHIN() 为例,用于判断几何对象是否在指定范围内(如查找半径内的点)。
创建圆形几何对象:
public Geometry createCircle(double x, double y, double radius) {
GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
shapeFactory.setNumPoints(32); // 用32个点近似圆形
shapeFactory.setCentre(new Coordinate(x, y));
shapeFactory.setSize(radius * 2); // 半径需×2(双向扩展)
return shapeFactory.createCircle();
}
查询指定半径内的点:
@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() 函数。
❌ 踩坑:点 (3,4) 在圆周上但未被返回,因 within() 仅当几何对象完全在目标内部时返回 true。
7.2. ST_TOUCHES() 示例
插入多边形并查询相邻多边形(边接触)。定义 PolygonEntity:
@Entity
public class PolygonEntity {
@Id
@GeneratedValue
private Long id;
private Polygon polygon;
// 标准getter/setter
}
唯一区别是使用 Polygon 替代 Point。
测试用例:
@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() 查找相邻多边形,第三个多边形因无接触边被过滤。
8. 总结
Hibernate Spatial 通过封装底层细节,极大简化了空间数据操作。本文使用 Mariadb4j,但可无缝替换为 MySQL。
完整代码示例见 GitHub 仓库。