一、准备
1.添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
2.application.yml
spring: application: name: search-service data: elasticsearch: cluster-name: elasticsearch cluster-nodes: 192.168.25.129:9300
3.实体类
@Data @Document(indexName = "goods", type = "_doc", shards = 1, replicas = 0) public class Goods { @Idprivate Long id; @Field(type = FieldType.text, analyzer = "ik_max_word") private String all; @Field(type = FieldType.keyword, index = false) private String subTitle;private Long brandId;private Long cid1;private Long cid2;private Long cid3;private Date createTime;private List<Long> price; @Field(type = FieldType.keyword, index = false) private String skus;private Map<String, Object> specs; }
@Data @Document(indexName = "bulk_goods", type = "_doc", shards = 1, replicas = 0) public class Goods { @Id private Long id; @ApiModelProperty("商品名称") @Field(type = FieldType.text, analyzer = "ik_max_word") private String name; @Field(type = FieldType.text) @ApiModelProperty("供应商名称") private String SellerName; @Field(type = FieldType.keyword) @ApiModelProperty("规格") private String packsize; @ApiModelProperty("自提或配送价格") private BigDecimal price; @ApiModelProperty("单位名称") private BigDecimal unitName; @ApiModelProperty("类型 1:自提 2:配送") private String type; @ApiModelProperty("起批数量") private Integer wholesaleNum; @ApiModelProperty("商家地址 自提:提货地址 配送:供应商地址") private String merchAddress; @ApiModelProperty("我的卖价") private String mySellPrice; @ApiModelProperty("最小批量预计可获利") private String profit; @ApiModelProperty("商品图片") private String headUrl; }
@Data @Document(indexName = "bulk_goods_bank", type = "_doc", shards = 1, replicas = 0) public class GoodsBank { @Id private Long id; @ApiModelProperty("模板名称") @Field(type = FieldType.text, analyzer = "ik_max_word") private String name; @Field(type = FieldType.keyword) @ApiModelProperty("品牌") private String brand; @ApiModelProperty("规格") private String packsize; @ApiModelProperty("原图") private String headurl; @ApiModelProperty("缩略图") private String smallheadurl; }
@Document
作用在类,标记实体类为文档对象,一般有两个属性- indexName:对应索引库名称
- type:对应在索引库中的类型
- shards:分片数量,默认5
- replicas:副本数量,默认1
@Id
作用在成员变量,标记一个字段作为id主键@Field
作用在成员变量,标记为文档的字段,并指定字段映射属性:- type:字段类型,取值是枚举:FieldType
- index:是否索引,布尔类型,默认是true
- store:是否存储,布尔类型,默认是false
- analyzer:分词器名称
二.、索引操作
首先注入ElasticsearchTemplate
@Resource private ElasticsearchTemplate elasticsearchTemplate;
● 创建索引
elasticsearchTemplate.createIndex(Goods.class);
● 配置映射
elasticsearchTemplate.putMapping(Goods.class);
● 删除索引
//根据类 elasticsearchTemplate.deleteIndex(Goods.class); //根据索引名 elasticsearchTemplate.deleteIndex("goods");
三、文档操作
1.定义接口。也是SpringData风格
public interface ItemRepository extends ElasticsearchRepository<Item,Long> { }
2.注入
@Autowired private ItemRepository itemRepository;
● 新增文档
Item item = new Item(1L, "小米手机7", " 手机", "小米", 3499.00, "http://image.leyou.com/13123.jpg"); itemRepository.save(item);
● 批量新增
List<Item> list = new ArrayList<>(); list.add(new Item(2L, "坚果手机R1", " 手机", "锤子", 3699.00, "http://image.leyou.com/123.jpg")); list.add(new Item(3L, "华为META10", " 手机", "华为", 4499.00, "http://image.leyou.com/3.jpg")); // 接收对象集合,实现批量新增 itemRepository.saveAll(list);
四、 基本搜索
● 基本查询。
例:
// 查询全部,并安装价格降序排序 Iterable<Item> items = this.itemRepository.findAll(Sort.by(Sort.Direction.DESC, "price")); items.forEach(item-> System.out.println(item));
● 自定义查询
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And |
findByNameAndPrice |
{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or |
findByNameOrPrice |
{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is |
findByName |
{"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not |
findByNameNot |
{"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between |
findByPriceBetween |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual |
findByPriceLessThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual |
findByPriceGreaterThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before |
findByPriceBefore |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After |
findByPriceAfter |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like |
findByNameLike |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith |
findByNameStartingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith |
findByNameEndingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing |
findByNameContaining |
{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In |
findByNameIn(Collection<String>names) |
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn(Collection<String>names) |
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near |
findByStoreNear |
Not Supported Yet ! |
True |
findByAvailableTrue |
{"bool" : {"must" : {"field" : {"available" : true}}}} |
False |
findByAvailableFalse |
{"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
例:
public interface ItemRepository extends ElasticsearchRepository<Item,Long> { /** * 根据价格区间查询 * @param price1 * @param price2 * @return */ List<Item> findByPriceBetween(double price1, double price2); }
public interface GoodsBankRepository extends ElasticsearchRepository<GoodsBank, Long> { /** * 根据商品模板名称或品牌搜索 * * @param name 模板名称 * @param brand 品牌 * @return */ List<GoodsBank> findByNameOrBrand(String name, String brand); }
五、高级查询
● 词条查询
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("all", "小米"); // 执行查询 Iterable<Goods> goods = this.goodsRepository.search(queryBuilder);
● 自定义查询
// 构建查询条件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 添加基本的分词查询 queryBuilder.withQuery(QueryBuilders.matchQuery("all", "小米")); // 执行搜索,获取结果 Page<Goods> goods = this.goodsRepository.search(queryBuilder.build()); // 打印总条数 System.out.println(goods.getTotalElements()); // 打印总页数 System.out.println(goods.getTotalPages());
● 分页查询
// 构建查询条件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 添加基本的分词查询 queryBuilder.withQuery(QueryBuilders.termQuery("all", "手机")); // 初始化分页参数 int page = 0; int size = 3; // 设置分页参数 queryBuilder.withPageable(PageRequest.of(page, size)); // 执行搜索,获取结果 Page<Goods> goods = this.goodsRepository.search(queryBuilder.build()); // 打印总条数 System.out.println(goods.getTotalElements()); // 打印总页数 System.out.println(goods.getTotalPages()); // 每页大小 System.out.println(goods.getSize()); // 当前页 System.out.println(goods.getNumber());
● 排序
// 构建查询条件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 添加基本的分词查询 queryBuilder.withQuery(QueryBuilders.termQuery("all", "手机")); // 排序 queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); // 执行搜索,获取结果 Page<Goods> goods = this.goodsRepository.search(queryBuilder.build()); // 打印总条数 System.out.println(goods.getTotalElements());
● 聚合为桶
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 不查询任何结果 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brandId")); // 2、查询,需要把结果强转为AggregatedPage类型 AggregatedPage<Goods> aggPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build()); // 3、解析 // 3.1、从结果中取出名为brands的那个聚合, // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 LongTerms agg = (LongTerms) aggPage.getAggregation("brands"); // 3.2、获取桶 List<LongTerms.Bucket> buckets = agg.getBuckets(); // 3.3、遍历 for (LongTerms.Bucket bucket : buckets) { // 3.4、获取桶中的key,即品牌名称 System.out.println(bucket.getKeyAsString()); // 3.5、获取桶中的文档数量 System.out.println(bucket.getDocCount()); }
● 嵌套聚合,求平均值
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 不查询任何结果 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brandId") .subAggregation(AggregationBuilders.avg("priceAvg").field("price"))); // 在品牌聚合桶内进行嵌套聚合,求平均值 // 2、查询,需要把结果强转为AggregatedPage类型 AggregatedPage<Goods> aggPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build()); // 3、解析 // 3.1、从结果中取出名为brands的那个聚合, // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 LongTerms agg = (LongTerms) aggPage.getAggregation("brands"); // 3.2、获取桶 List<LongTerms.Bucket> buckets = agg.getBuckets(); // 3.3、遍历 for (LongTerms.Bucket bucket : buckets) { // 3.4、获取桶中的key,即品牌名称 3.5、获取桶中的文档数量 System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台"); // 3.6.获取子聚合结果: InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg"); System.out.println("平均售价:" + avg.getValue()); }
附:配置搜索结果不显示为null字段:
spring: jackson: default-property-inclusion: non_null # 配置json处理时忽略空值