Spring Data Elasticsearch
Spring Data Elasticsearch是Spring提供的一种以Spring Data风格来操作数据存储的方式,它可以避免编写大量的样板代码。
常用注解
@Document
//表示映射到Elasticsarch文档上的领域对象
public @interface Document{
//索引库名次,可以理解为MySQL数据库的概念
String indexName;
//文档类型,可以理解为MySQL中表的概念
String type() default "";
//默认分片数
short shards() default 5;
//默认分片数量
short replicas() default 1;
}
@Id
//表示是文档的Id,可以理解为MySQL中表行的概念
public @interface Id{
}
@Field
public @interface Field{
//文档中字段的类型
FieldType type() default FieldType.Auto;
//是否建立倒排索引
boolean index() default true;
//是否进行存储
boolean store() default false;
//分词器名称
String analyzer() default "";
}
//为文档自动指定元数据类型
public enum FieldType{
Text, //会自动进行分词并建了索引的字符类型
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto, //自动判断字段类型
Nested, //嵌套对象类型
Ip, //可以索引和存储IPV4和IPV6地址
Attachment, //附件类型
Keyword //不会进行分词建立索引类型
}
Spring Data方式的数据操作
继承ElasticsearchRepository接口可以获取常用的数据操作方法
使用衍生查询
在接口中直接指定查询方法便可进行查询,无需进行实现,如表中有名称、标题、关键字,可以直接定义以下查询,即可对该三个字段实现全文搜索
/**
* 搜索查询
*
* @param name 名称
* @param subTitle 标题
* @param keywords 关键字
* @param page 分页信息
* @return
*/
Page<EsProduct> findByNameOrSubTitleOrKeywords(String name, String subTitle, String keywords, Pageable page);
衍生查询原理
使用@Query注解可以用Elasticsearch的DSL语句进行查询
@Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
Page<EsProduct> findByName(String name,Pageable pageable);
整合Elasticsearch实现搜索功能
在Pom.xml中添加相关依赖
<!--Elasticsearch相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch<artifactId>
</dependency>
版本对应说明
修改SpringBoot配置文件
data:
elasticsearch:
repositories:
enabled: true
cluster-nodes: 127.0.0.1:9300 # es的连接地址及端口号
cluster-name: elasticsearch # es集群的名称
添加商品文档对象EsProduct
不需要中文分词设置为@Field(type=FieldType.Keyword)类型,需要中文分词的设置为@Field(analyzer = “ik_max_word”,type = FieldType.Text)类型。
package com.macro.tiny.nosql.elasticsearch.document;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
@Data
@Document(indexName = "pms", type = "product",shards = 1,replicas = 0)
public class EsProduct implements Serializable {
private static final long serialVersionUID = -1L;
@Id
private Long id;
@Field(type = FieldType.Keyword)
private String productSn;
private Long brandId;
@Field(type = FieldType.Keyword)
private String brandName;
private Long productCategoryId;
@Field(type = FieldType.Keyword)
private String productCategoryName;
private String pic;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String name;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String subTitle;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String keywords;
private BigDecimal price;
private Integer sale;
private Integer newStatus;
private Integer recommandStatus;
private Integer stock;
private Integer promotionType;
private Integer sort;
@Field(type =FieldType.Nested)
private List<EsProductAttributeValue> attrValueList;
}
添加EsProductRepository接口用于操作Elasticsearch
继承ElasticsearchRepository接口,这样就拥有了一些基本的Elasticsearch数据操作方法,同时相当于定义了一个衍生方法
package com.macro.tiny.nosql.elasticsearch.repository;
import com.macro.tiny.nosql.elasticsearch.document.EsProduct;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* 商品ES操作类
*/
public interface EsProductRepository extends ElasticsearchRepository<EsProduct, Long> {
/**
* 搜索查询
*
* @param name 商品名称
* @param subTitle 商品标题
* @param keywords 商品关键字
* @param page 分页信息
* @return
*/
Page<EsProduct> findByNameOrSubTitleOrKeywords(String name, String subTitle, String keywords, Pageable page);
}
添加EsProductService接口
package com.mall.tiny.service;
import com.macro.mall.tiny.nosql.elasticsearch.document.EsProduct;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* 商品搜索管理Service
*/
public interface EsProductService {
/**
* 从数据库中导入所有商品到ES
*/
int importAll();
/**
* 根据id删除商品
*/
void delete(Long id);
/**
* 根据id创建商品
*/
EsProduct create(Long id);
/**
* 批量删除商品
*/
void delete(List<Long> ids);
/**
* 根据关键字搜索名称或者副标题
*/
Page<EsProduct> search(String keyword, Integer pageNum, Integer pageSize);
}
添加EsProductService接口的实现类EsProductServiceImpl
package com.mall.tiny.service.impl;
import com.mall.tiny.dao.EsProductDao;
import com.mall.tiny.nosql.elasticsearch.document.EsProduct;
import com.mall.tiny.nosql.elasticsearch.repository.EsProductRepository;
import com.mall.tiny.service.EsProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 商品搜索管理Service实现类
* Created by macro on 2018/6/19.
*/
@Service
public class EsProductServiceImpl implements EsProductService {
private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class);
@Autowired
private EsProductDao productDao;
@Autowired
private EsProductRepository productRepository;
/**
* 该方法每次刷新一次可以获取到最新的数据
* 相比于Solr每次都要配置增量更新来说方便了很多
*/
@Override
public int importAll() {
List<EsProduct> esProductList = productDao.getAllEsProductList(null);
Iterable<EsProduct> esProductIterable = productRepository.saveAll(esProductList);
Iterator<EsProduct> iterator = esProductIterable.iterator();
int result = 0;
while (iterator.hasNext()) {
result++;
iterator.next();
}
return result;
}
@Override
public void delete(Long id) {
productRepository.deleteById(id);
}
@Override
public EsProduct create(Long id) {
EsProduct result = null;
List<EsProduct> esProductList = productDao.getAllEsProductList(id);
if (esProductList.size() > 0) {
EsProduct esProduct = esProductList.get(0);
result = productRepository.save(esProduct);
}
return result;
}
@Override
public void delete(List<Long> ids) {
if (!CollectionUtils.isEmpty(ids)) {
List<EsProduct> esProductList = new ArrayList<>();
for (Long id : ids) {
EsProduct esProduct = new EsProduct();
esProduct.setId(id);
esProductList.add(esProduct);
}
productRepository.deleteAll(esProductList);
}
}
@Override
public Page<EsProduct> search(String keyword, Integer pageNum, Integer pageSize) {
Pageable pageable = PageRequest.of(pageNum, pageSize);
return productRepository.findByNameOrSubTitleOrKeywords(keyword, keyword, keyword, pageable);
}
}
添加EsProductController定义接口
package com.mall.tiny.controller;
import com.mall.tiny.common.api.CommonPage;
import com.mall.tiny.common.api.CommonResult;
import com.mall.tiny.nosql.elasticsearch.document.EsProduct;
import com.mall.tiny.service.EsProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 搜索商品管理Controller
*/
@Controller
@Api(tags = "EsProductController", description = "搜索商品管理")
@RequestMapping("/esProduct")
public class EsProductController {
@Autowired
private EsProductService esProductService;
@ApiOperation(value = "导入所有数据库中商品到ES")
@RequestMapping(value = "/importAll", method = RequestMethod.POST)
@ResponseBody
public CommonResult<Integer> importAllList() {
int count = esProductService.importAll();
return CommonResult.success(count);
}
@ApiOperation(value = "根据id删除商品")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult<Object> delete(@PathVariable Long id) {
esProductService.delete(id);
return CommonResult.success(null);
}
@ApiOperation(value = "根据id批量删除商品")
@RequestMapping(value = "/delete/batch", method = RequestMethod.POST)
@ResponseBody
public CommonResult<Object> delete(@RequestParam("ids") List<Long> ids) {
esProductService.delete(ids);
return CommonResult.success(null);
}
@ApiOperation(value = "根据id创建商品")
@RequestMapping(value = "/create/{id}", method = RequestMethod.POST)
@ResponseBody
public CommonResult<EsProduct> create(@PathVariable Long id) {
EsProduct esProduct = esProductService.create(id);
if (esProduct != null) {
return CommonResult.success(esProduct);
} else {
return CommonResult.failed();
}
}
@ApiOperation(value = "简单搜索")
@RequestMapping(value = "/search/simple", method = RequestMethod.GET)
@ResponseBody
public CommonResult<CommonPage<EsProduct>> search(@RequestParam(required = false) String keyword,
@RequestParam(required = false, defaultValue = "0") Integer pageNum,
@RequestParam(required = false, defaultValue = "5") Integer pageSize) {
Page<EsProduct> esProductPage = esProductService.search(keyword, pageNum, pageSize);
return CommonResult.success(CommonPage.restPage(esProductPage));
}
}
接口测试
将数据库中数据导入到Elasticsearch
商品搜索
综合商品搜索
实现一个较为复杂的商品搜索,设计到过滤、不同字段匹配权重不同以及可以进行排序(需求:按输入的关键字搜索商品名称、副标题和关键词,可以按照品牌和分类进行筛选,可以有5种排序方式,默认按相关度进行排序。)
相关代码
@Override
public Page<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {
Pageable pageable = PageRequest.of(pageNum,pageSize);
//查询类,查询到的结果高亮显示
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//分页
nativeSearchQueryBuilder.withPageable(pageable);
//过滤
if (brandId != null || productCategoryId != null) {
//创建Elasticsearch查询方法
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (brandId != null) {
//must:所返回的文档必须满足must字句的条件,且参与分值计算
//QueryBuilders.termQuery:完全匹配
boolQueryBuilder.must(QueryBuilders.termQuery("brandId",brandId));
}
if (productCategoryId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("productCategoryId",productCategoryId));
}
nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
}
//搜素
if (StringUtils.isEmpty(keyword)) {
//如果条件为空,则搜索所有
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
} else {
//ScoreFunctionBuilders.weightFactorFunction():设置权重(权重越高,与搜索条件越匹配)
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name",keyword),
ScoreFunctionBuilders.weightFactorFunction(10)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle",keyword),
ScoreFunctionBuilders.weightFactorFunction(5)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords",keyword),
ScoreFunctionBuilders.weightFactorFunction(2)));
FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
filterFunctionBuilders.toArray(builders);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
//将返回结果合并在一个中,Sum:函数结果累加
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.setMinScore(2);
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
}
//排序
if (sort == 1) {
//按照新品从新到旧
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
} else if (sort == 2) {
//按照销量从高到低
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sale").order(SortOrder.DESC));
} else if (sort == 3) {
//按照价格从低到高
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
} else if (sort == 4) {
//按照价格从高到底
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
}else{
//按相关度
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
}
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
log.info("DSL:{}",searchQuery.getQuery().toString());
return productRepository.search(searchQuery);
}
相关商品推荐
查看相关商品的时候,一般底部会有一些商品的推荐(需求:按照指定的商品ID来查找相关商品)
相关代码
@Override
public Page<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {
Pageable pageable = PageRequest.of(pageNum,pageSize);
//先根据Id获取相关的商品信息
List<EsProduct> esProductList = productDao.getAllEsProductList(id);
if (esProductList.size() > 0) {
EsProduct esProduct = esProductList.get(0);
//获取商品的名称信息
String keyword = esProduct.getName();
//获取商品的BrandId
Long brandId = esProduct.getBrandId();
//获取商品分类Id
Long productCategoryId = esProduct.getProductCategoryId();
//根据商品、品牌、分类进行搜索
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name",keyword),
ScoreFunctionBuilders.weightFactorFunction(8)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle",keyword),
ScoreFunctionBuilders.weightFactorFunction(2)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords",keyword),
ScoreFunctionBuilders.weightFactorFunction(2)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("brandId",brandId),
ScoreFunctionBuilders.weightFactorFunction(5)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("productCategoryId",productCategoryId),
ScoreFunctionBuilders.weightFactorFunction(3)));
FunctionScoreQueryBuilder.FilterFunctionBuilder builders[] = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
filterFunctionBuilders.toArray(builders);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.setMinScore(2);
//用于过滤掉相同的商品
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//mustNot表示同时不满足
boolQueryBuilder.mustNot(QueryBuilders.termQuery("id",id));
//构建查询条件
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(functionScoreQueryBuilder);
builder.withFilter(boolQueryBuilder);
builder.withPageable(pageable);
NativeSearchQuery searchQuery = builder.build();
log.info("DSL:{}",searchQuery.getQuery().toString());
return productRepository.search(searchQuery);
}
return new PageImpl<>(null);
}