当前位置:   article > 正文

SpringBoot集成Elasticsearch实例

SpringBoot集成Elasticsearch实例

SpringBoot项目集成Elasticsearch实例

导包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

配置es连接

spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300
  • 1
  • 2
  • 3
  • 4
  • 5

准备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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

准备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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

定义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;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

准备一个Repository接口

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

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

{
    "query": {
        "bool": {
            "must": {
                "match_all": {
                    
                }
            },
            "filter": {
                "term": {
                    "username": "Steven King"
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

查询

@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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

高亮结果映射器

@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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

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

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/723405
推荐阅读
相关标签
  

闽ICP备14008679号