Hibernate-Spatial在Oracle12c下em.merge时SDO_GEOMETRY对象为空
嘿,针对你用Hibernate 5.0.9 + Hibernate-Spatial 5.0.9 Final + Oracle 12c时,执行em.merge()出现SDO_GEOMETRY对象为空的问题,结合你的业务场景(SQL*Loader导临时表后合并到基础表,还要做点位距离查询),我整理了几个实用的排查方向和解决方案:
1. 先检查实体类的空间映射配置
Hibernate Spatial对Oracle的SDO_GEOMETRY映射有严格要求,别踩配置坑:
- 必须给几何字段加上
@Type(type = "org.hibernate.spatial.GeometryType")注解,不能用普通JPA类型。 - 实体里的几何字段得是
com.vividsolutions.jts.geom.Point类型(Hibernate Spatial 5.x依赖JTS库),别直接用Oracle原生的SDO_GEOMETRY对象。
给你个示例代码参考:import com.vividsolutions.jts.geom.Point; import org.hibernate.annotations.Type; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class PointEntity { @Id private String businessKey; // 你的业务键 @Column(name = "GEOM_COLUMN") // 对应数据库里的SDO_GEOMETRY列名 @Type(type = "org.hibernate.spatial.GeometryType") private Point location; // getter、setter自己补上哈 }
2. 确认方言用对了
你提到用了org.hibernate.dialect.Oracle...,这里一定要用Hibernate Spatial专门提供的Oracle方言,普通Oracle方言不支持空间类型:
- 针对Oracle 12c,优先用
org.hibernate.spatial.dialect.oracle.OracleSpatial12cDialect,如果版本不兼容就退用org.hibernate.spatial.dialect.oracle.OracleSpatialDialect。 - 配置的时候要在
persistence.xml或者你的Spring配置里写对:<property name="hibernate.dialect" value="org.hibernate.spatial.dialect.oracle.OracleSpatial12cDialect"/>
3. 排查SQL*Loader导入的几何数据合法性
有时候不是Hibernate的问题,是SQL*Loader导进临时表的SDO_GEOMETRY本身就有问题,导致Hibernate读不出来:
- 确保临时表的几何数据是合法的Oracle空间格式,比如用
SDO_GEOMETRY(2001, 4326, SDO_POINT_TYPE(经度, 纬度, null), null, null)生成的点位。 - 先在数据库里跑个验证SQL,看看数据有效不:
返回SELECT sdo_geom.validate_geometry(geom_column, 0.005) FROM temp_point_table;TRUE才是有效的几何数据,要是返回错误信息,先把数据格式修好再说。
4. 合并时的实体加载要完整
执行em.merge()的时候,如果你的实体是从临时表查出来的,别只查业务键,得把几何字段一起查出来,避免懒加载(虽然Hibernate Spatial默认是立即加载,但保险起见):
- 别写这种只查业务键的语句:
SELECT t.businessKey FROM TempPoint t,要查完整实体或者包含几何字段的结果:TempPoint tempPoint = em.createQuery("SELECT t FROM TempPoint t WHERE t.businessKey = :key", TempPoint.class) .setParameter("key", targetKey) .getSingleResult();
5. 检查JDBC驱动版本兼容性
Hibernate Spatial 5.0.9得配兼容的Oracle JDBC驱动,建议用Oracle 12c对应的ojdbc7或ojdbc8,版本不匹配很容易出类型解析错误。
6. 实在不行就手动处理几何字段赋值
如果上面的方法都没解决,试试手动把临时表的SDO_GEOMETRY转成JTS Point对象再合并:
- 先用Oracle的空间函数把SDO_GEOMETRY转成WKT格式,再用JTS的
WKTReader解析成Point:// 从临时表取WKT格式的几何数据 String wkt = (String) em.createNativeQuery("SELECT sdo_util.to_wktgeometry(geom_column) FROM temp_point_table WHERE business_key = :key") .setParameter("key", businessKey) .getSingleResult(); // 转成JTS Point Point point = new WKTReader().read(wkt); // 合并到基础表 PointEntity baseEntity = em.find(PointEntity.class, businessKey); if (baseEntity != null) { baseEntity.setLocation(point); em.merge(baseEntity); } else { // 新增逻辑 PointEntity newEntity = new PointEntity(); newEntity.setBusinessKey(businessKey); newEntity.setLocation(point); em.persist(newEntity); }
最后,顺便给你提一嘴后续的距离查询怎么写,用Hibernate Spatial的SpatialRestrictions.dwithin就行,挺方便的:
import org.hibernate.spatial.criterion.SpatialRestrictions; import com.vividsolutions.jts.geom.Point; // 目标点位 Point targetPoint = ...; // 距离(单位要和你的空间参考系一致,比如用4326的话可能是度,用3857就是米) double distance = 1000; // 查询范围内的点位 List<PointEntity> nearbyPoints = em.createCriteria(PointEntity.class) .add(SpatialRestrictions.dwithin("location", targetPoint, distance)) .list();
内容的提问来源于stack exchange,提问作者Kaspatoo

