问题描述
我正在创建一个应用程序,用户可以在其中通过许多各种(可选)过滤器过滤结果,其中一种是按接近度进行搜索。我正在使用org.springframework.data.jpa.domain.Specification根据提供的参数动态构建查询,其中大多数参数都是简单的ge / le一些整数值。 我的where子句:(location_lng / lat是数据库中的值,其中数字与查询一起提供坐标,并且
SELECT *
FROM rental
WHERE(
6371 * acos(
cos( radians(52.2477331) ) * cos( radians( location_lat ) )
*
cos( radians( location_lng ) - radians(21.0136079) )
+
sin( radians(52.2477331) )
*
sin( radians( location_lat ) )
)
) < 10
public Page<RentalDTO> getByCriteria(RentalSearchCriteria cr,Optional<Integer> page,Optional<String> sort,Optional<Integer> size,Optional<Sort.Direction> order) {
return rentalRepository.findAll(
where(
priceFrom(cr.getPriceFrom())
.and(inProximity(cr.getdist(),cr.getLat(),cr.getLng()))
.and(priceto(cr.getPriceto()))
.and(sizefrom(cr.getSizefrom()))
.and(sizeto(cr.getSizeto()))
.and(buildFrom(cr.getBuildFrom()))
.and(builtTo(cr.getBuildTo()))
.and(roomFrom(cr.getRoomFrom()))
.and(roomTo(cr.getRoomTo()))
.and(moveInAt(cr.getMoveInTo()))
.and(tagsIncluded(cr.getFeatures()))
),PageRequest.of(
page.orElse(0),size.orElse(3),order.orElse(Sort.Direction.ASC),sort.orElse("price"))
).map(RentalDTO::createFrom);
}
和说明(此为空):
public static Specification<Rental> inProximity(Integer distance,Double lat,Double lng) {
if (distance == null || lat == null || lng == null) {
return null;
} else {
return (root,query,cb) -> {
return null; // todo
};
}
}
该查询使用分组方式,而其他查询则不使用分组方式,但是肯定应该有某种方法可以使分组不使用分组方式,因为我不需要检查整个表来计算特定行的距离,可能需要一些子查询或这样,不是很确定如何解决这个问题。
解决方法
我最终创建了一个postgreSQL函数,并在规范中使用了该函数:
create or replace function distance(lat double precision,lng double precision,db_lat double precision,db_lng double precision)
returns double precision as $dist$
begin
return
6371 * acos(
cos(radians(lat)) * cos(radians(db_lat))
*
cos(radians(db_lng) - radians(lng))
+
sin(radians(lat))
*
sin(radians(db_lat))
);
end;
$dist$ language plpgsql;
Springboot:
public static Specification<Rental> inProximity(Double distance,Double lat,Double lng) {
if (distance == null || lat == null || lng == null) {
return null;
} else {
return (root,query,cb) ->
cb.lessThanOrEqualTo(
cb.function("distance",Double.class,cb.literal(lat),cb.literal(lng),root.get("locationLat"),root.get("locationLng")),distance
);
}
}