SpringBoot集成Elasticsearch实例

SpringBoot项目集成Elasticsearch实例

导包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置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("<span style='color:red'>").postTags("</span>");
		
        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<IdName>
        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<Long,String>
                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;
    }
}

相关推荐

  1. SpringBoot集成Elasticsearch实例

    2024-06-15 12:26:04       9 阅读
  2. Elasticsearch8搭建及Springboot集成使用

    2024-06-15 12:26:04       17 阅读
  3. Elasticsearch与Kafka集成实现数据流处理

    2024-06-15 12:26:04       5 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-15 12:26:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-15 12:26:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-15 12:26:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-15 12:26:04       18 阅读

热门阅读

  1. 什么是JWT?为什么用JWT?JWT的实战案例

    2024-06-15 12:26:04       7 阅读
  2. Android EventLog简介

    2024-06-15 12:26:04       7 阅读
  3. 设置服务器禁止和ip通信

    2024-06-15 12:26:04       5 阅读
  4. 网络安全(补充)

    2024-06-15 12:26:04       7 阅读
  5. Web前端进国企:挑战与机遇并存

    2024-06-15 12:26:04       6 阅读
  6. 标题:探索开源世界:2024年热门开源项目推荐

    2024-06-15 12:26:04       7 阅读
  7. 【技巧】Leetcode 67. 二进制求和【简单】

    2024-06-15 12:26:04       7 阅读
  8. 6spark期末复习

    2024-06-15 12:26:04       7 阅读