Open coder-yuxing opened 2 years ago
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.4.6</version>
</dependency>
elasticsearch 版本:7.9.3
spring:
application:
name: trainee-search-service
elasticsearch:
rest:
uris: localhost:9200
username: elastic
password:
注:除上述依赖及配置外不需要其他任何额外配置
在开发环境中,若需要查看elasticsearch请求的请求体及响应可在配置文件中引入如下配置:
logging:
level:
org:
springframework:
data:
elasticsearch:
core: DEBUG
@Document
:类级别注解,用于标识该类与elasticsearch中对应索引的映射关系
true
@Id
:字段级别注解,用于标识索引文档的主键@Field
: 字段级别注解,用于指定该字段映射至索引文档时的数据类型、分词器等
trainee-search:
index:
goods: goods
/**
* 索引文档配置
*
* @author yuxing
* @since 2022/1/14
*/
@Data
@Configuration("searchIndexConfig")
@ConfigurationProperties(prefix = "trainee-search.index")
public class SearchIndexConfig {
/**
* 素材索引名称
*/
private String goods;
}
// #{@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;
}
ElasticsearchRepository
完成基础的CRUD操作/**
* 素材文档仓储接口
*
* @author yuxing
* @since 2022/1/14
*/
public interface EsGoodsRepository extends ElasticsearchRepository<EsGoods, String> {
}
@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);
}
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;
}
// 简单聚合
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"));
}
例如要实现这样一个查询:
一个商品会存在多个属性:如颜色、尺寸、材质等,假如我们想要实现一个查询: 存在材质属性,且材质为木材的商品,并且对所有查询结果聚合其属性、属性值
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);
}
目录
ElasticsearchRepository
完成基础的CRUD操作ElasticsearchRestTemplate
完成查询