1、POM
<!--es-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2、YAML配置
spring:
elasticsearch:
rest:
# 集群连接以","逗号分割http://192.168.80.121:9211,http://192.168.80.121:9212,http://192.168.80.121:9213
uris: http://192.168.80.121:9200
#username: user
#password: pwd
# 默认1S
connection-timeout: 2
# 默认30S
read-timeout: 10
3、封装Builder构造器
ConditionQueryBuilder:
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.RangeQueryBuilder;
/**
* @Author:
* @Date: 2022/8/25 16:46
* @Description: 条件构造器
**/
public class ConditionQueryBuilder {
private ConditionQueryBuilder() {
}
private static ConditionQueryBuilder conditionQueryBuilder;
public static synchronized ConditionQueryBuilder getInstance() {
if (conditionQueryBuilder == null) {
conditionQueryBuilder = new ConditionQueryBuilder();
}
return conditionQueryBuilder;
}
/**
* 匹配(等于)查询台条件
*
* @param filed
* @param value
*/
public MatchQueryBuilder match(String filed, String value) {
return new MatchQueryBuilder(filed, value);
}
/**
* 大于
*
* @param filed
* @param value
*/
public RangeQueryBuilder gt(String filed, Object value) {
return new RangeQueryBuilder(filed).gt(value);
}
/**
* 大于等于
*
* @param filed
* @param value
*/
public RangeQueryBuilder gte(String filed, Object value) {
return new RangeQueryBuilder(filed).gte(value);
}
/**
* 小于
*
* @param filed
* @param value
*/
public RangeQueryBuilder lt(String filed, Object value) {
return new RangeQueryBuilder(filed).lt(value);
}
/**
* 小于等于
*
* @param filed
* @param value
*/
public RangeQueryBuilder lte(String filed, Object value) {
return new RangeQueryBuilder(filed).lte(value);
}
}
HighlightFiledBuilder:
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
/**
* @Author:
* @Date: 2022/8/25 14:54
* @Description: 高亮构造器
**/
public class HighlightFiledBuilder {
/**
* 高亮的html开始
*/
private final String preTag = "<font style='color:red'>";
/**
* 高亮的html结束
*/
private final String postTag = "</font>";
/**
* 高亮字段名称
*
* @param filed
* @return
*/
public HighlightBuilder.Field highlightBuild(String filed) {
return new HighlightBuilder.Field(filed).preTags(preTag).postTags(postTag);
}
}
PageBuilder:
import org.springframework.data.domain.PageRequest;
/**
* @Author:
* @Date: 2022/8/25 14:54
* @Description: 分页构造器
**/
public class PageBuilder {
public PageRequest startPage(Integer pageSize, Integer pageNum) {
if (pageSize == null || pageNum == null) {
pageSize = 10;
pageNum = 1;
}
return PageRequest.of(pageNum - 1, pageSize);
}
}
NewPageBean:
import com.lhz.search.model.param.PageParam;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Author:
* @Date:
* @Version: 1.0
**/
public class NewPageBean<T> {
private Long total;
private List<T> list;
private List<Map<String, String>> highList;
private MyPage pageInfo;
public NewPageBean(PageParam param, SearchHits<T> search) {
Integer pageNum = param.getPageNum();
Integer pageSize = param.getPageSize();
long totalSize = search.getTotalHits();
//计算总页数
long totalPage = totalSize % pageSize == 0 ? totalSize / pageSize : totalSize / pageSize + 1;
// 查询结果字符串拼接
List<SearchHit<T>> searchHits = search.getSearchHits();
List<T> objList = new ArrayList<>();
try {
for (SearchHit hit : searchHits) {
T content = (T) hit.getContent();
objList.add(content);
Class<?> clazz = content.getClass();
// 由于使用了ES则数据量肯定大,必定会分页,所以在分页中 解析高亮字段
Map<String, List<String>> highlightFields = hit.getHighlightFields();
for (Map.Entry<String, List<String>> entry : highlightFields.entrySet()) {
String field = entry.getKey();
String value = entry.getValue().get(0);
Field cField = clazz.getDeclaredField(field);
cField.setAccessible(true);
cField.set(content, value);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("ES分页错误");
}
this.list = objList;
this.total = totalSize;
MyPage page = new MyPage();
page.setTotalPage(totalPage);
page.setPageNum(pageNum);
page.setPageSize(pageSize);
page.setPreviousPage(pageNum - 1);
page.setNextPage(pageNum + 1);
this.pageInfo = page;
}
public long getNum() {
return this.total;
}
public void setNum(Long total) {
this.total = total;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public MyPage getPageInfo() {
return pageInfo;
}
public void setPageInfo(MyPage pageInfo) {
this.pageInfo = pageInfo;
}
public List<Map<String, String>> getHighList() {
return highList;
}
public void setHighList(List<Map<String, String>> highList) {
this.highList = highList;
}
class MyPage {
private int pageNum;
private int pageSize;
private long totalPage;
private int nextPage;
private int previousPage;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalPage() {
return totalPage;
}
public void setTotalPage(long totalPage) {
this.totalPage = totalPage;
}
public int getNextPage() {
return nextPage;
}
public void setNextPage(int nextPage) {
this.nextPage = nextPage;
}
public int getPreviousPage() {
return previousPage;
}
public void setPreviousPage(int previousPage) {
this.previousPage = previousPage;
}
}
}
4、封装实体类
ProductAgg:
import lombok.Data;
/**
* @Author:
* @Date: 2022/8/25 22:19
* @Description:
**/
@Data
public class ProductAgg {
/**
* 聚合字段属性值
*/
private String name;
/**
* 数量
*/
private Long count;
/**
* 聚合结果
*/
private Object value;
}
ProductEntity:
import lombok.Data;
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;
/**
* @Author:
* @Date: 2022/8/25 21:31
* @Description: 创建索引为 shop_product,主分片3个,副本2个
**/
@Document(indexName = "shop_product", shards = 3, replicas = 2)
@Data
public class ProductEntity implements Serializable {
@Id
private String id;
/**
* 文本类型,使用ik分词器
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
/**
* 价格
*/
@Field(type = FieldType.Double)
private BigDecimal money;
/**
* 时间戳
*/
@Field(type = FieldType.Long)
private Long time;
/**
* 颜色
*/
@Field(type = FieldType.Keyword)
private String color;
}
5、封装请求类
PageParam:
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Author:
* @Date: 2020/11/14 17:33
* @Description:
**/
@Data
public class PageParam {
@ApiModelProperty("当前页码")
private Integer pageNum;
@ApiModelProperty("每页数量")
private Integer pageSize;
}
ProductQueryParam:
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* @author
*/
@ApiModel("ES查询参数")
@EqualsAndHashCode(callSuper = true)
@Data
public class ProductQueryParam extends PageParam {
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "最小金额")
private Double minMoney;
}
5、repository
import com.lhz.search.model.entity.ProductEntity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* @Author:
* @Date: 2022/8/25 21:52
* @Description:
**/
public interface ProductRepository extends ElasticsearchRepository<ProductEntity, String> {
}
6、Service
ProductService:
import com.lhz.search.model.entity.ProductEntity;
import com.lhz.search.model.param.ProductQueryParam;
/**
* @Author:
* @Date: 2022/8/25 21:59
* @Description:
**/
public interface ProductService {
/**
* 新增
*/
void save();
/**
* 修改
*
* @param productEntity
*/
void update(ProductEntity productEntity);
/**
* 根据Id查询
*
* @param id
* @return
*/
Object selectById(String id);
/**
* 查询所有
* @return
*/
Object selectByAll();
/**
* 根据Id删除
*
* @param id
*/
void deleteById(String id);
/**
* 复制条件查询
*
* @param param
* @return
*/
Object listQuery(ProductQueryParam param);
/**
* 聚合
*
* @return
*/
Object agg();
}
ProductServiceImpl:
import com.lhz.search.builder.ConditionQueryBuilder;
import com.lhz.search.builder.HighlightFiledBuilder;
import com.lhz.search.builder.PageBuilder;
import com.lhz.search.model.entity.ProductAgg;
import com.lhz.search.model.entity.ProductEntity;
import com.lhz.search.model.page.NewPageBean;
import com.lhz.search.model.param.ProductQueryParam;
import com.lhz.search.repository.ProductRepository;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedSum;
import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* @Author:
* @Date: 2022/8/25 22:00
* @Description:
**/
@Service
public class ProductServiceImpl implements ProductService {
@Resource
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Resource
private ProductRepository productRepository;
/**
* 新增
*/
@Override
public void save() {
String[] colors = {"red", "purple", "blue"};
String[] name = {"手机", "电脑", "微波炉", "电视"};
// 新增列表数据
List<ProductEntity> testList = new ArrayList<>();
long sys = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
// 模拟随机数
double random = Math.random() * 1000;
ProductEntity productEntity = new ProductEntity();
productEntity.setId(i + "");
productEntity.setName(name[i % 4]);
productEntity.setMoney(new BigDecimal(random));
productEntity.setTime(sys + i % 3);
productEntity.setColor(colors[i % 3]);
testList.add(productEntity);
}
// 新增成功后,在head或者kibana中就可以看到相应数据,如果save时数据Id存在则更新操作
// 批量新增
productRepository.saveAll(testList);
// 单个新增
ProductEntity productEntity = testList.get(9);
productEntity.setId("11");
productRepository.save(productEntity);
}
/**
* 修改
*
* @param productEntity
*/
@Override
public void update(ProductEntity productEntity) {
productRepository.save(productEntity);
}
/**
* 查询所有
*
* @return
*/
@Override
public Object selectByAll() {
return productRepository.findAll();
}
/**
* 根据Id查询
*
* @param id
* @return
*/
@Override
public Object selectById(String id) {
return productRepository.findById(id);
}
/**
* 根据Id删除
*
* @param id
*/
@Override
public void deleteById(String id) {
productRepository.deleteById(id);
// 批量删
// esTestRepository.deleteAll(List<ProductEntity>);
}
/**
* 复杂条件查询
* SearchQuery 总的查询
* BoolQueryBuilder 条件查询,可在后面加上must,mustNot,should等等
* MatchQueryBuilder 匹配查询
* TermQueryBuilder 倒排索引查询
* HighlightBuilder 高亮查询,用于设置要高亮的field
*
* @param param
* @return
*/
@Override
public Object listQuery(ProductQueryParam param) {
// 分页参数
PageRequest pageRequest = new PageBuilder().startPage(param.getPageSize(), param.getPageNum());
// 高亮字段 - 如果需要则配置(非必要)
String lightFiled = "name";
HighlightBuilder.Field highlightBuilder = new HighlightFiledBuilder().highlightBuild(lightFiled);
// 条件查询
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
ConditionQueryBuilder conditionQueryBuilder = ConditionQueryBuilder.getInstance();
// 比如:name必须包含“xxx”并且金额大于xx
if (param.getMinMoney() != null) {
boolQueryBuilder.must(conditionQueryBuilder.gte("money", param.getMinMoney()));
}
if (param.getName() != null) {
boolQueryBuilder.must(conditionQueryBuilder.match("name", param.getName()));
}
//查询实例
NativeSearchQuery query = new NativeSearchQueryBuilder()
// 设置条件
.withQuery(boolQueryBuilder)
.withQuery(boolQueryBuilder)
// 简易条件,单一条件直接设置
// .withQuery(conditionQueryBuilder.match("name", "手机"))
//设置高亮效果
.withHighlightFields(highlightBuilder)
//分页
.withPageable(pageRequest)
// 排序,分词的字段不能排序;比如:按time字段排序;ASC-升序,DESC-降序
.withSort(SortBuilders.fieldSort("time").order(SortOrder.DESC))
// 多字段排序
.withSort(SortBuilders.fieldSort("money").order(SortOrder.ASC))
.build();
SearchHits<ProductEntity> search = elasticsearchRestTemplate.search(query, ProductEntity.class);
// 封装分页参数
return new NewPageBean<>(param, search);
}
/**
* 分组聚合计算,先分组然后聚合计算
* 聚合可用直接进行terms聚合(分组查询),AggregationBuilders.terms("time").field("time");
* 聚合可用直接进行聚合计算(直接使用sum、max、min等), AggregationBuilders.sum("money").field("money");
*
* @return
*/
@Override
public Object agg() {
// 构建聚合,聚合之间可以进行嵌套聚合,如果要先分组然后sum或者avg时,需要嵌套
// 分组。terms分组名称、field分组字段;默认分组后的结果为count
TermsAggregationBuilder builder = AggregationBuilders.terms("colorAgg").field("color");
// 其他聚合,比如:AVG/SumAggregationBuilder等
SumAggregationBuilder sumBuilder = AggregationBuilders.sum("sumMoney").field("money");
builder.subAggregation(sumBuilder);
NativeSearchQuery query = new NativeSearchQueryBuilder()
.addAggregation(builder)
.build();
SearchHits<ProductEntity> search = elasticsearchRestTemplate.search(query, ProductEntity.class);
Aggregations aggregations = search.getAggregations();
if (aggregations != null) {
Terms terms = (Terms) aggregations.asMap().get("colorAgg");
List<ProductAgg> productAggList = new ArrayList<>();
// 遍历取出聚合字段列的值,与对应的数量
for (Terms.Bucket bucket : terms.getBuckets()) {
ProductAgg productAgg = new ProductAgg();
productAgg.setName(bucket.getKeyAsString());
productAgg.setCount(bucket.getDocCount());
// 默认value就是count
productAgg.setValue(bucket.getDocCount());
// 解析嵌套聚合
Aggregations sumAggregations = bucket.getAggregations();
if (sumAggregations != null) {
// ParsedXxx的选择需要根据聚合计算的方式来定
ParsedSum parsedSum = sumAggregations.get("sumMoney");
productAgg.setValue(parsedSum.getValueAsString());
}
productAggList.add(productAgg);
}
return productAggList;
} else {
return search;
}
}
}
7、Controller
import com.lhz.search.model.entity.ProductEntity;
import com.lhz.search.model.param.ProductQueryParam;
import com.lhz.search.service.ProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* @Author:
* @Date: 2020/6/25 23:07
* @Description: 对ES的操作,数据操作之前需要进行索引的创建
**/
@Api(tags = "ES管理")
@ApiSort(10)
@RestController
@RequestMapping("es")
@Slf4j
public class ProductController {
@Resource
private ProductService productService;
@ApiOperation(value = "新增文档数据", notes = "新增文档数据")
@PostMapping
public void save() {
productService.save();
}
@ApiOperation(value = "修改文档数据", notes = "修改文档数据")
@PutMapping
public void update(@RequestBody ProductEntity productEntity) {
productService.update(productEntity);
}
@ApiOperation(value = "查看所有文档", notes = "查看所有文档")
@GetMapping("/all")
public Object selectByAll() {
return productService.selectByAll();
}
@ApiOperation(value = "根据Id查看文档", notes = "根据Id查看文档")
@GetMapping("/{id}")
public Object selectById(@PathVariable("id") String id) {
return productService.selectById(id);
}
@ApiOperation(value = "删除文档", notes = "删除文档")
@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") String id) {
productService.deleteById(id);
}
@ApiOperation(value = "自定义查询", notes = "自定义查询")
@GetMapping("/list")
public Object listQuery(ProductQueryParam param) {
return productService.listQuery(param);
}
@ApiOperation(value = "聚合查询", notes = "聚合查询")
@PostMapping("/agg")
public Object agg() {
return productService.agg();
}
}