分布式索引检索系统ElasticSearch 用于实时数据查询。本文基于springboot搭建入门demo.
通过操作es 实现crud.
重点:学会套路
需要注意的是使用 NativeSearchQueryBuilder 实现分页查询,关键字模糊查询,字段的聚合查询 ,不同字段的排序查询。
数据库对应elasticsearch 中的索引字段的表
product表:
CREATE TABLE `product` (
`id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`image_url` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`brand_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
表的结构:
使用关键字模糊查询 es 会根据对应的name 字段 进行 ik 分词之后 来进行匹配。返回对应商品关键字的品牌集合,以及分页之后的匹配到的数据。
请求格式:
http://127.0.0.1/manager/like?name=商&pageNum=1&pageSize=2&brand=华为
name 查询的关键字
pageNum 当前页
pageSize 每页显示的数量
brand 指定品牌名称
环境搭建:
项目结构:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yellow</groupId>
<artifactId>ElasticsearchTest</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependies</artifactId>
<version>Greenwich.SP1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
</dependencies>
</project>
application.yml
#端口路径
server:
port: 80
servlet:
context-path: /
#配置ElasticSearch
spring:
data:
elasticsearch:
#名称
cluster-name: elasticsearch
#ip地址端口
cluster-nodes: 127.0.0.1:9300
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/elasticsearch?serverTimezone=Asia/Shanghai
username: root
password: 123456
service.impl
package com.yellow.es.service.impl;
import com.yellow.es.bean.Product;
import com.yellow.es.mapper.ProductMapper;
import com.yellow.es.repository.EsRepository;
import com.yellow.es.service.EsManagerService;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class EsManagerServiceImpl implements EsManagerService {
@Autowired
private ElasticsearchTemplate template;
@Autowired
private EsRepository esRepository;
@Autowired
private ProductMapper productMapper;
/**
* 创建索引库以及mapping
*/
@Override
public void create() {
template.createIndex(Product.class);
template.putMapping(Product.class);
}
@Override
public void importAll() {
List<Product> products = productMapper.selectAll();
if (products == null || products.size() <= 0) {
throw new RuntimeException("no data from database");
}
esRepository.saveAll(products);
}
@Override
public void addAndUpdate(Product product) {
if (product == null) {
throw new RuntimeException("product is null");
}
esRepository.save(product);
}
@Override
public void delete(Long id) {
esRepository.deleteById(id);
}
@Override
public List<Product> findByName(String name) {
if (StringUtils.isEmpty(name)) {
throw new RuntimeException(" name is null or size=0 ");
}
return esRepository.findByName(name);
}
/**
* 关键字模糊查询
*
* @param searchMap
* @return
*/
@Override
public Map<String, Object> findLikeName(Map searchMap) {
if (searchMap == null || searchMap.size() <= 0) {
throw new RuntimeException(" searchMap is null or size=0 ");
}
//创建返回对象
Map<String, Object> resultMap = new HashMap<>();
String keyword = (String) searchMap.get("name");
//构建boolQueryBuilder 用于单独添加条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (!StringUtils.isEmpty(keyword)) {
//存在name
boolQueryBuilder.must(QueryBuilders.matchQuery("name", keyword));
}
//分页
Object pageNum = searchMap.get("pageNum");
Object pageSize = searchMap.get("pageSize");
if (pageNum == null) {
pageNum = 1;
}
if (pageSize == null) {
pageSize = 2;
}
//构建原生查询对象
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
//排序
//获取排序的域
String sortField = (String) searchMap.get("sortField");
//获取排序规则
String sortMode = (String) searchMap.get("sortMode");
//创建排序对象
FieldSortBuilder sortBuilder = null;
if (StringUtils.isEmpty(sortField)) {
sortField = "id";
}
if ("desc".equals(sortMode)) {
sortBuilder = SortBuilders.fieldSort(sortField).order(SortOrder.DESC);
} else {
sortBuilder = SortBuilders.fieldSort(sortField).order(SortOrder.ASC);
}
SearchQuery searchQuery = builder
//模糊查询
.withQuery(boolQueryBuilder)
//排序
.withSort(sortBuilder)
//分页 当前页 1每页显示 2个数据
.withPageable(PageRequest.of(Integer.parseInt(pageNum.toString())-1 , Integer.parseInt(pageSize.toString()) ))
//高亮显示 设置需要加入的特定字段
.withHighlightFields(new HighlightBuilder.Field("name").preTags("<span style='color:red'>").postTags("</span>"))
//设置分组 名称 brand 指定的域 brandName下field的keyword
.addAggregation(AggregationBuilders.terms("brand").field("brandName.keyword"))
//构建
.build();
AggregatedPage<Product> aggregatedPage = template.queryForPage(searchQuery, Product.class, new SearchResultMapper() {
/**
* @param searchResponse 主要的操作对象 获取命中数 分片等等
* @param aClass 操作ElasticSearch的实体类 的字节码对象
* @param pageable 分页对象
* @param <T> 泛型 (ElasticSearch的实体类)
* @return
*/
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
List<T> resultList = new ArrayList<>();
//获取搜索命中
SearchHits hits = searchResponse.getHits();
//遍历命中数
for (SearchHit hit : hits) {
//获取高亮域 ,放入结果集中
Map<String, HighlightField> fields = hit.getHighlightFields();
Product product = new Product();
//获取fields 中的name 域 获取第一个片段
product.setName(fields.get("name").getFragments()[0].toString());
//转化为map
Map<String, Object> map = hit.getSourceAsMap();
//System.out.println("map = " + map);
//封装实体类
product.setId(Long.valueOf((map.get("id").toString())));
product.setPrice((Double) map.get("price"));
product.setImageUrl((String) map.get("imageUrl"));
product.setCreateTime(new Date((long) map.get("createTime")));
product.setBrandName(map.get("brandName").toString());
resultList.add((T) product);
}
//参数一:结果集泛型需要与方法的泛型一致
//参数二:传入分页对象
//参数三:命中总数
//参数四:传入所有的聚合
return new AggregatedPageImpl<T>(resultList, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//获取对应的集合,转换为集合
List<Product> collect = aggregatedPage.get().collect(Collectors.toList());
resultMap.put("list", collect);
resultMap.put("pageNum", pageNum);
resultMap.put("pageSize", pageSize);
//由于brandName 是String 类型
StringTerms stringTerms = (StringTerms) aggregatedPage.getAggregation("brand");
if (stringTerms != null) {
List<String> brandList = stringTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
}
return resultMap;
}
}
bean:
package com.yellow.es.bean;
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 javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* ElasticSearch 映射类
*
* @Docment indexName 索引库名称 可以任意写 一般来说写项目名
* type 类型 可以任意写 一般来所写对应的实体类
* shards 分区数 默认为5
* replicas 备份数 默认为1
* refreshInterval 刷新间隔时间 默认1s
* indexStoreType 索引文件存储类型 默认为fs
* createIndex 创建索引 默认为true
*/
@Document(indexName = "productinfo", type = "productMsg")
@Table(name = "product") //没有声明,就默认使用类名称去匹配
public class Product implements Serializable {
/**
* @Id 唯一标识
* @Field index 是否开启索引检索 默认为true
* store 是否独立存储 默认为false 就需要从_source中去解析
* type 存储类型 keyword 可以聚合查询
* analyzer 指定分词器 eg "ik_max_word"/"ik_smart" 中文分词器
*/
@Id
@Field(index = true, store = true, type = FieldType.Keyword)
private Long id;//编号
@Field(index = true, store = true, type = FieldType.Keyword, analyzer = "ik_max_word")
private String name;//商品名称
@Field(index = true, store = true, type = FieldType.Double)
private Double price;//商品价格
@Field(index = true, store = true, type = FieldType.Date)
private Date createTime;//创建时间
@Field(index = true, store = true, type = FieldType.Keyword)
private String brandName;//商品品牌
@Field(index = false, store = true, type = FieldType.Text)
private String imageUrl;//图片地址
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}
common:
package com.yellow.es.common;
public class Result<T> {
private Long code;//状态码
private String message;//信息
private T data;//数据
public Result(Long code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static Result Success(Long code, String message) {
return new Result(code, message, null);
}
public static <T> Result Success(Long code, String message, T data) {
return new Result(code, message, data);
}
public static Result Error(Long code, String message) {
return new Result(code, message, null);
}
public Long getCode() {
return code;
}
public void setCode(Long code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
controller:
package com.yellow.es.controller;
import com.yellow.es.bean.Product;
import com.yellow.es.common.Result;
import com.yellow.es.service.EsManagerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/manager")
public class EsManagerController {
@Autowired
private EsManagerService service;
/**
* 创建索引库
*/
@GetMapping("/create")
public Result create() {
try {
service.create();
} catch (Exception e) {
e.printStackTrace();
return Result.Error(20004L, "索引库创建失败");
}
return Result.Success(20001L, "索引库创建成功");
}
@GetMapping("/importAll")
public Result importAll() {
try {
service.importAll();
} catch (Exception e) {
e.printStackTrace();
return Result.Error(20004L, "导入数据失败");
}
return Result.Success(20004L, "导入数据成功");
}
@PostMapping
public Result addAndUpdate(@RequestBody Product product) {
try {
service.addAndUpdate(product);
} catch (Exception e) {
e.printStackTrace();
return Result.Error(20004L, "添加更新失败");
}
return Result.Success(20001L, "添加更新成功");
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Long id) {
try {
service.delete(id);
} catch (Exception e) {
e.printStackTrace();
return Result.Error(20004L, "删除失败");
}
return Result.Success(20001L, "删除成功");
}
/**
* 模糊查询
*
* @param name
* @return
*/
@GetMapping("/{name}")
public Result findByName(@PathVariable("name") String name) {
try {
List<Product> p = service.findByName(name);
return Result.Success(20001L, "关键字查询成功", p);
} catch (Exception e) {
e.printStackTrace();
return Result.Error(20004L, "关键字查询失败");
}
}
/**
* 模糊查询 高亮 分页等等
*
* @param queryMap
* @return
*/
@GetMapping("/like")
public Result findLikeName(@RequestParam Map queryMap) {
try {
Map<String,Object> p = service.findLikeName(queryMap);
return Result.Success(20001L, "模糊查询成功", p);
} catch (Exception e) {
e.printStackTrace();
return Result.Error(20004L, "模糊查询失败");
}
}
}
mapper:
package com.yellow.es.mapper;
import com.yellow.es.bean.Product;
import tk.mybatis.mapper.common.Mapper;
public interface ProductMapper extends Mapper<Product> {
}
repository:
package com.yellow.es.repository;
import com.yellow.es.bean.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
public interface EsRepository extends ElasticsearchRepository<Product, Long> {
/**
* 根据商品名称查询
*
* @param name
* @return
*/
public List<Product> findByName(String name);
}
service:
package com.yellow.es.service;
import com.yellow.es.bean.Product;
import java.util.List;
import java.util.Map;
public interface EsManagerService {
/**
* 创建索引库以及mapping
*/
public void create();
/**
* 将数据库的数据导入
*/
public void importAll();
public void addAndUpdate(Product product);
public void delete(Long id);
List<Product> findByName(String name);
Map<String,Object> findLikeName(Map searchMap);
}
启用类:
package com.yellow.es;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.yellow.es.mapper")
public class EsApplication {
public static void main(String[] args) {
SpringApplication.run(EsApplication.class, args);
}
}