SpringBoot集成Elasticsearch实例

作者 : admin 本文共8952个字,预计阅读时间需要23分钟 发布时间: 2024-06-17 共1人阅读

SpringBoot项目集成Elasticsearch实例

导包


    org.springframework.boot
    spring-boot-starter-data-elasticsearch

配置es连接

spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300

准备query接收查询条件

@Data
public class CarSearchQuery extends BaseQuery {
    private Long carType;
    private Double maxPrice;
    private Double minPrice;

    //0 以下 1 以上
    private Integer carAgeType;
    private Integer carAge;

    //是否超值
    private Integer costEffective;
    //急售
    private Integer rushSale;
    //准新车
    private Integer quasiNewCar;
    //可迁全国
    private Integer transitiveCountry;

    //排序字段
    private String sortField;
    //排序类型 desc降序 asc升序号
    private String sortType;

    private Double longitude;
    private Double latitude;
    private Double distance;
    private Long shopId;
}

准备Controller

@RestController
@RequestMapping("/car/search")
public class CarSearchController {

    @Autowired
    private ICarSearchService carSearchService;

    @PostMapping
    public AjaxResult search(@RequestBody CarSearchQuery query) {
        PageList<CarDoc> search = carSearchService.search(query);
        return AjaxResult.me().setData(search);
    }
}

定义Document文档类型

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "example-car", type = "car")
public class CarDoc {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
private String title;
@Field(type = FieldType.Keyword)
private String cover;
@Field(type = FieldType.Double)
private BigDecimal salePrice;
@Field(type = FieldType.Double)
private BigDecimal costPrice;
@Field(type = FieldType.Integer)
private Integer isNew;
@Field(type = FieldType.Date)
private Date registerTime;
@Field(type = FieldType.Double)
private Double mileAge;
@Field(type = FieldType.Long)
private Long shopId;
@MultiField(mainField = @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart"),
otherFields = {@InnerField(type = FieldType.Keyword, suffix = "keyword")})
private String shopName; // @MultiField对同一字段应用不同的分析器或存储策略,这里既可以将该字段按照text进行拆分,也可以作为keyword进行查询
@Field(type = FieldType.Keyword)
private String shopAddress;
@Field(type = FieldType.Date)
private Date onSaleTime;
@Field(type = FieldType.Integer)
private Integer costEffective;
@Field(type = FieldType.Integer)
private Integer rushSale;
@Field(type = FieldType.Integer)
private Integer quasiNewCar;
@Field(type = FieldType.Integer)
private Integer transitiveCountry;
@Field(type = FieldType.Long)
private Long typeId;
@MultiField(mainField = @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart"),
otherFields = {@InnerField(type = FieldType.Keyword, suffix = "keyword")})
private String typeName; 
@Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
private String carInfo;
@GeoPointField   // 表示坐标类型
private GeoPoint shopPoint;  
}

准备一个Repository接口

@Repository
public interface CarDocRepository extends ElasticsearchRepository<CarDoc, Long> {
}// 接口中的泛型,一个是文档对象的类型,一个是文档对象id的类型

使用spring-boot-starter-data-elasticsearch对es进行操作时也要按照es请求的格式进行操作

{
"query": {
"bool": {
"must": {
"match_all": {
}
},
"filter": {
"term": {
"username": "Steven King"
}
}
}
}
}

查询

@Service
public class CarSearchServiceImpl implements ICarSearchService {
@Autowired
private CarDocRepository repository;
@Autowired
private HighlightResultMapper highlightResultMapper;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Override
public PageList<CarDoc> search(CarSearchQuery query) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
BoolQueryBuilder boole = QueryBuilders.boolQuery(); // 外层的bool
List<QueryBuilder> filter = boole.filter();		// 内层的fitler
List<QueryBuilder> must = boole.must();		// 内层的must
// 查询"title", "typeName", "shopName", "carInfo"中有查询关键字的结果
if (StringUtils.isNotBlank(query.getSearch())) {
must.add(QueryBuilders.multiMatchQuery(query.getSearch(), "title", "typeName", "shopName", "carInfo"));
}
if (Objects.nonNull(query.getShopId())) {
filter.add(QueryBuilders.termQuery("shopId", query.getShopId()));
}
// 进行范围查询
if (Objects.nonNull(query.getMinPrice())) {
filter.add(QueryBuilders.rangeQuery("costPrice").gte(query.getMinPrice()));
}
if (Objects.nonNull(query.getMaxPrice())) {
filter.add(QueryBuilders.rangeQuery("costPrice").lte(query.getMaxPrice()));
}
// 按时间进行查找
if (Objects.nonNull(query.getCarAge())) {
Date date = DateUtils.addYears(new Date(), (-query.getCarAge()));
if (query.getCarAgeType() == 1) {
filter.add(QueryBuilders.rangeQuery("registerTime").lt(date.getTime()));
}
if (query.getCarAgeType() == 0) {
filter.add(QueryBuilders.rangeQuery("registerTime").gte(date.getTime()));
}
}
// 经纬度
Double lon = query.getLongitude();
Double lat = query.getLatitude();
// 按照经纬度计算距离并按照距离进行查询
if (Objects.nonNull(lon) && Objects.nonNull(lat) && Objects.nonNull(query.getDistance())) {
GeoDistanceQueryBuilder point = QueryBuilders.geoDistanceQuery("shopPoint").point(lat, lon)
.distance(query.getDistance(), DistanceUnit.KILOMETERS);
filter.add(point);
}
// 将bool加入到最外层query的构造器
builder.withQuery(boole);
SortOrder order = SortOrder.ASC;
if (!"asc".equals(query.getSortType())) {
order = SortOrder.DESC;
}
// 通过指定字段进行排序
if (Objects.nonNull(query.getSortField())) {
FieldSortBuilder sort = SortBuilders.fieldSort(query.getSortField()).order(order);
builder.withSort(sort);
} else {
// 没传则默认按照距离排序
if (Objects.nonNull(lat) && Objects.nonNull(lon)) {
GeoDistanceSortBuilder point = new GeoDistanceSortBuilder("shopPoint", lat, lon);
GeoDistanceSortBuilder sort = point.order(order);
builder.withSort(sort);
}
}
// 高亮展示,通过给字段前后加html标签的方式实现高亮等效果,需要一个高亮的工具类
HighlightBuilder.Field title = new HighlightBuilder.Field("title")
.preTags("").postTags("");
builder.withHighlightFields(title);
// 进行分页展示
builder.withPageable(PageRequest.of(query.getCurrentPage() - 1, query.getPageSize()));
TermsAggregationBuilder aggBuilders1 = AggregationBuilders.terms("typeIdGroup").field("typeId").order(BucketOrder.count(true))
.subAggregation(AggregationBuilders.terms("typeNameGroup").field("typeName.keyword").order(BucketOrder.count(true)));// 聚合查询
builder.addAggregation(aggBuilders1);
AggregatedPage<CarDoc> carDocs = elasticsearchTemplate.queryForPage(builder.build(), CarDoc.class, highlightResultMapper);
Map<String, Object> map = SearchUtil.handleTermsAggsData(carDocs.getAggregations());
Long counts = carDocs.getTotalElements();
List<CarDoc> content = carDocs.getContent();
for (CarDoc doc : content) {
String s = doc.getShopAddress();
if (StringUtils.isNotBlank(s)) {
String string = s.split("市")[0] + "市";
}
doc.setShopAddress(s);
}
return new PageList<>(counts, content, map);
}
}

