coder-yuxing / trainee-parent

Coder.YuXing's Notes
0 stars 0 forks source link

Spring Data Elasticsearch 的使用 #16

Open coder-yuxing opened 2 years ago

coder-yuxing commented 2 years ago

目录

coder-yuxing commented 2 years ago

依赖及配置

Maven依赖

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

elasticsearch 版本:7.9.3

Spring项目配置

spring:
  application:
    name: trainee-search-service
  elasticsearch: 
    rest:
      uris: localhost:9200
      username: elastic
      password:

注:除上述依赖及配置外不需要其他任何额外配置

日志配置

在开发环境中,若需要查看elasticsearch请求的请求体及响应可在配置文件中引入如下配置:

logging:
  level:
    org:
      springframework:
        data:
          elasticsearch:
            core: DEBUG
coder-yuxing commented 2 years ago

使用注解配置Elasticsearch文档元数据与Java对应的映射

常用注解介绍

代码示例

配置文件配置

trainee-search:
  index:
    goods: goods

Java配置


/**
 * 索引文档配置
 *
 * @author yuxing
 * @since 2022/1/14
 */
@Data
@Configuration("searchIndexConfig")
@ConfigurationProperties(prefix = "trainee-search.index")
public class SearchIndexConfig {

    /**
     * 素材索引名称
     */
    private String goods;

}

Java类与Elasticsearch索引的数据映射代码示例

// #{@searchIndexConfig.getGoods()} 通过 SpEL表达式引入配置文件中配置的索引名称
@Document(indexName = "#{@searchIndexConfig.getGoods()}")
public class EsGoods {

    /**
     * 文档ID
     */
    @Id
    private String id;

    /**
     * 素材名称
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;

    /**
     * 素材编码
     */
    @Field(type = FieldType.Keyword)
    private String code;

    /**
     * 素材类型
     */
    private Integer type;
}

Reference

coder-yuxing commented 2 years ago

使用 ElasticsearchRepository 完成基础的CRUD操作

创建仓储接口

/**
 * 素材文档仓储接口
 *
 * @author yuxing
 * @since 2022/1/14
 */
public interface EsGoodsRepository extends ElasticsearchRepository<EsGoods, String> {

}

CRUD

@Service
@AllArgsConstructor
public class EsGoodsSearchFacadeServiceImpl implements EsGoodsSearchFacadeService {

    private final EsGoodsRepository esGoodsRepository;

    /**
     * 新建/编辑文档
     */
    @Override
    public void saveDoc(SaveGoodsDocCommand command) {
        esGoodsRepository.save(new EsGoods(command.getId().toString(), command.getName(), command.getCode(), command.getType()));
    }

    /**
     * 根据主键查询文档
     */
    @Override
    public EsGoodsDTO getById(Long id) {
        if (Objects.isNull(id)) {
            return null;
        }
        return EsGoodsAssembler.INSTANCE.toDto(esGoodsRepository.findById(id.toString()).orElse(null));
    }

    /**
     * 删除文档
     * /
    @Override
    public void deletedById(Long id) {
        if (Objects.isNull(id)) {
            return;
        }
        esGoodsRepository.deleteById(id.toString());
    }
}

基于方法名派生构建基础的查询功能

通过实现ElasticsearchRepository 接口,spring data elasticsearch 支持我们通过方法名派生的方法快速实现查询接口

/**
 * 素材文档仓储接口
 *
 * @author yuxing
 * @since 2022/1/14
 */
public interface EsGoodsRepository extends ElasticsearchRepository<EsGoods, String> {

    /**
     * 根据编码和类型查询素材文档
     * @param code 素材编码
     * @param type 素材类型
     * @return 符合条件的素材文档集合
     */
    List<EsGoods> findByCodeAndType(String code, Integer type);
}

尽管使用上述方式可以很便捷的实现查询,但查询较为复杂时它可能并不能很好的解决问题,这种情况下可以使用@Query注解的方式实现查询方法

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
    Page<Book> findByName(String name,Pageable pageable);
}

