使用 Query DSL 按给定公式 (Haversine) 排序

Sort by given formula (Haversine) with Query DSL

我的 Location 实体中有 latitudelongitude。现在我正在使用类似的东西通过给定的坐标获得最近的位置。

String haversine = "(3956 * 2 * ASIN(SQRT(" +
        "POWER(SIN((l.latitude - ?1) *  pi()/180 / 2), 2) +" +
        "COS(l.latitude * pi()/180) * COS(?1 * pi()/180) *" +
        "POWER(SIN((l.longitude - ?2) * pi()/180 / 2), 2) )))";

@Query(value = "SELECT l FROM Location l ORDER BY " + haversine)
List<Location> findNearest(String latitude, String longitude);

但现在我想使用查询 DSL 自定义此查询。我正在尝试找出如何使用 OrderSpecifier<?>

按给定公式和实体值对记录进行排序

也许我应该在我的 Location 实体中创建方法,其中 returns 我的位置和给定坐标之间的距离。但是我认为在 MODEL 层中创建方法不是解决这个问题的最佳方法。

所以我的主要问题是:如何在查询 DSL 中按给定公式(例如 Haversine)对记录进行排序

据我所知,haversine 只是一个公式(即一个函数),带有两个参数纬度和经度。实际上,在模型内部创建方法并不是最好的方法。您应该使用 LocationService class 和 @Service(如果您使用的是 Spring 或 Springboot),其中包含所有程序逻辑。在那里你可以有一个计算距离的函数,然后将它传递给使用 QueryDSL 进行排序的函数。

像这样:

public class Location {
  private int latitude;
  private int longitude;
}

@Service
public class LocationServices {

  public String haversineCalculation(int locationLat, int locationLong, int latitude, int longitude) {
    //Do your calculations here in java
  }

  public List<Location> findNearest(int latitude, int longitude) {
    QLocation location = QLocation.location;
    SQLTemplates dialect = new YourSQLDialectTemplate();
    SQLQuery query = new SQLQueryImpl(connection, dialect);
    List<Location> locations = query
      .from(location);
    locations.sort((loc1, loc2) -> haversineCalculation(loc1.latitude, loc1.longitude, latitude, longitude).compareTo(haversineCalculation(loc1.latitude, loc1.longitude, latitude, longitude)));
  }
}

不幸的是,我不认为 OrderSpecifier 可以执行您希望的顺序,因为涉及到整个函数,并且顺序说明符似乎只能将实体的属性作为顺序描述符.

我刚刚使用了 Apache Lucene from Hibernate Search,代码如下所示。

 FullTextEntityManager fullTextSession = Search.getFullTextEntityManager(em);

    QueryBuilder builder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Place.class).get();

    Double centerLatitude = 51d;
    Double centerLongitude = 17d;
    org.apache.lucene.search.Query luceneQuery = builder
            .spatial()
            .within(1000, Unit.KM)
            .ofLatitude(centerLatitude)
            .andLongitude(centerLongitude)
            .createQuery();

    javax.persistence.Query jpaQuery =
            fullTextSession.createFullTextQuery(luceneQuery, Place.class);

    em.close();
    return (List<Place>) jpaQuery.getResultList();