Spring 查询:Haversine 公式可分页

Spring Query: Haversine formula with pageable

我正在尝试使用 Haversine formula to find entities near to a location in a Spring Data JPA Query with Pageable 但我没有完成它。

我的第一个方法是这样的

    @Query("SELECT m, (6371 * acos(cos(radians(:latitude)) * cos(radians(m.latitude)) * cos(radians(m.longitude) - radians(:longitude)) + sin(radians(:latitude)) * sin(radians(m.latitude)))) as dist FROM Entity m WHERE dist < :distance ORDER BY dist DESC")
    public List<Entity> findEntitiesByLocation(@Param("latitude") final double latitude, @Param("longitude") final double longitude, @Param("distance") final double distance, Pageable pageable);

但它失败了,因为 Spring/JPA 似乎无法在 where 子句中使用别名。堆栈跟踪中的 SQL 看起来像这样

select (...),(haversine) as col_1_0_ where dist<? order by col_1_0_ DESC

所以where子句中的别名没有被替换。使用 "col_1_0_"(不带 ")代替 dist 也不起作用。

根据此 SO Answer,至少 MySQL 被解释为由内而外,并且在 Where 子句中使用别名是不可能的。建议的解决方案是使用 HAVING 而不是 WHERE,但在 HAVING 子句中,别名未解析。

我知道我可以将 Haversine 公式移动到 where 子句中,但我在 Order By 子句中仍然需要它,我认为它可能会降低性能,因为在 Order By 子句中使用与我相同的长 Haversine 公式select 来自几十万个实体。

然后我尝试手动创建查询,但我不知道如何将 Pageable 应用于此:

    @Override
    public List<Entity> findEntitiesByLocation(final double latitude, final double longitude, final double distance, Pageable pageable) {
    final javax.persistence.Query query = this.entityManager.createQuery(SELECT_ENTITES_BY_DISTANCE);

    query.setParameter("latitude", latitude);
    query.setParameter("longitude", longitude);
    query.setParameter("distance", distance);

    final List<Entity> entities = query.getResultList();
    // order by distance
    entities .sort(new EntityDistanceComparator(latitude, longitude));

    return entities ;
}

所以,我要么需要使用 @Query 的第一种方法(我更喜欢),要么需要使用 Pageable

的第二种方法

JPQL 不允许在 WHERE、HAVING 子句中使用 "result aliases";它们只能在 ORDER 子句中使用。类似地,RADIANS、COS、ACOS、SIN、ASIN 等的使用是不可移植的 JPQL;有些实现可能支持它们(我知道 DataNucleus JPA 支持,而且看起来你也有 Hibernate)但不能保证。

只需使用 NativeQuery 并输入您需要的任何内容 SQL。您失去了 (RDBMS) 的可移植性,但是您在上面尝试的内容(对于 JPA 提供程序或 RDBMS)却没有。

在 Neil Stockton 的帮助下,我决定坚持在 WHERE 和 ORDER BY 子句中使用 Haversine 公式的非本机查询,这样我仍然可以使用 Spring 的分页功能。我的最终解决方案如下所示:

static final String HAVERSINE_PART = "(6371 * acos(cos(radians(:latitude)) * cos(radians(m.latitude)) * cos(radians(m.longitude) - radians(:longitude)) + sin(radians(:latitude)) * sin(radians(m.latitude))))";

@Query("SELECT m FROM Entity m WHERE "+HAVERSINE_PART+" < :distance ORDER BY "+HAVERSINE_PART+" DESC")
public List<Entity> findEntitiesByLocation(@Param("latitude") final double latitude, @Param("longitude") final double longitude, @Param("distance") final double distance, Pageable pageable);

对于 100.000 个实体,这大约需要 585 毫秒来找到距离给定位置最近的前 10 个实体,而在 1.000.000 个实体中找到前 10 个最近的实体大约需要 8 秒,这对我来说现在没问题。如果我优化查询,我将 post 放在这里。