Reference

coder-yuxing commented 2 years ago

使用ElasticsearchRestTemplate完成查询


public NativeSearchQuery getQuery() {
     NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

     BoolQueryBuilder builder = new BoolQueryBuilder();
     // 条件筛选
     if (Objects.nonNull(this.type)) {
         builder.filter(QueryBuilders.termQuery("type", this.type));
     }

     if (Objects.nonNull(this.enabled)) {
         builder.filter(QueryBuilders.termQuery("enabled", this.enabled));
     }

     // 关键字查询
     if (StrUtil.isNotBlank(this.keyword)) {
         String[] fieldNames = new String[] {"name"};
         MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(this.keyword, fieldNames);
         multiMatchQuery.minimumShouldMatch("80%");
         builder.must(multiMatchQuery);
         builder.should(QueryBuilders.termQuery("code", this.keyword));
     }
     queryBuilder.withQuery(builder);

     // 分页查询
     queryBuilder.withPageable(PageRequest.of(this.page, this.pageSize));
     return queryBuilder.build();

}

@Override
public Pager<EsGoodsDTO> search(EsGoodsQuery query) {
    EsGoodsSearchQuery searchQuery = EsGoodsAssembler.INSTANCE.toQuery(query);
    SearchHits<EsGoods> hits = elasticsearchRestTemplate.search(searchQuery.getQuery(), EsGoods.class);
    Pager<EsGoodsDTO> pager = new Pager<>(query.getPage(), query.getPageSize(), hits.getTotalHits());
    List<EsGoodsDTO> collect = hits.getSearchHits().stream().map(s -> EsGoodsAssembler.INSTANCE.toDto(s.getContent())).collect(Collectors.toList());
    pager.setData(collect);
    pager.setAggregations(ElasticsearchUtils.parseAggregations(hits.getAggregations()));
    return pager;
}
coder-yuxing commented 2 years ago

聚合查询&嵌套聚合查询(nested)

简单聚合查询

// 简单聚合
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
Map<EsGoodsAggregationField, List<Aggregation>> map = this.aggregations.stream().collect(Collectors.groupingBy(Aggregation::getField));
List<Aggregation> categoryAgg = map.get(EsGoodsAggregationField.CATEGORY);
if (CollUtil.isNotEmpty(categoryAgg)) {
    queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg.get(0).getName()).field("categoryId"));
}

嵌套聚合查询(Nested)

例如要实现这样一个查询:

一个商品会存在多个属性:如颜色、尺寸、材质等,假如我们想要实现一个查询: 存在材质属性,且材质为木材的商品,并且对所有查询结果聚合其属性、属性值

文档 mapping配置

对文档中的属性属性值字段设置其类型为nested(嵌套对象)类型。

{
    "mappings": {
         // 省略其他属性配置...
        "props" : {
            "type": "nested", // 设置props类型为 nested
            "properties": {
             "id": {
               "type": "text",
               "fields": {
               "keyword": {
               "type": "keyword",
               "ignore_above": 256
             }
           }
         },
         "showType": { "type": "short"  },
         "values":    { "type": "keyword"    }
        }
      }
    }
}

java映射对象

public class Goods {

   // 省略其他字段...

   public List<Prop> props; 
}

嵌套聚合查询

// 嵌套聚合 -> nested
List<Aggregation> propAgg = map.get(EsGoodsAggregationField.PROP);
if (CollUtil.isNotEmpty(propAgg)) {
    TermsAggregationBuilder aggregation = AggregationBuilders.terms("prop").field("props.id.keyword")
            .subAggregation(AggregationBuilders.terms("propValue").field("props.values"));
    NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested(propAgg.get(0).getName(), "props")
            .subAggregation(aggregation);
    queryBuilder.addAggregation(nestedAggregationBuilder);
}