Lucene Hibernate搜索查询所有相交点的实体
我正在开发一个有餐馆的应用程序,每个餐馆都有送货范围,并且需要为用户回答哪些餐馆可以送货到他/她的当前位置 我用hibernate spatial获得了一个简单的解决方案,但当我进入hibernate搜索,将全文搜索与地理搜索结合起来时(由于可伸缩性),我还没有找到解决方案。一些想法/建议/例子 例如,在Hibernate Spatial中,查询如下所示:Lucene Hibernate搜索查询所有相交点的实体,lucene,geolocation,hibernate-search,hibernate-spatial,Lucene,Geolocation,Hibernate Search,Hibernate Spatial,我正在开发一个有餐馆的应用程序,每个餐馆都有送货范围,并且需要为用户回答哪些餐馆可以送货到他/她的当前位置 我用hibernate spatial获得了一个简单的解决方案,但当我进入hibernate搜索,将全文搜索与地理搜索结合起来时(由于可伸缩性),我还没有找到解决方案。一些想法/建议/例子 例如,在Hibernate Spatial中,查询如下所示: SELECT r FROM Restaurant r WHERE within(:point, r.coverage) 显然,保险范围是指
SELECT r FROM Restaurant r WHERE within(:point, r.coverage)
显然,保险范围是指餐馆的保险范围
我认为hibernate搜索的解决方案是添加一个过滤器,但我找到的所有示例都不在类似的内容上。在一些小麻烦之后,我找到了一个解决方案 我实现了几何场的桥接:
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Geometry;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions;
/**
* @see https://docs.jboss.org/hibernate/stable/search/reference/en-US/html/ch04.html#section-custom-bridges
* @see http://www.gossamer-threads.com/lists/lucene/java-user/254444
*/
public class GeometryBridge implements FieldBridge {
// http://unterbahn.com/2009/11/metric-dimensions-of-geohash-partitions-at-the-equator/
public static final int GEOHASH_MAX_LEVELS = 9; // 4.78m
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
JtsSpatialContext spatialContext = JtsSpatialContext.GEO;
SpatialPrefixTree grid = new GeohashPrefixTree(spatialContext, 22);
// Preparing the tree strategy field
SpatialStrategy treeStrategy = new RecursivePrefixTreeStrategy(grid, name);
for (IndexableField field: treeStrategy.createIndexableFields(
new JtsGeometry((Geometry) value, spatialContext, false, true))) {
document.add(field);
}
// Preparing the verify strategy field
SerializedDVStrategy verifyStrategy = new SerializedDVStrategy(spatialContext, "serialized_" + name);
for (IndexableField field: verifyStrategy.createIndexableFields(
new JtsGeometry((Geometry) value, spatialContext, false, true))) {
document.add(field);
}
}
}
稍后将使用自定义筛选器筛选查询:
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.Point;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.hibernate.search.annotations.Factory;
import org.hibernate.search.filter.impl.CachingWrapperFilter;
/**
* @see https://vimeo.com/106843184
* @see http://www.slideshare.net/lucenerevolution/lucene-solr-4-spatial-extended-deep-dive
*/
public class CoverageFilterFactory {
public static final String NAME = "coverage";
private String field;
private double latitude;
private double longitude;
public void setField(String field) {
this.field = field;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
@Factory
public Filter getFilter() {
JtsSpatialContext spatialContext = JtsSpatialContext.GEO;
Point point = spatialContext.makePoint(latitude, longitude);
SpatialArgs spatialArgs = new SpatialArgs(SpatialOperation.Intersects, point);
SpatialPrefixTree grid = new GeohashPrefixTree(spatialContext, GeometryBridge.GEOHASH_MAX_LEVELS);
SpatialStrategy treeStrategy = new RecursivePrefixTreeStrategy(grid, field);
SerializedDVStrategy verifyStrategy = new SerializedDVStrategy(spatialContext, "serialized_" + field);
Query treeQuery = new ConstantScoreQuery(treeStrategy.makeFilter(spatialArgs));
Query combinedQuery = new FilteredQuery(treeQuery,
verifyStrategy.makeFilter(spatialArgs),
FilteredQuery.QUERY_FIRST_FILTER_STRATEGY);
return new CachingWrapperFilter(new QueryWrapperFilter(combinedQuery));
}
}
最后,这里是自定义hibernate存储库的代码:
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import org.apache.lucene.search.Sort;
import org.hibernate.search.annotations.Spatial;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.query.dsl.Unit;
import org.hibernate.search.spatial.DistanceSortField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
@Autowired
private EntityManager entityManager;
@Override
public Page<SearchResult> search(Locale locale, double latitude, double longitude,
Order.DeliveryMode deliveryMode, String text, Pageable pageRequest) {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
String analyzerName;
switch (locale.getLanguage()) {
case "es": analyzerName = SPANISH_NAME_FIELD_DISCRIMINATOR; break;
case "en": analyzerName = ENGLISH_NAME_FIELD_DISCRIMINATOR; break;
default: throw new RuntimeException("Unexpected language found");
}
QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder()
.forEntity(Company.class)
.overridesForField(Company_.name.getName(), analyzerName)
.overridesForField(Company_.groups.getName() + "." + Group_.name.getName(), analyzerName)
.overridesForField(Company_.products.getName() + "." + Product_.name.getName(), analyzerName)
.get();
org.apache.lucene.search.Query luceneQuery = queryBuilder
.spatial()
.within(30000, Unit.KM)
.ofLatitude(latitude)
.andLongitude(longitude)
.createQuery();
// Adding text if corresponds
if (text != null) {
luceneQuery = queryBuilder
.bool()
.must(luceneQuery)
.must(queryBuilder
.keyword()
.onField(Company_.name.getName())
.boostedTo(2f)
.andField(Company_.headline.getName())
.andField(Company_.groups.getName() + "." + Group_.name.getName())
.andField(Company_.products.getName() + "." + Product_.name.getName())
.matching(text)
.createQuery()
)
.createQuery();
}
// wrap Lucene query in a javax.persistence.Query
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery);
fullTextQuery.setProjection(FullTextQuery.SPATIAL_DISTANCE, FullTextQuery.THIS);
fullTextQuery.setSpatialParameters(latitude, longitude, Spatial.COORDINATES_DEFAULT_FIELD);
// Applying filters
if (deliveryMode != null) {
fullTextQuery.enableFullTextFilter(DeliveryModeFilterFactory.NAME).setParameter("deliveryMode", deliveryMode);
if (deliveryMode == Order.DeliveryMode.DELIVERY) {
fullTextQuery.enableFullTextFilter(CoverageFilterFactory.NAME)
.setParameter("field", "coverage")
.setParameter("latitude", latitude)
.setParameter("longitude", longitude);
}
}
// Sorting the results
Sort distanceSort = new Sort(new DistanceSortField(latitude, longitude, Spatial.COORDINATES_DEFAULT_FIELD));
fullTextQuery.setSort(distanceSort);
// Execute Search
// Caution: The number of results might be slightly different from getResultList().size() because getResultList() may be not in sync with the database at the time of query.
long total = fullTextQuery.getResultSize();
List<Object[]> result = fullTextQuery
.setMaxResults(pageRequest.getPageSize())
.setFirstResult(pageRequest.getOffset())
.getResultList();
// Transforming the results
List<SearchResult> content = result.stream()
.map(o -> new SearchResult((double) o[0], (Company) o[1]))
.collect(Collectors.toList());
return new PageImpl<>(content, pageRequest, total);
}
}
import java.util.List;
导入java.util.Locale;
导入java.util.stream.collector;
导入javax.persistence.EntityManager;
导入org.apache.lucene.search.Sort;
导入org.hibernate.search.annotations.Spatial;
导入org.hibernate.search.jpa.FullTextEntityManager;
导入org.hibernate.search.jpa.FullTextQuery;
导入org.hibernate.search.jpa.search;
导入org.hibernate.search.query.dsl.QueryBuilder;
导入org.hibernate.search.query.dsl.Unit;
导入org.hibernate.search.spatial.DistanceSortField;
导入org.springframework.beans.factory.annotation.Autowired;
导入org.springframework.data.domain.Page;
导入org.springframework.data.domain.PageImpl;
导入org.springframework.data.domain.Pageable;
公共类CompanyRepositoryImpl实现CompanyRepositoryCustom{
@自动连线
私人实体管理者实体管理者;
@凌驾
公共页面搜索(区域设置、双纬度、双经度、,
Order.DeliveryMode DeliveryMode、字符串文本、可分页页面请求){
FullTextEntityManager FullTextEntityManager=Search.getFullTextEntityManager(entityManager);
字符串分析器名称;
开关(locale.getLanguage()){
案例“es”:analyzerName=SPANISH_NAME_FIELD_DISCRIMINATOR;中断;
案例“en”:analyzerName=英文名称字段鉴别器;中断;
默认值:抛出新的RuntimeException(“发现意外语言”);
}
QueryBuilder QueryBuilder=fullTextEntityManager.getSearchFactory()
.buildQueryBuilder()
.forEntity(公司级)
.overridesForField(公司名称.getName(),analyzerName)
.overridesForField(公司名称.groups.getName()+““+”组名称.getName(),analyzerName)
.overridesForField(公司名称.products.getName()+““+”产品名称.getName(),analyzerName)
.get();
org.apache.lucene.search.Query luceneQuery=queryBuilder
.spatical()
.以内(30000,单位公里)
海拔高度(纬度)
.和经度(经度)
.createQuery();
//如果对应,则添加文本
如果(文本!=null){
luceneQuery=queryBuilder
.bool()
.must(luceneQuery)
.must(查询生成器)
.keyword()
.onField(公司名称.getName())
.boostedTo(2f)
.andField(公司标题.getName())
.andField(公司名称.groups.getName()+““+集团名称.getName())
.andField(公司名称.products.getName()+““+产品名称.getName())
.匹配(文本)
.createQuery()
)
.createQuery();
}
//在javax.persistence.query中包装Lucene查询
FullTextQuery FullTextQuery=fullTextEntityManager.createFullTextQuery(luceneQuery);
setProjection(fullTextQuery.spatical_DISTANCE,fullTextQuery.THIS);
fullTextQuery.setSpatialParameters(纬度、经度、空间坐标\默认\字段);
//应用过滤器
如果(deliveryMode!=null){
enableFullTextFilter(DeliveryModeFilterFactory.NAME).setParameter(“deliveryMode”,deliveryMode);
if(deliveryMode==Order.deliveryMode.DELIVERY){
启用FullTextFilter(CoverageFilterFactory.NAME)
.setParameter(“字段”、“覆盖范围”)
.setParameter(“纬度”,纬度)
.setParameter(“经度”,经度);
}
}
//对结果进行排序
排序距离排序=新排序(新距离排序字段(纬度、经度、空间坐标\默认\字段));
fullTextQuery.setSort(距离排序);
//执行搜索
//注意:结果的数量可能与getResultList().size()略有不同,因为getResultList()在查询时可能与数据库不同步。
long total=fullTextQuery.getResultSize();
列表结果=fullTextQuery
.setMaxResults(pageRequest.getPageSize())
.setFirstResult(pageRequest.getOffset())
.getResultList();
//改变结果
List content=result.stream()
.map(o->新搜索结果((双精度)o[0],(公司)o[1]))
.collect(Collectors.toList());
返回新的PageImpl(内容、pageRequest、总数);
}
}
在经历了一些小麻烦之后,我找到了一个解决方案
我实现了几何场的桥接:
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Geometry;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions;
/**
* @see https://docs.jboss.org/hibernate/stable/search/reference/en-US/html/ch04.html#section-custom-bridges
* @see http://www.gossamer-threads.com/lists/lucene/java-user/254444
*/
public class GeometryBridge implements FieldBridge {
// http://unterbahn.com/2009/11/metric-dimensions-of-geohash-partitions-at-the-equator/
public static final int GEOHASH_MAX_LEVELS = 9; // 4.78m
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
JtsSpatialContext spatialContext = JtsSpatialContext.GEO;
SpatialPrefixTree grid = new GeohashPrefixTree(spatialContext, 22);
// Preparing the tree strategy field
SpatialStrategy treeStrategy = new RecursivePrefixTreeStrategy(grid, name);
for (IndexableField field: treeStrategy.createIndexableFields(
new JtsGeometry((Geometry) value, spatialContext, false, true))) {
document.add(field);
}
// Preparing the verify strategy field
SerializedDVStrategy verifyStrategy = new SerializedDVStrategy(spatialContext, "serialized_" + name);
for (IndexableField field: verifyStrategy.createIndexableFields(
new JtsGeometry((Geometry) value, spatialContext, false, true))) {
document.add(field);
}
}
}
稍后将使用自定义筛选器筛选查询:
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.Point;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.hibernate.search.annotations.Factory;
import org.hibernate.search.filter.impl.CachingWrapperFilter;
/**
* @see https://vimeo.com/106843184
* @see http://www.slideshare.net/lucenerevolution/lucene-solr-4-spatial-extended-deep-dive
*/
public class CoverageFilterFactory {
public static final String NAME = "coverage";
private String field;
private double latitude;
private double longitude;
public void setField(String field) {
this.field = field;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
@Factory
public Filter getFilter() {
JtsSpatialContext spatialContext = JtsSpatialContext.GEO;
Point point = spatialContext.makePoint(latitude, longitude);
SpatialArgs spatialArgs = new SpatialArgs(SpatialOperation.Intersects, point);
SpatialPrefixTree grid = new GeohashPrefixTree(spatialContext, GeometryBridge.GEOHASH_MAX_LEVELS);
SpatialStrategy treeStrategy = new RecursivePrefixTreeStrategy(grid, field);
SerializedDVStrategy verifyStrategy = new SerializedDVStrategy(spatialContext, "serialized_" + field);
Query treeQuery = new ConstantScoreQuery(treeStrategy.makeFilter(spatialArgs));
Query combinedQuery = new FilteredQuery(treeQuery,
verifyStrategy.makeFilter(spatialArgs),
FilteredQuery.QUERY_FIRST_FILTER_STRATEGY);
return new CachingWrapperFilter(new QueryWrapperFilter(combinedQuery));
}
}
最后,这里是自定义hibernate存储库的代码:
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import org.apache.lucene.search.Sort;
import org.hibernate.search.annotations.Spatial;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.query.dsl.Unit;
import org.hibernate.search.spatial.DistanceSortField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
@Autowired
private EntityManager entityManager;
@Override
public Page<SearchResult> search(Locale locale, double latitude, double longitude,
Order.DeliveryMode deliveryMode, String text, Pageable pageRequest) {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
String analyzerName;
switch (locale.getLanguage()) {
case "es": analyzerName = SPANISH_NAME_FIELD_DISCRIMINATOR; break;
case "en": analyzerName = ENGLISH_NAME_FIELD_DISCRIMINATOR; break;
default: throw new RuntimeException("Unexpected language found");
}
QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder()
.forEntity(Company.class)
.overridesForField(Company_.name.getName(), analyzerName)
.overridesForField(Company_.groups.getName() + "." + Group_.name.getName(), analyzerName)
.overridesForField(Company_.products.getName() + "." + Product_.name.getName(), analyzerName)
.get();
org.apache.lucene.search.Query luceneQuery = queryBuilder
.spatial()
.within(30000, Unit.KM)
.ofLatitude(latitude)
.andLongitude(longitude)
.createQuery();
// Adding text if corresponds
if (text != null) {
luceneQuery = queryBuilder
.bool()
.must(luceneQuery)
.must(queryBuilder
.keyword()
.onField(Company_.name.getName())
.boostedTo(2f)
.andField(Company_.headline.getName())
.andField(Company_.groups.getName() + "." + Group_.name.getName())
.andField(Company_.products.getName() + "." + Product_.name.getName())
.matching(text)
.createQuery()
)
.createQuery();
}
// wrap Lucene query in a javax.persistence.Query
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery);
fullTextQuery.setProjection(FullTextQuery.SPATIAL_DISTANCE, FullTextQuery.THIS);
fullTextQuery.setSpatialParameters(latitude, longitude, Spatial.COORDINATES_DEFAULT_FIELD);
// Applying filters
if (deliveryMode != null) {
fullTextQuery.enableFullTextFilter(DeliveryModeFilterFactory.NAME).setParameter("deliveryMode", deliveryMode);
if (deliveryMode == Order.DeliveryMode.DELIVERY) {
fullTextQuery.enableFullTextFilter(CoverageFilterFactory.NAME)
.setParameter("field", "coverage")
.setParameter("latitude", latitude)
.setParameter("longitude", longitude);
}
}
// Sorting the results
Sort distanceSort = new Sort(new DistanceSortField(latitude, longitude, Spatial.COORDINATES_DEFAULT_FIELD));
fullTextQuery.setSort(distanceSort);
// Execute Search
// Caution: The number of results might be slightly different from getResultList().size() because getResultList() may be not in sync with the database at the time of query.
long total = fullTextQuery.getResultSize();
List<Object[]> result = fullTextQuery
.setMaxResults(pageRequest.getPageSize())
.setFirstResult(pageRequest.getOffset())
.getResultList();
// Transforming the results
List<SearchResult> content = result.stream()
.map(o -> new SearchResult((double) o[0], (Company) o[1]))
.collect(Collectors.toList());
return new PageImpl<>(content, pageRequest, total);
}
}
import java.util.List;
导入java.util.Locale;
导入java.util.stream.collector;
导入javax.persistence.EntityManager;
导入org.apa