一、Spring Data Elasticsearch
Spring Data Elasticsearch 简介
Spring Data Elasticsearch是Spring提供的一种以Spring Data风格来操作数据存储的方式,它可以避免编写大量的样板代码。
常用注解
常用注解说明如下:
注解名称 |
作用 |
参数说明 |
用于标识映射到Elasticsearch文档上的领域对象 |
indexName:索引库的名字,MySQL中数据库的概念 |
|
ES的配置注解 |
shards:默认分片数 |
|
@Id |
用于标识文档的ID,文档可以认为是MySQL中表行的概念 |
无参数 |
用于标识文档中的字段,可以认为是MySQL中列的概念 |
type:文档中字段的类型 |
其中常用的FieldType类型有如下几种:
public enum FieldType {
Auto("auto"), //自动判断字段类型
Text("text"), //会进行分词并建了索引的字符类型
Keyword("keyword"), //不会进行分词建立索引的类型
Long("long"), //
Integer("integer"), //
Short("short"), //
Byte("byte"), //
Double("double"), //
Float("float"), //
Date("date"), //
Boolean("boolean"), //
Object("object"), //
Nested("nested"), //嵌套对象类型
Ip("ip"), //
}
Spring Data方式的数据操作
- 继承ElasticsearchRepository接口可以获得常用的数据操作方法;(Ctrl + F12)
- 可以使用衍生查询,在接口中直接指定查询方法名称便可查询,无需进行实现,如商品表中有商品名称、标题和关键字,直接定义以下查询,就可以对这三个字段进行全文搜索。
/**
* @description 商品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);
}
- 在编写衍生查询方法时,IDEA会提示对应字段,更多关键字可以参考
衍生查询关键字对照表
;
衍生查询关键字对照表
Keyword |
Sample |
Elasticsearch Query String |
And |
findByNameAndPrice |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Or |
findByNameOrPrice |
{ "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Is |
findByName |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Not |
findByNameNot |
{ "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Between |
findByPriceBetween |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
LessThan |
findByPriceLessThan |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }} |
LessThanEqual |
findByPriceLessThanEqual |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
GreaterThan |
findByPriceGreaterThan |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }} |
GreaterThanEqual |
findByPriceGreaterThan |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Before |
findByPriceBefore |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
After |
findByPriceAfter |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Like |
findByNameLike |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
StartingWith |
findByNameStartingWith |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
EndingWith |
findByNameEndingWith |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
Contains/Containing |
findByNameContaining |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
In (when annotated as FieldType.Keyword) |
findByNameIn(Collectionnames) |
{ "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
In |
findByNameIn(Collectionnames) |
{ "query": {"bool": {"must": [{"query_string":{"query": ""?" "?"", "fields": ["name"]}}]}}} |
NotIn (when annotated as FieldType.Keyword) |
findByNameNotIn(Collectionnames) |
{ "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
NotIn |
findByNameNotIn(Collectionnames) |
{"query": {"bool": {"must": [{"query_string": {"query": "NOT("?" "?")", "fields": ["name"]}}]}}} |
True |
findByAvailableTrue |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }} |
False |
findByAvailableFalse |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] } |
Exists |
findByNameExists |
{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}} |
IsNull |
findByNameIsNull |
{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}} |
IsNotNull |
findByNameIsNotNull |
{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}} |
IsEmpty |
findByNameIsEmpty |
{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}} |
IsNotEmpty |
findByNameIsNotEmpty |
{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}} |
- 通过
@Query
注解可以使用Elasticsearch的原生DSL语句进行查询;
/**
* @description 商品ES操作类
*/
public interface EsProductRepository extends ElasticsearchRepository<EsProduct, Long> {
@Query("{"bool" : {"must" : {"field" : {"name" : " ? 0"}}}}")
Page<EsProduct> findByName(String name, Pageable pageable);
}
二、项目使用表结构说明
pms_product
:商品信息表;pms_product_attribute
:商品属性参数表;pms_product_attribute_value
:存储产品参数值的表。
三、整合Elasticsearch实现商品搜索
整合依赖及配置
- 在
pom.xml
中添加相关依赖;
<!--Elasticsearch相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- 修改
application.yml
配置文件,在spring节点下添加Elasticsearch相关配置;
spring:
data:
elasticsearch:
repositories:
enabled: true # 开启ES仓库配置,自动为仓库接口生成实现类
elasticsearch:
uris: http://localhost:9200 # ES的连接地址及端口号
运行后可以验证一下
实现商品搜索功能
- 添加商品文档对象
EsProduct
,不需要中文分词的字段设置成Keyword
类型,需要中文分词的设置成Text
类型,并设置分词器为ik_max_word
;
/**
* @description 搜索商品的信息
*/
@Data
@EqualsAndHashCode
@Document(indexName = "pms")
@Setting(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;
}
- 继承ElasticsearchRepository接口,这样就拥有了一些基本的Elasticsearch数据操作方法,同时定义了一个衍生查询方法;
/**
* @description 商品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,定义好ES的操作方法;
/**
* @description 商品搜索管理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;
/**
* @description 搜索商品管理Service实现类
*/
@Service
public class EsProductServiceImpl implements EsProductService {
private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class);
@Autowired
private EsProductDao productDao;
@Autowired
private EsProductRepository productRepository;
@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定义接口。
/**
* @description 搜索商品管理Controller
*/
@Controller
@Api(tags = "EsProductController")
@Tag(name = "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));
}
}
商品导入与搜索演示
- 运行项目,访问Swagger API文档,访问地址:http://localhost:8080/swagger-ui/
- 通过
/esProduct/importAll
接口将数据库中的商品数据导入到Elasticsearch中;
- 通过
/esProduct/search/simple
接口进行商品搜索。
得到的结果是: