Spring JPA结合QueryDSL使用时抛出无法定位命名参数[longitude]异常
异常产生原因
- 核心原因是JPA 2.1及更早版本的规范限制:
ORDER BY子句仅允许引用实体持久化属性、SELECT子句定义的别名,不允许出现命名参数占位符。你使用的Hibernate 5.4.20版本默认遵循该规范,会直接把Haversine公式里的:latitude、:longitude识别为普通属性路径,不会解析为命名参数,最终参数元数据中仅存在WHERE子句里的:searchTerm,绑定另外两个参数时就会抛出参数不存在的异常。 - 现有WHERE子句存在逻辑优先级bug:
AND运算符优先级高于OR,你没有添加括号明确逻辑边界,实际执行的过滤条件为(c.dateDeleted is NULL AND c.title匹配搜索词) OR (c.description匹配搜索词),会查出已删除的无效数据。 - 直接写
like %:searchTerm%的写法兼容性差,部分Hibernate版本会出现参数解析偏移问题,官方推荐用CONCAT函数拼接模糊查询通配符。
修复方案
你可以根据自己的业务场景选以下任意一种方案:
方案1:最小改动,不调整返回结构
在WHERE子句末尾添加两个恒真条件,强制Hibernate解析到两个地理坐标参数,同时修复WHERE逻辑和模糊查询写法,不需要修改方法返回值,也不需要调整其他业务代码:
String HAVERSINE_FORMULA_IMPROVED = "(0.000621371 * 6378137 * 2 * atan2(" + " sqrt(sin(radians(c.geonamesPostal.latitude - :latitude) / 2) * sin(radians(c.geonamesPostal.latitude - :latitude) / 2)" + " + cos(radians(:latitude)) * cos(radians(c.geonamesPostal.latitude))" + " * sin(radians(c.geonamesPostal.longitude - :longitude) / 2) * sin(radians(c.geonamesPostal.longitude - :longitude) / 2))," + " sqrt(1 - sin(radians(c.geonamesPostal.latitude - :latitude) / 2) * sin(radians(c.geonamesPostal.latitude - :latitude) / 2)" + " + cos(radians(:latitude)) * cos(radians(c.geonamesPostal.latitude))" + " * sin(radians(c.geonamesPostal.longitude - :longitude) / 2) * sin(radians(c.geonamesPostal.longitude - :longitude) / 2))))"; @Query("SELECT c FROM Classified c WHERE c.dateDeleted is NULL AND (c.title LIKE CONCAT('%', :searchTerm, '%') OR c.description LIKE CONCAT('%', :searchTerm, '%')) " + "AND :latitude = :latitude AND :longitude = :longitude " // 恒真条件,仅用于让解析器识别命名参数 + "ORDER BY " + HAVERSINE_FORMULA_IMPROVED) Page<Classified> findNearestClassifieds(@Param("latitude") double latitude, @Param("longitude") double longitude, @Param("searchTerm") String searchTerm, Pageable page);
方案2:规范JPQL写法,支持返回距离值
将距离计算逻辑移到SELECT子句中定义别名,ORDER BY直接引用别名,同时添加countQuery适配分页查询。如果需要在业务层拿到计算出的距离值,推荐用这个方案:
- 首先定义投影接口接收返回结果:
public interface ClassifiedWithDistance { Classified getClassified(); Double getDistance(); } - 修改Repository方法:
String HAVERSINE_DISTANCE = "(0.000621371 * 6378137 * 2 * atan2(" + " sqrt(sin(radians(c.geonamesPostal.latitude - :latitude) / 2) * sin(radians(c.geonamesPostal.latitude - :latitude) / 2)" + " + cos(radians(:latitude)) * cos(radians(c.geonamesPostal.latitude))" + " * sin(radians(c.geonamesPostal.longitude - :longitude) / 2) * sin(radians(c.geonamesPostal.longitude - :longitude) / 2))," + " sqrt(1 - sin(radians(c.geonamesPostal.latitude - :latitude) / 2) * sin(radians(c.geonamesPostal.latitude - :latitude) / 2)" + " + cos(radians(:latitude)) * cos(radians(c.geonamesPostal.latitude))" + " * sin(radians(c.geonamesPostal.longitude - :longitude) / 2) * sin(radians(c.geonamesPostal.longitude - :longitude) / 2)))) as distance"; @Query( value = "SELECT c, " + HAVERSINE_DISTANCE + " FROM Classified c WHERE c.dateDeleted is NULL AND (c.title LIKE CONCAT('%', :searchTerm, '%') OR c.description LIKE CONCAT('%', :searchTerm, '%')) ORDER BY distance", countQuery = "SELECT count(c) FROM Classified c WHERE c.dateDeleted is NULL AND (c.title LIKE CONCAT('%', :searchTerm, '%') OR c.description LIKE CONCAT('%', :searchTerm, '%'))" ) Page<ClassifiedWithDistance> findNearestClassifieds(@Param("latitude") double latitude, @Param("longitude") double longitude, @Param("searchTerm") String searchTerm, Pageable page);
方案3:改用原生SQL查询
设置nativeQuery = true编写原生SQL,原生SQL不受JPA规范的ORDER BY参数限制,但需要注意表名、字段名要和数据库实际结构一致,不能使用实体类的属性名。
内容的提问来源于stack exchange,提问作者Trey
相关产品推荐
相关产品推荐

