近期用到了spring boot3.0,当然jdk也一起跟着升级到了17,后面有需求要用到ES,前后捣鼓了一整子发现,大部分的人都还在用7.X及以下的ES,所以找了很久的资料,主要是ES的语法有点不太好用,一大堆的lambda,索性就自己封装了一个工具类,语法类似于mybatis-plus,这样的语法我想大家应该都比较熟悉,话不多说了,直接上代码
项目结构
TestController
package com.xxx.xxx.es.test.controller;
import co.elastic.clients.elasticsearch._types.query_dsl.RegexpQuery;
import com.alibaba.fastjson2.JSONObject;
import com.xxx.xxx.es.test.entity.User;
import com.xxx.xxx.es.test.utils.ElasticsearchUtils;
import com.xxx.xxx.es.test.utils.ElasticsearchUtils.*;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@RestController
@RequestMapping("/es")
@RequiredArgsConstructor
public class TestController {
private final ElasticsearchUtils elasticsearchUtils;
@GetMapping("/test")
public Object test() throws IOException {
String index = "user-index";
//删除索引测试
deleteIndexTest(index);
//创建索引测试
createIndexTest(index);
//新增数据测试
addTest(index);
//删除数据测试
deleteTest(index);
//修改数据测试
updateTest(index);
//条件查询测试
List<?> listResult = getListTest(index);
if (false){
return getResultData(listResult);
}
//分页查询测试
List<?> pageResult = getPageTest(index);
if (false){
return getResultData(pageResult);
}
//count
long count = elasticsearchUtils.count(new EsSearchWrapper(index));
//ES未封装方法
List<?> esFunResult = getFunResultTest(index);
if (true){
return getResultData(esFunResult);
}
//最后我还做了分组的聚合,es虽然提供了分组的功能,但是没有帮我们把数据聚合,这里我按mysql的逻辑聚合了,用法也与之相似
//分组查询测试
List<Object> groupList = getGroupListTest(index);
return getResultData(groupList);
}
private List<Object> getGroupListTest(String index) throws IOException {
//加点数据
List<User> addBatchList = new ArrayList<>();
User user = new User();
user.setId(9);
user.setName("赵六小儿");
user.setSex(1);
user.setCreateTime(new Date());
addBatchList.add(user);
user = new User();
user.setId(10);
user.setName("孙七小儿");
user.setSex(2);
user.setCreateTime(new Date());
addBatchList.add(user);
elasticsearchUtils.addBatch(index, addBatchList);
//方法1
// EsSearchWrapper groupWrapper = new EsSearchWrapper<>(index);
// groupWrapper.groupBy("sex", "name.keyword");
//当然这里可以不select,我就没有让它报错(和mysql不同,mysql会报错), 但是如果只是要这两个字分组,就没必要查其他字段了
//groupWrapper.select("sex","name");
//lambda也支持,基本一直
//EsSearchWrapper<User> groupWrapper = new EsSearchWrapper<>();
//List<User> groupList = elasticsearchUtils.list(groupWrapper, User.class);
//相当于 select sex,name from user group by sex,name
// List<Object> groupList = elasticsearchUtils.list(groupWrapper);
//方法2
//分组如果要使用到函数的话 比如:max,min等 就要调用另一个重载方法
EsSearchWrapper groupWrapper = new EsSearchWrapper<>(index);
groupWrapper.groupBy(EsFunction.of("sex")
, EsFunction.of("name.keyword")
, EsFunction.of("id", EsFunctionEnum.SUM));
//相当于 select sex,name,sum(id) from user group by sex,name
List<Object> groupList = elasticsearchUtils.list(groupWrapper);
return groupList;
}
private List<?> getFunResultTest(String index) throws IOException {
//当然ES和mysql还是有点区别的,我所以保留了一些方法
List<Object> funRestulList = elasticsearchUtils.list(new EsSearchWrapper<>(index)
//分词,模糊查询 相当于 select * from user where (name like '%赵%' or name like '%小%')
//当然ES 用的是倒排索引,效率会比mysql高 这里只是说查询出来的效果相似
// .match(MatchQuery.of(f -> f.field("name").query("赵小")))
//精确查询
// .term(TermQuery.of(f -> f.field("name").value("赵六小儿")))
//keyword text whidcard 等String类型 不支持
// .range(RangeQuery.of(f -> f.field("id").gte(JsonData.of("7"))))
//就是match的缩减版 通过slop 这个去控制范围
//slop == 1 相当于 select * from user where name like '%赵_小%'
//slop == 2 相当于 select * from user where (name like '%赵_小%' or name like '%赵__小%')
//slop == 3 相当于 select * from user where (name like '%赵_小%' or name like '%赵__小%' or name like '%赵___小%')
//以此类推
// .matchPhrase(MatchPhraseQuery.of(f -> f.field("name").query("赵小").slop(1)))
//类似 select * from user where name like '赵%'
// .prefix(PrefixQuery.of(f -> f.field("name").value("赵")))
// //*:通配符 ?:占位符 类似like *相当于% ?相当于_
// .wildcard(WildcardQuery.of(f -> f.field("name.keyword").value("赵*??")))
//就是允许你错别字,非特殊情况还是别用,非常消耗性能不说,还很容易和产品扯皮
// .fuzzy(FuzzyQuery.of(f -> f.field("name.keyword").value("赵漏小儿")))
//正则匹配 value 正则就是表达式
.regexp(RegexpQuery.of(f -> f.field("name.keyword").value(".*")))
);
return funRestulList;
}
private List<?> getPageTest(String index) throws IOException {
//分页查询
EsPage<User> esPage = new EsPage<>(1, 10);
EsSearchWrapper<User> pageWrapper = new EsSearchWrapper<>(index);
pageWrapper.like(User::getName, "小儿");
//注意:是and里需要拼接or的条件则必须先调用.or(),调用一次后,后面的拼接条件都是or
pageWrapper.and(wrapper -> wrapper.or().eq(User::getId, "6").eq(User::getId, "7"));
elasticsearchUtils.page(esPage, pageWrapper, User.class);
return esPage.getRecords();
}
private List<?> getListTest(String index) throws IOException {
//条件查询(全量滚动查询)
EsSearchWrapper<User> listWrapper = new EsSearchWrapper<>(index);
//如果AUTO或Keyword类型,会自动拼接.keyword 默认精确匹配
listWrapper.like(User::getName, "小儿");
// listWrapper.like("name.keyword", "小儿");
List<User> userList = elasticsearchUtils.list(listWrapper, User.class);
return userList;
}
private void updateTest(String index) throws IOException {
//修改方法1
User user = new User();
user.setId(6);
user.setName("赵六小儿");
elasticsearchUtils.updateById(index, user);
//修改方法2
// EsUpdateWrapper<User> esUpdateWrapper = new EsUpdateWrapper<>(index);
// esUpdateWrapper.doc(user);
// //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
// esUpdateWrapper.refresh(true);
List<User> updateBatchList = new ArrayList<>();
user = new User();
user.setId(7);
user.setName("孙七小儿");
updateBatchList.add(user);
user = new User();
user.setId(8);
user.setName("周八小儿");
updateBatchList.add(user);
//批量修改1
elasticsearchUtils.updateBatch(index, updateBatchList);
//批量修改2
// EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
// //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
// esBulkWrapper.refresh(true);
// esBulkWrapper.update(updateBatchList);
// elasticsearchUtils.exec(esBulkWrapper);
}
private void deleteTest(String index) throws IOException {
//删除方法1
elasticsearchUtils.deleteById(index, "3");
//删除方法2
// EsDeleteWrapper esDeleteWrapper = new EsDeleteWrapper();
// esDeleteWrapper.id("1");
// //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
// esDeleteWrapper.refresh(true);
// elasticsearchUtils.delete(esDeleteWrapper);
//批量删除方法1
elasticsearchUtils.deleteByIds(index, List.of("4", "5"));
//批量删除方法2
// EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
// //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
// esBulkWrapper.refresh(true);
// esBulkWrapper.delete(List.of("2","3"));
// elasticsearchUtils.exec(esBulkWrapper);
}
private void addTest(String index) throws IOException {
User user = new User();
user.setId(3);
user.setName("张三");
user.setSex(1);
user.setCreateTime(new Date());
//方法1
elasticsearchUtils.add(index, user);
//方法2
// EsCreateWrapper<User> esCreateWrapper = new EsCreateWrapper<>(index);
// esCreateWrapper.document(user);
// elasticsearchUtils.add(esCreateWrapper);
List<User> addBatchList = new ArrayList<>();
user = new User();
user.setId(4);
user.setName("李四");
user.setSex(1);
user.setCreateTime(new Date());
addBatchList.add(user);
user = new User();
user.setId(5);
user.setName("王五");
user.setSex(2);
user.setCreateTime(new Date());
addBatchList.add(user);
user = new User();
user.setId(6);
user.setName("赵六");
user.setSex(2);
user.setCreateTime(new Date());
addBatchList.add(user);
user = new User();
user.setId(7);
user.setName("孙七");
user.setSex(2);
user.setCreateTime(new Date());
addBatchList.add(user);
user = new User();
user.setId(8);
user.setName("周八");
user.setSex(2);
user.setCreateTime(new Date());
addBatchList.add(user);
//批量新增方法1
elasticsearchUtils.addBatch(index, addBatchList);
//批量新增方法2
// EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
// //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
// esBulkWrapper.refresh(true);
// esBulkWrapper.create(addBatchList);
// elasticsearchUtils.exec(esBulkWrapper);
}
private void createIndexTest(String index) throws IOException {
EsCreateIndexWrapper esCreateIndexWrapper = new EsCreateIndexWrapper(index);
//如果不设置,每次查询最多返回10000条,当然这个要根据情况而定设置太大会影响性能
esCreateIndexWrapper.setMaxResultWindow(100000);
/**
* 无论新增还是编辑,目前es是自动支持动态字段,所以加索引直接加实体字段即可,不需要主动去设置
* 当然如果自定义的话可以调用下面setDocClass方法
* 取@Field注解上的值 可以不设置,那么字段都是自动生成类型
*/
esCreateIndexWrapper.setDocClass(User.class);
elasticsearchUtils.createIndex(esCreateIndexWrapper);
}
private void deleteIndexTest(String index) throws IOException {
//删除索引方法1
elasticsearchUtils.deleteIndex(index);
//删除索引方法2
// EsDeleteIndexWrapper esDeleteIndexWrapper = new EsDeleteIndexWrapper(index);
// elasticsearchUtils.deleteIndex(esDeleteIndexWrapper);
}
private String getResultData(List<?> result) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>");
html.append("<html>");
html.append("<head>");
html.append("<meta charset=\"utf-8\" />");
html.append("<title></title>");
html.append("</head>");
html.append("<body>");
html.append("<table border=\"1\" width=\"50%\" align=\"left\">");
boolean title = true;
for (Object p : result) {
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(p));
if (title) {
html.append("<tr>");
for (String key : jsonObject.keySet()) {
html.append("<td>").append(key).append("</td>");
}
title = false;
}
html.append("</tr>");
html.append("<tr>");
for (String key : jsonObject.keySet()) {
html.append("<td>").append(Objects.nonNull(jsonObject.get(key)) ? String.valueOf(jsonObject.get(key)) : "").append("</td>");
}
html.append("</tr>");
}
html.append("</table>");
html.append("</body>");
html.append("</html>");
return html.toString();
}
}
User
package com.xxx.xxx.es.test.entity;
import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
import java.util.Date;
@Data
public class User implements Serializable {
//必须有一个id主键字段
@Field(type= FieldType.Auto)
private Integer id;
//自动生成text+keyword,既支持分词又支持全匹配,当然如果数据量过大且需要模糊查询的话,会影响性能,可以考虑用Wildcard
@Field(type= FieldType.Auto)
private String name;
/**
* 1:男 0:女
*/
@Field(type= FieldType.Integer)
private Integer sex;
@Field(type= FieldType.Date)
private Date createTime;
}
ElasticsearchUtils
package com.xxx.xxx.es.test.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.SourceFilter;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* @author: chr
* @since: 1.0.0
* @date: 2024/2/28 11:21
* @des 基于8.7.1版本客户端代码封装
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticsearchUtils {
private final ElasticsearchClient elasticsearchClient;
private static final String ID_FIELD = "id";
private static final String FLAG_KEYWORD = ".keyword";
//这个建议放到缓存中,如redis等,这个定义了静态是不会被回收的,是一种内存泄漏,这里只是案例,当然如果你的系统小型,接口不多,内存也足够,影响忽略不计
private static ConcurrentHashMap<Class<?>, String> lambdaCach = new ConcurrentHashMap<>(16);
/**
* 创建索引
*
* @param wrapper
* @return
* @throws IOException
*/
public boolean createIndex(EsCreateIndexWrapper wrapper) throws IOException {
wrapper.initIndexSetting();
log.info("索引:{} 创建-dsl json:{}", wrapper.getIndex(), wrapper.getCreateIndexRequestDsl().build().toString());
CreateIndexResponse createIndexResponse = elasticsearchClient.indices().create(c -> wrapper.getCreateIndexRequest());
log.info("索引:{} 创建成功{}", wrapper.getIndex(), createIndexResponse.toString());
return Boolean.TRUE;
}
/**
* 删除索引
*
* @param index 索引名称
* @return
* @throws IOException
*/
public boolean deleteIndex(String index) throws IOException {
return deleteIndex(new EsDeleteIndexWrapper(index));
}
/**
* 删除索引
*
* @param wrapper
* @return
* @throws IOException
*/
public boolean deleteIndex(EsDeleteIndexWrapper wrapper) throws IOException {
if (elasticsearchClient.indices().exists(request -> request.index(wrapper.getIndex())).value()) {
log.info("索引:{} 创建-dsl json:{}", wrapper.getIndex(), wrapper.getDeleteIndexRequestDsl().build().toString());
DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(s -> wrapper.getDeleteIndexRequest());
log.info("索引:{} 删除成功{}", wrapper.getIndex(), deleteIndexResponse.toString());
return Boolean.TRUE;
}
log.info("{}索引不存在", wrapper.getIndex());
return Boolean.FALSE;
}
/**
* 新增
*
* @param index 索引名称
* @param t doc
* @return
* @throws IOException
*/
public <T> boolean add(String index, T t) throws IOException {
return add(new EsCreateWrapper<T>(index).document(t).refresh(Boolean.TRUE));
}
/**
* 新增
*
* @param wrapper
* @return
* @throws IOException
*/
public <T> boolean add(EsCreateWrapper<T> wrapper) throws IOException {
log.info("索引:{} 新增-dsl json:{}", wrapper.getIndex(), wrapper.getCreateRequestDsl().build().toString());
CreateResponse createResponse = elasticsearchClient.create(c -> (ObjectBuilder) wrapper.getCreateRequest());
log.info("索引:{} 新增成功 {}", wrapper.getIndex(), createResponse.toString());
return Boolean.TRUE;
}
/**
* 删除
*
* @param index 索引名称
* @param id 实体id
* @return
* @throws IOException
*/
public boolean deleteById(String index, String id) throws IOException {
return delete(new EsDeleteWrapper(index).id(id).refresh(Boolean.TRUE));
}
/**
* 删除
*
* @param wrapper
* @return
* @throws IOException
*/
public boolean delete(EsDeleteWrapper wrapper) throws IOException {
log.info("索引:{} 删除-dsl json:{}", wrapper.getIndex(), wrapper.getDeleteRequestDsl().build().toString());
DeleteResponse deleteResponse = elasticsearchClient.delete(i -> wrapper.getDeleteRequest());
log.info("索引:{} 删除成功:{}", wrapper.getIndex(), deleteResponse.toString());
return Boolean.TRUE;
}
/**
* 更新
*
* @param index 索引名称
* @param t doc实体
* @return
* @throws IOException
*/
public <T> boolean updateById(String index, T t) throws IOException {
return updateById(new EsUpdateWrapper<T>(index).doc(t).refresh(Boolean.TRUE), t.getClass());
}
/**
* 更新
*
* @param wrapper
* @return
* @throws IOException
*/
public <T> boolean updateById(EsUpdateWrapper<T> wrapper, Class<?> tClass) throws IOException {
log.info("索引{} 更新-dsl json:{}", wrapper.getIndex(), wrapper.getUpdateRequestDsl().build().toString());
UpdateResponse<T> updateResponse = (UpdateResponse<T>) elasticsearchClient.update(u -> (ObjectBuilder) wrapper.getUpdateRequest(), tClass);
log.info("索引{} 更新成功{}", wrapper.getIndex(), updateResponse.toString());
return Boolean.TRUE;
}
/**
* 根据Id查询
*
* @param index 索引名称
* @param id
* @return
* @throws IOException
*/
public Object getById(String index, String id) throws IOException {
return getById(index, id, Object.class);
}
/**
* 根据Id查询
*
* @param index 索引名称
* @param id
* @param tClass docClass
* @return
* @throws IOException
*/
public <T> T getById(String index, String id, Class<T> tClass) throws IOException {
List<T> list = list(new EsSearchWrapper<T>(index).eq(ID_FIELD, id), tClass);
if (CollectionUtils.isEmpty(list)) {
return null;
}
return list.get(0);
}
/**
* 批量新增
*
* @param index 索引名称
* @param docList doc实体集合
* @return
* @throws IOException
*/
public <T> boolean addBatch(String index, List<T> docList) throws IOException {
return exec(new EsBulkWrapper<T>(index).create(docList).refresh(Boolean.TRUE));
}
/**
* 批量更新
*
* @param index 索引名称
* @param docList doc实体集合
* @return
* @throws IOException
*/
public <T> boolean updateBatch(String index, List<T> docList) throws IOException {
return exec(new EsBulkWrapper<T>(index).update(docList).refresh(Boolean.TRUE));
}
/**
* 批量删除
*
* @param index 索引名称
* @param ids 实体ids
* @return
* @throws IOException
*/
public boolean deleteByIds(String index, List<String> ids) throws IOException {
return exec(new EsBulkWrapper<>(index).delete(ids).refresh(Boolean.TRUE));
}
/**
* 根据Id集合查询
*
* @param index 索引名称
* @param ids
* @return
* @throws IOException
*/
public List<Object> getByIds(String index, List<String> ids) throws IOException {
return getByIds(index, ids, Object.class);
}
/**
* 根据Id集合查询
*
* @param index 索引名称
* @param ids
* @param tClass docClass
* @return
* @throws IOException
*/
public <T> List<T> getByIds(String index, List<String> ids, Class<T> tClass) throws IOException {
return list(new EsSearchWrapper<T>(index).in(ID_FIELD, ids), tClass);
}
/**
* 批量操作
*
* @param wrapper
* @return
* @throws IOException
*/
public <T> boolean exec(EsBulkWrapper<T> wrapper) throws IOException {
log.info("索引:{} 批量操作-dsl json:{}", wrapper.getIndex(), wrapper.getBulkRequestDsl().build().toString());
BulkResponse bulkResponse = elasticsearchClient.bulk(s -> wrapper.getBulkRequest());
log.info("索引:{} 批量操作成功:{}", wrapper.getIndex(), bulkResponse.toString());
return Boolean.TRUE;
}
/**
* 列表查询
*
* @param esSearchWrapper 搜索条件
* @return
* @throws IOException
*/
public long count(EsSearchWrapper esSearchWrapper) throws IOException {
log.info("索引{} 数量查询-dsl json:{}", esSearchWrapper.getIndex(), new CountRequest.Builder().index(esSearchWrapper.getIndex()).query(q -> q.bool(b -> esSearchWrapper.getBoolQueryCountDsl())).build().toString());
CountResponse countResponse = elasticsearchClient.count(s -> new CountRequest.Builder().index(esSearchWrapper.getIndex()).query(q -> q.bool(b -> esSearchWrapper.getBoolQueryCount())));
log.info("索引{} 数量查询结果 {}", esSearchWrapper.getIndex(), countResponse.toString());
return countResponse.count();
}
/**
* 列表查询
*
* @param esSearchWrapper 搜索条件
* @return
* @throws IOException
*/
public List<Object> list(EsSearchWrapper<Object> esSearchWrapper) throws IOException {
return list(esSearchWrapper, Object.class);
}
/**
* 列表查询
*
* @param esSearchWrapper 搜索条件
* @param tClass doc类型
* @return
* @throws IOException
*/
public <T> List<T> list(EsSearchWrapper<T> esSearchWrapper, Class<T> tClass) throws IOException {
List<T> resultList = new ArrayList<>();
long total = 0;
//如果不设置返回窗口条数,es每次最多查询10000条
esSearchWrapper.size(10000);
if (Objects.nonNull(esSearchWrapper.getCustomSize())) {
esSearchWrapper.size(esSearchWrapper.getCustomSize());
}else {
//虽然es查询可以设置参数返回总数,但这里先查询总数的原因是判断是否需要滚动查询,如果数据量不高的情况下无需滚动查询,避免造成游标不足的情况
//当然这里性能最优的是将滚动查询与非滚动查询分开两个方法,这里的list查询是为了兼容两种情况,为解决数据量过大如果不采用滚动查询是返回不了所有数据的
total = count(esSearchWrapper);
if (total > 10000){
//没有设置条数才使用滚动查询 滚动查询默认游标只有500个,并发高的情况尽量别使用滚动查询
esSearchWrapper.scroll(2);
}
}
esSearchWrapper.initBoolQuery();
log.info("索引{} 列表查询-dsl json:{}", esSearchWrapper.getIndex(), esSearchWrapper.getSearchRequestDsl().build().toString());
SearchResponse<Object> searchResponse = elasticsearchClient.search(s -> esSearchWrapper.getSearchRequest(), Object.class);
log.info("索引{} 列表查询结果 {}", esSearchWrapper.getIndex(), searchResponse.toString());
//如果设置了自定义size, 则无需滚动查询
if (Objects.nonNull(esSearchWrapper.getCustomSize())) {
searchResponse.hits().hits().forEach(p -> resultList.add(BeanUtil.copyProperties(p.source(), tClass)));
return resultList;
}
//分组数据聚合处理
putResultData(searchResponse.aggregations(), resultList, tClass, searchResponse.hits().hits());
//超过1w,则采用游标查询
String scrollId = searchResponse.scrollId();
for (var i = 10000; i < total; i = i + 10000) {
ScrollResponse<Object> scrollSearch = elasticsearchClient.scroll(s -> s.scrollId(scrollId).scroll(t -> t.time("2s")), Object.class);
log.info("索引{} 滚动查询结果 {}", esSearchWrapper.getIndex(), scrollSearch.toString());
putResultData(scrollSearch.aggregations(), resultList, tClass, scrollSearch.hits().hits());
}
//清除游标
if (StringUtils.isNotBlank(scrollId)) {
elasticsearchClient.clearScroll(c -> c.scrollId(scrollId));
}
return resultList;
}
private <T> void putResultData(Map<String, Aggregate> aggregateMap, List<T> resultList, Class<T> tClass, List<Hit<Object>> hits) {
if (CollUtil.isNotEmpty(aggregateMap)) {
//如果是分组则返回分组后的数据即可
List<JSONObject> groupJsonList = new ArrayList<>();
aggregateMap.forEach((k, v) -> putGroup(k, v, groupJsonList, null));
resultList.addAll(BeanUtil.copyToList(groupJsonList, tClass));
} else {
hits.forEach(p -> resultList.add(BeanUtil.copyProperties(p.source(), tClass)));
}
}
private void putGroup(String key, Aggregate aggregate, List<JSONObject> groupJsonList, JSONObject groupJson) {
List<?> tBucketList = getTBucketList(aggregate);
for (Object tBucket : tBucketList) {
if (Objects.isNull(groupJson)) {
groupJson = new JSONObject();
} else {
groupJson = BeanUtil.copyProperties(groupJson, JSONObject.class);
}
groupJson.put(key.replace(FLAG_KEYWORD, ""), getBucketValue(tBucket, aggregate));
Map<String, Aggregate> aggregateMap = getTBucketAggregations(tBucket, aggregate);
if (CollUtil.isNotEmpty(aggregateMap)) {
JSONObject finalGroupJson = groupJson;
aggregateMap.forEach((k, v) -> putGroup(k, v, groupJsonList, finalGroupJson));
} else {
groupJsonList.add(groupJson);
}
}
}
private Map<String, Aggregate> getTBucketAggregations(Object tBucket, Aggregate aggregate) {
if (aggregate.isSterms()) {
StringTermsBucket stringTermsBucket = (StringTermsBucket) tBucket;
return stringTermsBucket.aggregations();
} else if (aggregate.isLterms()) {
LongTermsBucket longTermsBucket = (LongTermsBucket) tBucket;
return longTermsBucket.aggregations();
} else if (aggregate.isDterms()) {
DoubleTermsBucket doubleTermsBucket = (DoubleTermsBucket) tBucket;
return doubleTermsBucket.aggregations();
}
return new HashMap<>();
}
private Object getBucketValue(Object tBucket, Aggregate aggregate) {
if (aggregate.isSterms()) {
StringTermsBucket stringTermsBucket = (StringTermsBucket) tBucket;
return stringTermsBucket.key().stringValue();
} else if (aggregate.isLterms()) {
LongTermsBucket longTermsBucket = (LongTermsBucket) tBucket;
return longTermsBucket.key();
} else if (aggregate.isDterms()) {
DoubleTermsBucket doubleTermsBucket = (DoubleTermsBucket) tBucket;
return doubleTermsBucket.key();
} else if (aggregate.isSum()) {
SumAggregate sumAggregate = (SumAggregate) tBucket;
return sumAggregate.value();
} else if (aggregate.isMax()) {
MaxAggregate maxAggregate = (MaxAggregate) tBucket;
return maxAggregate.value();
} else if (aggregate.isAvg()) {
AvgAggregate avgAggregate = (AvgAggregate) tBucket;
return avgAggregate.value();
} else if (aggregate.isMin()) {
MinAggregate minAggregate = (MinAggregate) tBucket;
return minAggregate.value();
} else if (aggregate.isValueCount()) {
ValueCountAggregate valueCountAggregate = (ValueCountAggregate) tBucket;
return valueCountAggregate.value();
}
return null;
}
private List<?> getTBucketList(Aggregate aggregate) {
//目前只定义了三种类型 string,long,double,有需要可自行扩展
if (aggregate.isSterms()) {
return aggregate.sterms().buckets().array();
} else if (aggregate.isLterms()) {
return aggregate.lterms().buckets().array();
} else if (aggregate.isDterms()) {
return aggregate.dterms().buckets().array();
} else if (aggregate.isSum()) {
return List.of(aggregate.sum());
} else if (aggregate.isMax()) {
return List.of(aggregate.max());
} else if (aggregate.isAvg()) {
return List.of(aggregate.avg());
} else if (aggregate.isMin()) {
return List.of(aggregate.min());
} else if (aggregate.isValueCount()) {
return List.of(aggregate.valueCount());
}
return new ArrayList<>();
}
/**
* 分页查询
*
* @param esPage 分页参数
* @param wrapper
* @return
* @throws IOException
*/
public List<Object> page(EsPage<Object> esPage, EsSearchWrapper<Object> wrapper) throws IOException {
return page(esPage, wrapper, Object.class);
}
/**
* 分页查询
*
* @param esPage 分页参数
* @param wrapper
* @param tClass
* @return
* @throws IOException
* @explain 用法与mybatisPlus语法相似
* EsSearchWrapper<Org> wrapper = new EsSearchWrapper<>(index);
* wrapper.eq("id",2);
* //支持lambda
* wrapper.eq(Org::getId,1);
* //支持条件拼接 如果想用"或"条件只需要调用or()方法
* warpper.and(w->w.or().eq(Org::getId,1).or().eq(Org::getId,2)))
*/
public <T> List<T> page(EsPage<T> esPage, EsSearchWrapper<T> wrapper, Class<T> tClass) throws IOException {
long from = (esPage.getCurrent() - 1L) * esPage.getSize();
//设置参数
wrapper.from((int) from).size((int) esPage.getSize()).trackTotalHits(Boolean.TRUE).initBoolQuery();
log.info("索引{} 分页查询-dsl json:{}", wrapper.getIndex(), wrapper.getSearchRequestDsl().build().toString());
SearchResponse<Object> searchResponse = elasticsearchClient.search(s -> wrapper.getSearchRequest(), Object.class);
log.info("索引{} 分页查询结果:{}", wrapper.getIndex(), searchResponse.toString());
esPage.setTotal(searchResponse.hits().total().value());
esPage.setPages((esPage.getTotal() + esPage.getSize() - 1) / esPage.getSize());
List<T> records = new ArrayList<>();
searchResponse.hits().hits().forEach(p -> records.add(BeanUtil.copyProperties(p.source(), tClass)));
esPage.setRecords(records);
return records;
}
@Getter
public static class EsCreateIndexWrapper {
CreateIndexRequest.Builder createIndexRequest = new CreateIndexRequest.Builder();
//用于打印dsl json
CreateIndexRequest.Builder createIndexRequestDsl = new CreateIndexRequest.Builder();
IndexSettings.Builder indexSetting = new IndexSettings.Builder();
IndexSettings.Builder indexSettingDsl = new IndexSettings.Builder();
private String index;
public void setIndex(String index) {
this.index = index;
this.createIndexRequest.index(index);
this.createIndexRequestDsl.index(index);
}
private void initIndexSetting() {
this.createIndexRequest.settings(t -> indexSetting);
this.createIndexRequestDsl.settings(t -> indexSettingDsl);
}
public EsCreateIndexWrapper() {
}
public EsCreateIndexWrapper(String index) {
this.setIndex(index);
}
/**
* 主要是分页查询的翻页是否超10000
*
* @param value
* @return
*/
public EsCreateIndexWrapper setMaxResultWindow(Integer value) {
this.indexSetting.maxResultWindow(value);
this.indexSettingDsl.maxResultWindow(value);
return this;
}
public EsCreateIndexWrapper setDocClass(Class<?> tClass) {
if (Objects.isNull(tClass)) {
return this;
}
List<java.lang.reflect.Field> fieldList = new ArrayList<>();
while (Objects.nonNull(tClass)) {
fieldList.addAll(new ArrayList<>(Arrays.asList(tClass.getDeclaredFields())));
tClass = tClass.getSuperclass();
}
Map<String, Property> map = new HashMap<>();
for (java.lang.reflect.Field field : fieldList) {
Field fieldAnnotation = field.getAnnotation(Field.class);
if (Objects.nonNull(fieldAnnotation)) {
switch (fieldAnnotation.type()) {
case Text:
map.put(field.getName(), Property.of(f -> f.text(t -> t)));
break;
case Keyword:
map.put(field.getName(), Property.of(f -> f.keyword(k -> k)));
break;
case Wildcard:
map.put(field.getName(), Property.of(f -> f.wildcard(k -> k)));
break;
case Long:
map.put(field.getName(), Property.of(f -> f.long_(k -> k)));
break;
case Integer:
map.put(field.getName(), Property.of(f -> f.integer(k -> k)));
break;
case Double:
map.put(field.getName(), Property.of(f -> f.double_(k -> k)));
break;
case Date:
map.put(field.getName(), Property.of(f -> f.date(k -> k)));
break;
//其他的自行扩展
default:
break;
}
}
}
if (CollUtil.isNotEmpty(map)) {
createIndexRequest.mappings(m -> m.properties(map));
}
return this;
}
public EsCreateIndexWrapper setMaxOpenScrollContext(Integer value) {
if (Objects.nonNull(value)) {
indexSetting.maxSlicesPerScroll(value);
indexSettingDsl.maxSlicesPerScroll(value);
}
return this;
}
//......(后续其他属性自行添加)
}
@Getter
public static class EsDeleteIndexWrapper {
DeleteIndexRequest.Builder deleteIndexRequest = new DeleteIndexRequest.Builder();
DeleteIndexRequest.Builder deleteIndexRequestDsl = new DeleteIndexRequest.Builder();
private String index;
public void setIndex(String index) {
this.index = index;
this.deleteIndexRequest.index(index);
this.deleteIndexRequestDsl.index(index);
}
public EsDeleteIndexWrapper() {
}
public EsDeleteIndexWrapper(String index) {
this.setIndex(index);
}
}
@Getter
public static class EsCreateWrapper<T> {
private CreateRequest.Builder<T> createRequest = new CreateRequest.Builder<>();
private CreateRequest.Builder<T> createRequestDsl = new CreateRequest.Builder<>();
private String index;
public void setIndex(String index) {
this.index = index;
this.createRequest.index(index);
this.createRequestDsl.index(index);
}
public EsCreateWrapper() {
}
public EsCreateWrapper(String index) {
this.setIndex(index);
}
public EsCreateWrapper<T> document(T t) {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(t));
//如果有主键id,则设置id主键
if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
createRequest.id(String.valueOf(jsonObject.get(ID_FIELD)));
createRequestDsl.id(String.valueOf(jsonObject.get(ID_FIELD)));
}
createRequest.document(t);
createRequestDsl.document(t);
return this;
}
public EsCreateWrapper<T> refresh(boolean refresh) {
createRequest.refresh(refresh ? Refresh.True : Refresh.False);
createRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
return this;
}
}
@Getter
public static class EsDeleteWrapper {
private DeleteRequest.Builder deleteRequest = new DeleteRequest.Builder();
private DeleteRequest.Builder deleteRequestDsl = new DeleteRequest.Builder();
private String index;
public void setIndex(String index) {
this.index = index;
this.deleteRequest.index(index);
this.deleteRequestDsl.index(index);
}
public EsDeleteWrapper() {
}
public EsDeleteWrapper(String index) {
this.setIndex(index);
}
public EsDeleteWrapper id(String id) {
deleteRequest.id(id);
deleteRequestDsl.id(id);
return this;
}
public EsDeleteWrapper refresh(boolean refresh) {
deleteRequest.refresh(refresh ? Refresh.True : Refresh.False);
deleteRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
return this;
}
}
@Getter
public static class EsUpdateWrapper<T> {
UpdateRequest.Builder<Object, T> updateRequest = new UpdateRequest.Builder<>();
//用于打印dsl json
UpdateRequest.Builder<Object, T> updateRequestDsl = new UpdateRequest.Builder<>();
private String index;
public void setIndex(String index) {
this.index = index;
this.updateRequest.index(index);
this.updateRequestDsl.index(index);
}
public EsUpdateWrapper() {
}
public EsUpdateWrapper(String index) {
this.setIndex(index);
}
public EsUpdateWrapper<T> doc(T t) {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(t));
//如果有主键id,则设置id主键
if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
updateRequest.id(String.valueOf(jsonObject.get(ID_FIELD)));
updateRequestDsl.id(String.valueOf(jsonObject.get(ID_FIELD)));
}
updateRequest.doc(t);
updateRequestDsl.doc(t);
return this;
}
public EsUpdateWrapper<T> refresh(boolean refresh) {
updateRequest.refresh(refresh ? Refresh.True : Refresh.False);
updateRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
return this;
}
}
@Getter
public static class EsSearchWrapper<T> {
private SearchRequest.Builder searchRequest = new SearchRequest.Builder();
private SearchRequest.Builder searchRequestDsl = new SearchRequest.Builder();
private BoolQuery.Builder boolQuery = new BoolQuery.Builder();
private BoolQuery.Builder boolQueryDsl = new BoolQuery.Builder();
private BoolQuery.Builder boolQueryCount = new BoolQuery.Builder();
private BoolQuery.Builder boolQueryCountDsl = new BoolQuery.Builder();
private String index;
private Integer customSize;
public void setIndex(String index) {
this.index = index;
this.searchRequest.index(index);
this.searchRequestDsl.index(index);
//默认设置返回总条数
this.searchRequest.trackTotalHits(track -> track.enabled(Boolean.TRUE));
this.searchRequestDsl.trackTotalHits(track -> track.enabled(Boolean.TRUE));
this.orCondition = false;
}
public EsSearchWrapper<T> initBoolQuery() {
this.searchRequest.query(q -> q.bool(b -> this.boolQuery));
this.searchRequestDsl.query(q -> q.bool(b -> this.boolQueryDsl));
return this;
}
private boolean orCondition;
public EsSearchWrapper() {
}
public EsSearchWrapper(String index) {
this.setIndex(index);
}
public EsSearchWrapper<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
return eq(column, val);
}
return this;
}
public EsSearchWrapper<T> eq(SFunction<T, ?> column, Object val) {
String filedName = getfiledNameWithCach(column);
return eq(filedName, val);
}
public EsSearchWrapper<T> eq(boolean condition, String column, Object val) {
if (condition) {
return eq(column, val);
}
return this;
}
public EsSearchWrapper<T> eq(String column, Object val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
this.boolQuery.must(m -> m.term(t -> setTermQuery(column, t, val)));
this.boolQueryDsl.must(m -> m.term(t -> setTermQuery(column, t, val)));
this.boolQueryCount.must(m -> m.term(t -> setTermQuery(column, t, val)));
this.boolQueryCountDsl.must(m -> m.term(t -> setTermQuery(column, t, val)));
} else {
this.boolQuery.should(m -> m.term(t -> setTermQuery(column, t, val)));
this.boolQueryDsl.should(m -> m.term(t -> setTermQuery(column, t, val)));
this.boolQueryCount.should(m -> m.term(t -> setTermQuery(column, t, val)));
this.boolQueryCountDsl.should(m -> m.term(t -> setTermQuery(column, t, val)));
}
return this;
}
public EsSearchWrapper<T> in(boolean condition, SFunction<T, ?> column, Collection<?> vals) {
if (condition) {
return in(column, vals);
}
return this;
}
public EsSearchWrapper<T> in(SFunction<T, ?> column, Collection<?> vals) {
String filedName = getfiledNameWithCach(column);
return in(filedName, vals);
}
public EsSearchWrapper<T> in(boolean condition, String column, Collection<?> vals) {
if (condition) {
return in(column, vals);
}
return this;
}
public EsSearchWrapper<T> in(String column, Collection<?> vals) {
if (StringUtils.isBlank(column)) {
return this;
}
List<FieldValue> vList = new ArrayList<>();
vals.forEach(p -> vList.add(FieldValue.of(String.valueOf(p))));
if (!orCondition) {
boolQuery.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
boolQueryDsl.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
boolQueryCount.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
boolQueryCountDsl.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
} else {
boolQuery.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
boolQueryDsl.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
boolQueryCount.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
boolQueryCountDsl.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
}
return this;
}
public EsSearchWrapper<T> ne(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
return ne(column, val);
}
return this;
}
public EsSearchWrapper<T> ne(SFunction<T, ?> column, Object val) {
String filedName = getfiledNameWithCach(column);
return ne(filedName, val);
}
public EsSearchWrapper<T> ne(boolean condition, String column, Object val) {
if (condition) {
return ne(column, val);
}
return this;
}
public EsSearchWrapper<T> ne(String column, Object val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
boolQueryDsl.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
boolQueryCount.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
boolQueryCountDsl.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
} else {
boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
}
return this;
}
public EsSearchWrapper<T> gt(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
return gt(column, val);
}
return this;
}
public EsSearchWrapper<T> gt(SFunction<T, ?> column, Object val) {
String filedName = getfiledNameWithCach(column);
return gt(filedName, val);
}
public EsSearchWrapper<T> gt(boolean condition, String column, Object val) {
if (condition) {
return gt(column, val);
}
return this;
}
public EsSearchWrapper<T> gt(String column, Object val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
boolQueryDsl.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
boolQueryCount.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
boolQueryCountDsl.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
} else {
boolQuery.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
boolQueryDsl.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
boolQueryCount.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
boolQueryCountDsl.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
}
return this;
}
public EsSearchWrapper<T> ge(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
return ge(column, val);
}
return this;
}
public EsSearchWrapper<T> ge(SFunction<T, ?> column, Object val) {
String filedName = getfiledNameWithCach(column);
return ge(filedName, val);
}
public EsSearchWrapper<T> ge(boolean condition, String column, Object val) {
if (condition) {
return ge(column, val);
}
return this;
}
public EsSearchWrapper<T> ge(String column, Object val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
boolQueryDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
boolQueryCount.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
boolQueryCountDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
} else {
boolQuery.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
boolQueryDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
boolQueryCount.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
boolQueryCountDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
}
return this;
}
public EsSearchWrapper<T> lt(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
return lt(column, val);
}
return this;
}
public EsSearchWrapper<T> lt(SFunction<T, ?> column, Object val) {
String filedName = getfiledNameWithCach(column);
return lt(filedName, val);
}
public EsSearchWrapper<T> lt(boolean condition, String column, Object val) {
if (condition) {
return lt(column, val);
}
return this;
}
public EsSearchWrapper<T> lt(String column, Object val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
boolQueryDsl.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
boolQueryCount.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
boolQueryCountDsl.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
} else {
boolQuery.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
boolQueryDsl.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
boolQueryCount.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
boolQueryCountDsl.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
}
return this;
}
public EsSearchWrapper<T> le(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
return le(column, val);
}
return this;
}
public EsSearchWrapper<T> le(SFunction<T, ?> column, Object val) {
String filedName = getfiledNameWithCach(column);
return le(filedName, val);
}
public EsSearchWrapper<T> le(boolean condition, String column, Object val) {
if (condition) {
return le(column, val);
}
return this;
}
public EsSearchWrapper<T> le(String column, Object val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
boolQueryDsl.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
boolQueryCount.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
boolQueryCountDsl.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
} else {
boolQuery.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
boolQueryDsl.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
boolQueryCount.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
boolQueryCountDsl.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
}
return this;
}
public EsSearchWrapper<T> between(boolean condition, SFunction<T, ?> column, Object val1, Object val2) {
if (condition) {
return between(column, val1, val2);
}
return this;
}
public EsSearchWrapper<T> between(SFunction<T, ?> column, Object val1, Object val2) {
String filedName = getfiledNameWithCach(column);
return between(filedName, val1, val2);
}
public EsSearchWrapper<T> between(boolean condition, String column, Object val1, Object val2) {
if (condition) {
return between(column, val1, val2);
}
return this;
}
public EsSearchWrapper<T> between(String column, Object val1, Object val2) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryCount.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryCountDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
} else {
boolQuery.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryCount.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryCountDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
}
return this;
}
public EsSearchWrapper<T> notBetween(boolean condition, SFunction<T, ?> column, Object val1, Object val2) {
if (condition) {
return notBetween(column, val1, val2);
}
return this;
}
public EsSearchWrapper<T> notBetween(SFunction<T, ?> column, Object val1, Object val2) {
String filedName = getfiledNameWithCach(column);
return notBetween(filedName, val1, val2);
}
public EsSearchWrapper<T> notBetween(boolean condition, String column, Object val1, Object val2) {
if (condition) {
return notBetween(column, val1, val2);
}
return this;
}
public EsSearchWrapper<T> notBetween(String column, Object val1, Object val2) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryDsl.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryCount.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
boolQueryCountDsl.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
} else {
boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
}
return this;
}
public EsSearchWrapper<T> like(boolean condition, SFunction<T, ?> column, String val) {
if (condition) {
return like(column, val);
}
return this;
}
public EsSearchWrapper<T> like(SFunction<T, ?> column, String val) {
String filedName = getfiledNameWithCach(column);
return like(filedName, val);
}
public EsSearchWrapper<T> like(boolean condition, String column, String val) {
if (condition) {
return like(column, val);
}
return this;
}
public EsSearchWrapper<T> like(String column, String val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryCount.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryCountDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
} else {
boolQuery.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryCount.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryCountDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
}
return this;
}
public EsSearchWrapper<T> notLike(boolean condition, SFunction<T, ?> column, String val) {
if (condition) {
return notLike(column, val);
}
return this;
}
public EsSearchWrapper<T> notLike(SFunction<T, ?> column, String val) {
String filedName = getfiledNameWithCach(column);
return notLike(filedName, val);
}
public EsSearchWrapper<T> notLike(boolean condition, String column, String val) {
if (condition) {
return notLike(column, val);
}
return this;
}
public EsSearchWrapper<T> notLike(String column, String val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryCount.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
boolQueryCountDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
} else {
boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
}
return this;
}
public EsSearchWrapper<T> likeLeft(boolean condition, SFunction<T, ?> column, String val) {
if (condition) {
return likeLeft(column, val);
}
return this;
}
public EsSearchWrapper<T> likeLeft(SFunction<T, ?> column, String val) {
String filedName = getfiledNameWithCach(column);
return likeLeft(filedName, val);
}
public EsSearchWrapper<T> likeLeft(boolean condition, String column, String val) {
if (condition) {
return likeLeft(column, val);
}
return this;
}
public EsSearchWrapper<T> likeLeft(String column, String val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryCount.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryCountDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
} else {
boolQuery.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryCount.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryCountDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
}
return this;
}
public EsSearchWrapper<T> notLikeLeft(boolean condition, SFunction<T, ?> column, String val) {
if (condition) {
return notLikeLeft(column, val);
}
return this;
}
public EsSearchWrapper<T> notLikeLeft(SFunction<T, ?> column, String val) {
String filedName = getfiledNameWithCach(column);
return notLikeLeft(filedName, val);
}
public EsSearchWrapper<T> notLikeLeft(boolean condition, String column, String val) {
if (condition) {
return notLikeLeft(column, val);
}
return this;
}
public EsSearchWrapper<T> notLikeLeft(String column, String val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryCount.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
boolQueryCountDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
} else {
boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
}
return this;
}
public EsSearchWrapper<T> likeRight(boolean condition, SFunction<T, ?> column, String val) {
if (condition) {
return likeRight(column, val);
}
return this;
}
public EsSearchWrapper<T> likeRight(SFunction<T, ?> column, String val) {
String filedName = getfiledNameWithCach(column);
return likeRight(filedName, val);
}
public EsSearchWrapper<T> likeRight(boolean condition, String column, String val) {
if (condition) {
return likeRight(column, val);
}
return this;
}
public EsSearchWrapper<T> likeRight(String column, String val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryDsl.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryCount.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryCountDsl.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
} else {
boolQuery.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryDsl.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryCount.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryCountDsl.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
}
return this;
}
public EsSearchWrapper<T> notLikeRight(boolean condition, SFunction<T, ?> column, String val) {
if (condition) {
return notLikeRight(column, val);
}
return this;
}
public EsSearchWrapper<T> notLikeRight(SFunction<T, ?> column, String val) {
String filedName = getfiledNameWithCach(column);
return notLikeRight(filedName, val);
}
public EsSearchWrapper<T> notLikeRight(boolean condition, String column, String val) {
if (condition) {
return notLikeRight(column, val);
}
return this;
}
public EsSearchWrapper<T> notLikeRight(String column, String val) {
if (StringUtils.isBlank(column)) {
return this;
}
if (!orCondition) {
boolQuery.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryDsl.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryCount.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
boolQueryCountDsl.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
} else {
boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
}
return this;
}
public EsSearchWrapper<T> orderBy(SFunction<T, ?> column, SortOrder sortOrder) {
String filedName = getfiledNameWithCach(column);
return orderBy(filedName, sortOrder);
}
public EsSearchWrapper<T> orderBy(String column, SortOrder sortOrder) {
if (StringUtils.isBlank(column)) {
return this;
}
searchRequest.sort(sort -> sort.field(f -> f.field(column).order(sortOrder)));
searchRequestDsl.sort(sort -> sort.field(f -> f.field(column).order(sortOrder)));
return this;
}
public EsSearchWrapper<T> orderByAsc(SFunction<T, ?> column) {
String filedName = getfiledNameWithCach(column);
return orderByAsc(filedName);
}
public EsSearchWrapper<T> orderByAsc(String column) {
if (StringUtils.isBlank(column)) {
return this;
}
searchRequest.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Asc)));
searchRequestDsl.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Asc)));
return this;
}
public EsSearchWrapper<T> orderByDesc(SFunction<T, ?> column) {
String filedName = getfiledNameWithCach(column);
return orderByDesc(filedName);
}
public EsSearchWrapper<T> orderByDesc(String column) {
if (StringUtils.isBlank(column)) {
return this;
}
searchRequest.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Desc)));
searchRequestDsl.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Desc)));
return this;
}
public EsSearchWrapper<T> and(boolean condition, SFunction<EsSearchWrapper<T>, EsSearchWrapper<T>> fn) {
if (condition) {
return and(fn);
}
return this;
}
public EsSearchWrapper<T> and(SFunction<EsSearchWrapper<T>, EsSearchWrapper<T>> fn) {
boolQuery.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
boolQueryDsl.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
boolQueryCount.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
boolQueryCountDsl.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
return this;
}
public EsSearchWrapper<T> or() {
orCondition = true;
return this;
}
public EsSearchWrapper<T> from(Integer value) {
searchRequest.from(value);
searchRequestDsl.from(value);
return this;
}
public EsSearchWrapper<T> size(Integer value) {
searchRequest.size(value);
searchRequestDsl.size(value);
return this;
}
public EsSearchWrapper<T> trackTotalHits(Boolean value) {
searchRequest.trackTotalHits(track -> track.enabled(value));
searchRequestDsl.trackTotalHits(track -> track.enabled(value));
return this;
}
/**
* 秒
*
* @param value
* @return
*/
public EsSearchWrapper<T> scroll(Integer value) {
searchRequest.scroll(t -> t.time(value + "s"));
searchRequestDsl.scroll(t -> t.time(value + "s"));
return this;
}
public EsSearchWrapper<T> select(SFunction<T, ?>... columns) {
String[] values = new String[columns.length];
for (int i = 0; i < columns.length; i++) {
String filedName = getfiledNameWithCach(columns[i]);
if (filedName.contains(FLAG_KEYWORD)) {
filedName = filedName.replace(FLAG_KEYWORD, "");
}
values[i] = filedName;
}
return select(values);
}
public EsSearchWrapper<T> select(String... values) {
if (ArrayUtils.isEmpty(values)) {
return this;
}
List<String> fields = new ArrayList<>();
for (String value : values) {
if (StringUtils.isNotBlank(value)) {
if (value.contains(",")) {
fields.addAll(Arrays.stream(value.split(",")).distinct().toList());
} else {
fields.add(value);
}
}
}
if (CollUtil.isEmpty(fields)) {
return this;
}
searchRequest.source(s -> s.filter(SourceFilter.of(f -> f.includes(fields))));
searchRequestDsl.source(s -> s.filter(SourceFilter.of(f -> f.includes(fields))));
return this;
}
public EsSearchWrapper<T> groupBy(SFunction<T, ?>... columns) {
String[] values = new String[columns.length];
for (int i = 0; i < columns.length; i++) {
String filedName = getfiledNameWithCach(columns[i]);
values[i] = filedName;
}
return groupBy(values);
}
public EsSearchWrapper<T> groupBy(String... values) {
EsFunction<T>[] esFunction = new EsFunction[values.length];
for (int i = 0; i < values.length; i++) {
esFunction[i] = new EsFunction<T>();
esFunction[i].setColumn(values[i]);
}
return groupBy(esFunction);
}
public EsSearchWrapper<T> groupBy(EsFunction<T>... esFunctions) {
if (ArrayUtils.isEmpty(esFunctions)) {
return this;
}
//保证函数放到最后(否则es会报错)
esFunctions = esFunctionsSort(esFunctions);
Map<String, Aggregation.Builder.ContainerBuilder> map = new HashMap<>();
Map<String, Aggregation.Builder.ContainerBuilder> mapDsl = new HashMap<>();
for (EsFunction<T> esFunction : esFunctions) {
Aggregation.Builder aggregationBuilder = new Aggregation.Builder();
Aggregation.Builder.ContainerBuilder containerBuilder = getContainerBuilder(aggregationBuilder, esFunction);
map.put(esFunction.getColumn(), containerBuilder);
Aggregation.Builder aggregationBuilderDsl = new Aggregation.Builder();
Aggregation.Builder.ContainerBuilder containerBuilderDsl = aggregationBuilderDsl.terms(t -> t.field(esFunction.getColumn()));
mapDsl.put(esFunction.getColumn(), containerBuilderDsl);
}
for (int i = esFunctions.length - 1; i >= 0; i--) {
if (i == 0) {
Aggregation.Builder.ContainerBuilder containerBuilderFirst = map.get(esFunctions[i].getColumn());
Aggregation.Builder.ContainerBuilder containerBuilderFirstDsl = mapDsl.get(esFunctions[i].getColumn());
searchRequest.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilderFirst));
searchRequestDsl.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilderFirstDsl));
continue;
}
Aggregation.Builder.ContainerBuilder containerBuilder = map.get(esFunctions[i].getColumn());
Aggregation.Builder.ContainerBuilder containerBuilderDsl = mapDsl.get(esFunctions[i].getColumn());
Aggregation.Builder.ContainerBuilder containerBuilderPrev = map.get(esFunctions[i - 1].getColumn());
Aggregation.Builder.ContainerBuilder containerBuilderPrevDsl = mapDsl.get(esFunctions[i - 1].getColumn());
containerBuilderPrev.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilder));
containerBuilderPrevDsl.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilderDsl));
}
return this;
}
public EsSearchWrapper<T> last(boolean condition, LimitSize limitSize) {
if (condition) {
return last(limitSize);
}
return this;
}
public EsSearchWrapper<T> last(LimitSize limitSize) {
this.customSize = limitSize.getLimitSize();
return this;
}
public EsSearchWrapper<T> match(MatchQuery matchQuery) {
if (!orCondition) {
boolQuery.must(m -> m.match(matchQuery));
boolQueryDsl.must(m -> m.match(matchQuery));
boolQueryCount.must(m -> m.match(matchQuery));
boolQueryCountDsl.must(m -> m.match(matchQuery));
} else {
boolQuery.should(m -> m.match(matchQuery));
boolQueryDsl.should(m -> m.match(matchQuery));
boolQueryCount.should(m -> m.match(matchQuery));
boolQueryCountDsl.should(m -> m.match(matchQuery));
}
return this;
}
public EsSearchWrapper<T> term(TermQuery termQuery) {
if (!orCondition) {
boolQuery.must(m -> m.term(termQuery));
boolQueryDsl.must(m -> m.term(termQuery));
boolQueryCount.must(m -> m.term(termQuery));
boolQueryCountDsl.must(m -> m.term(termQuery));
} else {
boolQuery.should(m -> m.term(termQuery));
boolQueryDsl.should(m -> m.term(termQuery));
boolQueryCount.should(m -> m.term(termQuery));
boolQueryCountDsl.should(m -> m.term(termQuery));
}
return this;
}
public EsSearchWrapper<T> range(RangeQuery rangeQuery) {
if (!orCondition) {
boolQuery.must(m -> m.range(rangeQuery));
boolQueryDsl.must(m -> m.range(rangeQuery));
boolQueryCount.must(m -> m.range(rangeQuery));
boolQueryCountDsl.must(m -> m.range(rangeQuery));
} else {
boolQuery.should(m -> m.range(rangeQuery));
boolQueryDsl.should(m -> m.range(rangeQuery));
boolQueryCount.should(m -> m.range(rangeQuery));
boolQueryCountDsl.should(m -> m.range(rangeQuery));
}
return this;
}
public EsSearchWrapper<T> matchPhrase(MatchPhraseQuery matchPhraseQuery) {
if (!orCondition) {
boolQuery.must(m -> m.matchPhrase(matchPhraseQuery));
boolQueryDsl.must(m -> m.matchPhrase(matchPhraseQuery));
boolQueryCount.must(m -> m.matchPhrase(matchPhraseQuery));
boolQueryCountDsl.must(m -> m.matchPhrase(matchPhraseQuery));
} else {
boolQuery.should(m -> m.matchPhrase(matchPhraseQuery));
boolQueryDsl.should(m -> m.matchPhrase(matchPhraseQuery));
boolQueryCount.should(m -> m.matchPhrase(matchPhraseQuery));
boolQueryCountDsl.should(m -> m.matchPhrase(matchPhraseQuery));
}
return this;
}
public EsSearchWrapper<T> prefix(PrefixQuery prefixQuery) {
if (!orCondition) {
boolQuery.must(m -> m.prefix(prefixQuery));
boolQueryDsl.must(m -> m.prefix(prefixQuery));
boolQueryCount.must(m -> m.prefix(prefixQuery));
boolQueryCountDsl.must(m -> m.prefix(prefixQuery));
} else {
boolQuery.should(m -> m.prefix(prefixQuery));
boolQueryDsl.should(m -> m.prefix(prefixQuery));
boolQueryCount.should(m -> m.prefix(prefixQuery));
boolQueryCountDsl.should(m -> m.prefix(prefixQuery));
}
return this;
}
public EsSearchWrapper<T> wildcard(WildcardQuery wildcardQuery) {
if (!orCondition) {
boolQuery.must(m -> m.wildcard(wildcardQuery));
boolQueryDsl.must(m -> m.wildcard(wildcardQuery));
boolQueryCount.must(m -> m.wildcard(wildcardQuery));
boolQueryCountDsl.must(m -> m.wildcard(wildcardQuery));
} else {
boolQuery.should(m -> m.wildcard(wildcardQuery));
boolQueryDsl.should(m -> m.wildcard(wildcardQuery));
boolQueryCount.should(m -> m.wildcard(wildcardQuery));
boolQueryCountDsl.should(m -> m.wildcard(wildcardQuery));
}
return this;
}
public EsSearchWrapper<T> fuzzy(FuzzyQuery fuzzyQuery) {
if (!orCondition) {
boolQuery.must(m -> m.fuzzy(fuzzyQuery));
boolQueryDsl.must(m -> m.fuzzy(fuzzyQuery));
boolQueryCount.must(m -> m.fuzzy(fuzzyQuery));
boolQueryCountDsl.must(m -> m.fuzzy(fuzzyQuery));
} else {
boolQuery.should(m -> m.fuzzy(fuzzyQuery));
boolQueryDsl.should(m -> m.fuzzy(fuzzyQuery));
boolQueryCount.should(m -> m.fuzzy(fuzzyQuery));
boolQueryCountDsl.should(m -> m.fuzzy(fuzzyQuery));
}
return this;
}
public EsSearchWrapper<T> regexp(RegexpQuery regexpQuery) {
if (!orCondition) {
boolQuery.must(m -> m.regexp(regexpQuery));
boolQueryDsl.must(m -> m.regexp(regexpQuery));
boolQueryCount.must(m -> m.regexp(regexpQuery));
boolQueryCountDsl.must(m -> m.regexp(regexpQuery));
} else {
boolQuery.should(m -> m.regexp(regexpQuery));
boolQueryDsl.should(m -> m.regexp(regexpQuery));
boolQueryCount.should(m -> m.regexp(regexpQuery));
boolQueryCountDsl.should(m -> m.regexp(regexpQuery));
}
return this;
}
private EsFunction<T>[] esFunctionsSort(EsFunction<T>[] esFunctions) {
List<EsFunction> functionList = new ArrayList<>();
for (EsFunction<T> esFunction : esFunctions) {
if (Objects.isNull(esFunction.getEsFunctionEnum())) {
//如果为null,则放到首位
functionList.add(0, esFunction);
continue;
}
functionList.add(esFunction);
}
return functionList.toArray(new EsFunction[functionList.size()]);
}
private Aggregation.Builder.ContainerBuilder getContainerBuilder(Aggregation.Builder aggregationBuilder, EsFunction<T> esFunction) {
if (Objects.isNull(esFunction.getEsFunctionEnum())) {
return aggregationBuilder.terms(t -> t.field(esFunction.getColumn()));
}
return switch (esFunction.getEsFunctionEnum()) {
case COUNT -> aggregationBuilder.valueCount(count -> count.field(esFunction.getColumn()));
case MAX -> aggregationBuilder.max(max -> max.field(esFunction.getColumn()));
case MIN -> aggregationBuilder.min(min -> min.field(esFunction.getColumn()));
case AVG -> aggregationBuilder.avg(avg -> avg.field(esFunction.getColumn()));
case SUM -> aggregationBuilder.sum(sum -> sum.field(esFunction.getColumn()));
};
}
private String getfiledNameWithCach(SFunction<T, ?> column) {
String filedName = lambdaCach.get(column.getClass());
if (StringUtils.isNotBlank(filedName)) {
return filedName;
}
try {
Method method = column.getClass().getDeclaredMethod("writeReplace");
//不检测方法是否为public或者private 大大提高了性能(近10倍)
ReflectionUtils.makeAccessible(method);
SerializedLambda lambda = (SerializedLambda) method.invoke(column);
filedName = methodToProperty(lambda.getImplMethodName());
FieldType fieldType = getEsFieldType(lambda, filedName);
if (Objects.nonNull(fieldType) && (fieldType.equals(FieldType.Auto) || fieldType.equals(FieldType.Keyword))) {
//Auto类型默认不分词
filedName = filedName + FLAG_KEYWORD;
} else if (Objects.isNull(fieldType) && "()Ljava/lang/String;".equals(lambda.getImplMethodSignature())) {
//如果String类型且没有自定义ES类型,那么默认是text+keyword,则自动拼接.keyword(使用lambda默认不分词)
filedName = filedName + FLAG_KEYWORD;
}
if (StringUtils.isNotBlank(filedName)) {
lambdaCach.put(column.getClass(), filedName);
}
} catch (Exception e) {
log.error("getfiledNameWithCach-参数转换异常", e);
}
return filedName;
}
private FieldType getEsFieldType(SerializedLambda lambda, String filedName) {
FieldType fieldType = null;
try {
String methodTypeStr = lambda.getInstantiatedMethodType();
String entityPath = methodTypeStr.substring(methodTypeStr.indexOf("(L") + "(L".length(), methodTypeStr.indexOf(";)Ljava/lang/Object;"));
Class<?> clazz = Class.forName(entityPath.replace("/", "."));
java.lang.reflect.Field field = clazz.getDeclaredField(filedName);
Field fieldAnnotation = field.getAnnotation(Field.class);
if (Objects.nonNull(fieldAnnotation)) {
fieldType = fieldAnnotation.type();
}
} catch (Exception e) {
log.error("加载实体es-FieldType异常", e);
}
return fieldType;
}
private String methodToProperty(String name) {
if (name.startsWith("is")) {
name = name.substring(2);
} else {
if (!name.startsWith("get") && !name.startsWith("set")) {
return "";
}
name = name.substring(3);
}
if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
}
return name;
}
private TermQuery.Builder setTermQuery(String column, TermQuery.Builder t, Object val) {
t.field(column);
if (val instanceof Long) {
t.value(objectToLong(val));
} else if (val instanceof Integer) {
t.value(objectToInteger(val).longValue());
} else if (val instanceof Double) {
t.value(objectToDouble(val));
} else if (val instanceof Boolean) {
t.value(objectToBoolean(val));
} else {
t.value(String.valueOf(val));
}
return t;
}
private Integer objectToInteger(Object val) {
return (Integer) val;
}
private Boolean objectToBoolean(Object val) {
return (Boolean) val;
}
private Double objectToDouble(Object val) {
return (Double) val;
}
private Long objectToLong(Object val) {
return (Long) val;
}
}
@Getter
public static class EsBulkWrapper<T> {
BulkRequest.Builder bulkRequest = new BulkRequest.Builder();
List<BulkOperation> bulkOperations = new ArrayList<>();
//用于打印dsl json
BulkRequest.Builder bulkRequestDsl = new BulkRequest.Builder();
List<BulkOperation> bulkOperationsDsl = new ArrayList<>();
private String index;
public void setIndex(String index) {
this.index = index;
this.bulkRequest.index(index);
this.bulkRequestDsl.index(index);
}
public EsBulkWrapper() {
this.bulkRequest.operations(bulkOperations);
this.bulkRequestDsl.operations(bulkOperationsDsl);
}
public EsBulkWrapper(String index) {
this.bulkRequest.operations(bulkOperations);
this.bulkRequestDsl.operations(bulkOperationsDsl);
this.setIndex(index);
}
public EsBulkWrapper<T> delete(List<String> ids) {
ids.forEach(id -> {
bulkOperations.add(BulkOperation.of(b -> b.delete(d -> d.id(id))));
bulkOperationsDsl.add(BulkOperation.of(b -> b.delete(d -> d.id(id))));
});
return this;
}
public EsBulkWrapper<T> create(List<T> docList) {
docList.forEach(doc -> {
bulkOperations.add(BulkOperation.of(b -> b.index(i -> {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
//如果有主键id,则设置id主键
if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
i.id(String.valueOf(jsonObject.get(ID_FIELD)));
}
return i.document(doc);
})));
bulkOperationsDsl.add(BulkOperation.of(b -> b.index(i -> {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
//如果有主键id,则设置id主键
if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
i.id(String.valueOf(jsonObject.get(ID_FIELD)));
}
return i.document(doc);
})));
}
);
return this;
}
public EsBulkWrapper<T> update(List<T> docList) {
docList.forEach(doc -> {
bulkOperations.add(BulkOperation.of(b -> b.update(i -> {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
//如果有主键id,则设置id主键
if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
i.id(String.valueOf(jsonObject.get(ID_FIELD)));
}
return i.action(a -> a.doc(doc));
})));
bulkOperationsDsl.add(BulkOperation.of(b -> b.update(i -> {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
//如果有主键id,则设置id主键
if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
i.id(String.valueOf(jsonObject.get(ID_FIELD)));
}
return i.action(a -> a.doc(doc));
})));
}
);
return this;
}
public EsBulkWrapper<T> refresh(boolean refresh) {
bulkRequest.refresh(refresh ? Refresh.True : Refresh.False);
bulkRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
return this;
}
}
@Data
public static class EsPage<T> {
private long current = 1L;
private long size = 10L;
private long total = 0L;
private long pages = 0L;
private List<T> records = Collections.emptyList();
public EsPage() {
}
public EsPage(long current, long size) {
this.current = current;
this.size = size;
}
}
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}
@Data
public static class EsFunction<T> {
private String column;
private EsFunctionEnum esFunctionEnum;
public static <T> EsFunction of(SFunction<T, ?> sfColumn, EsFunctionEnum esFunctionEnum) {
String column = new EsSearchWrapper<T>().getfiledNameWithCach(sfColumn);
return of(column, esFunctionEnum);
}
public static EsFunction of(String column, EsFunctionEnum esFunctionEnum) {
EsFunction function = new EsFunction<>();
function.setColumn(column);
function.setEsFunctionEnum(esFunctionEnum);
return function;
}
public static <T> EsFunction of(SFunction<T, ?> sfColumn) {
String column = new EsSearchWrapper<T>().getfiledNameWithCach(sfColumn);
return of(column);
}
public static EsFunction of(String column) {
EsFunction function = new EsFunction<>();
function.setColumn(column);
return function;
}
}
@Getter
public static class LimitSize {
private Integer limitSize;
private LimitSize(Integer limitSize) {
this.limitSize = limitSize;
}
public static LimitSize of(Integer limitSize) {
return new LimitSize(limitSize);
}
}
@Getter
public enum EsFunctionEnum {
COUNT("count", "去重总数"),
MAX("max", "去重最大值"),
MIN("min", "去重最小值"),
AVG("avg", "去重平均值"),
SUM("sum", "去重总和"),
;
private final String code;
private final String desc;
EsFunctionEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
}
EsTestApplication
package com.xxx.xxx.es.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EsTestApplication {
public static void main(String[] args) {
SpringApplication.run(EsTestApplication.class, args);
}
}
application.yml
spring:
profiles:
active: dev
elasticsearch:
uris: localhost:9200
username: elastic
password: xxx
server:
port: 8080
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>xxx-es-test</artifactId>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
<relativePath></relativePath>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.31</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</project>
这是一个自建测试项目。
最后在总结两个问题吧
1.分页查询无法查询后1w条的数据
我想用过es的分页的都会发现这个问题,es默认最多只能返回符合条件的前1w条数据,如果想看最后几页的数据,超过了1w条就会报错,如果对性能要求没那么高的话,可以修改这个默认值,这里我在工具类里提供了一个方法可以在创建索引的时候去设置
2.打印dsl json串
当访问es出现异常的时候,我们需要排查问题,需要看日志,这时就需要看你发送的json是什么来分析你的条件是否正确,在我自测的时候发现,这个客户端的代码重写了一个toString()方法,可以打印出你发送的json串,如下图
但是客户端的对象大部分都是只能build一次,比如
如果重复调用会抛出"Object builders can only be used once",他只允许你创建一次。
所以我在工具类里会创建两个对象,一个用于执行,一个用于打印dsl-json
目前使用的话大概就是遇到这两个问题吧,希望能帮到大家0.0