PageList

@Data
public class PageList<T> {
private Long count;
private List<T> data; // 用于存放聚合查询的数据
private Map<String,Object> map;
public PageList() {
}
public PageList(Long count, List<T> data, Map<String, Object> map) {
this.count = count;
this.data = data;
this.map = map;
}
public PageList(Long count, List<T> data) {
this.count = count;
this.data = data;
}
}

高亮结果映射器

@Component
public class HighlightResultMapper implements SearchResultMapper {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
// 记录总条数
long totalHits = response.getHits().getTotalHits();
// 记录列表(泛型) - 构建Aggregate使用
List<T> list = new ArrayList<>();
// 获取搜索结果(真正的的记录)
SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
if(hits.getHits().length <= 0){
return null;
}
// 将原本的JSON对象转换成Map对象
Map<String, Object> map = hit.getSourceAsMap();
// 获取高亮的字段Map
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
// 获取高亮的Key
String key = highlightField.getKey();
// 获取高亮的Value
HighlightField value = highlightField.getValue();
// 实际fragments[0]就是高亮的结果,无需遍历拼接
Text[] fragments = value.getFragments();
StringBuilder sb = new StringBuilder();
for (Text text : fragments) {
sb.append(text);
}
// 因为高亮的字段必然存在于Map中,就是key值
// 可能有一种情况,就是高亮的字段是嵌套Map,也就是说在Map里面还有Map的这种情况,这里没有考虑
map.put(key, sb.toString());
}
// 把Map转换成对象
T item = JSONObject.parseObject(JSONObject.toJSONString(map),aClass);
list.add(item);
}
// 返回的是带分页的结果
return new AggregatedPageImpl<>(list, pageable, totalHits,response.getAggregations()); //获取聚合结果
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
return null;
}
}

搜索工具类,用于处理聚合查询结果

public class SearchUtil {
/**
* 处理terms聚合   id  name
* @param aggregations
* @return
*/
public static Map<String, Object> handleTermsAggsData(Aggregations aggregations) {
// 获取聚合查询结果
Map<String, Aggregation> aggregationsMap = aggregations.getAsMap();
Set<Map.Entry<String, Aggregation>> entries = aggregationsMap.entrySet();
Iterator<Map.Entry<String, Aggregation>> iterator = entries.iterator();
//6.1有多少聚合就要返回多少个key-List
Map<String, Object> aggsData = new HashMap<>();
while (iterator.hasNext()) {
Map.Entry<String, Aggregation> entry = iterator.next();
String key = entry.getKey();
System.out.println(key);
Aggregation aggsId = entry.getValue();
if (aggsId instanceof LongTerms) {   //6.2 拿到id聚合,并且必须是LongTerms
LongTerms aggsIdLong = (LongTerms) aggsId;
List<LongTerms.Bucket> buckets = aggsIdLong.getBuckets();
//6.3 List<IdName
List<IdName> list = new ArrayList<>();
buckets.forEach(bucket -> {
String idStr = bucket.getKeyAsString();
//6.4 通过子聚合获取name
Map<String, Aggregation> subAggs = bucket.getAggregations().getAsMap();
Set<Map.Entry<String, Aggregation>> entries1 = subAggs.entrySet();
//直接获取第一个
Map.Entry<String, Aggregation> nameAggEntry = entries1.iterator().next();
Aggregation nameAgg = nameAggEntry.getValue();
if (nameAgg instanceof StringTerms) {
StringTerms nameAggStringTerms = (StringTerms) nameAgg;
String nameStr = nameAggStringTerms.getBuckets().get(0).getKeyAsString();
IdName idName = new IdName();
idName.setId(Long.valueOf(idStr));
idName.setName(nameStr);
list.add(idName);
}
});
aggsData.put(key, list);
}
}
return aggsData;
}
}
本站无任何商业行为
个人在线分享 » SpringBoot集成Elasticsearch实例
E-->