maven依赖,使用了RestHighLevelClient6.4.0版本
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.4.0</version>
</dependency>
首先RestHighLevelClient配置,采用注入方式,启动就注册客户端bean
@Configuration
public class ESHighLevelRestClient {
public static final int CONNECT_TIMEOUT = 5000;
public static final int SOCKET_TIMEOUT = 60000;
public static final int MAX_RETRY_TIMEOUT = 60000;
public static final int WORK_THREADS = Runtime.getRuntime().availableProcessors();
private final String authEnable = "enable";
// 配置类,里面配置了es的地址,用户,密码
@Autowired
private ElasticsearchConfig elasticsearchConfig;
@Bean
public RestHighLevelClient restHighLevelClient() throws UnsupportedEncodingException {
ArrayList<HttpHost> httpHosts = Lists.newArrayList();
Map<String, String> hostsMap = Splitter.on(',').trimResults().omitEmptyStrings()
.withKeyValueSeparator(":")
.split(elasticsearchConfig.getHosts());
hostsMap.entrySet().stream().forEach(x -> {
httpHosts.add(new HttpHost(x.getKey(), Integer.valueOf(x.getValue()), "http"));
});
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts.toArray(new HttpHost[httpHosts.size()]));
// set es connection timeout
restClientBuilder.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
@Override
public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {
return requestConfigBuilder
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT);
}
}).setMaxRetryTimeoutMillis(MAX_RETRY_TIMEOUT);
// set es client works numbers
restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setDefaultIOReactorConfig(
IOReactorConfig.custom().setIoThreadCount(WORK_THREADS).build());
}
});
if (authEnable.equalsIgnoreCase(elasticsearchConfig.getAuth())) {
Header[] defaultHeaders = new Header[]{new BasicHeader("Authorization", getToken(elasticsearchConfig.getUsername(), elasticsearchConfig.getPassword()))};
restClientBuilder.setDefaultHeaders(defaultHeaders);
}
return new RestHighLevelClient(restClientBuilder);
}
private String getToken(String username, String password) throws UnsupportedEncodingException {
StringBuilder tokenBuilder = new StringBuilder();
tokenBuilder.append(username);
tokenBuilder.append(":");
tokenBuilder.append(password);
String token = new String(Base64.getEncoder().encode(tokenBuilder.toString().getBytes()), StandardCharsets.UTF_8);
return "Basic " + token;
}
}
封装abstract类,想要操作ES的实体需要继承这个类,重写packageElasticSearchBody()方法,封装查询条件
@Getter
@Setter
public abstract class BaseElasticSearchEntity {
/**
* ES的INDEX
*/
private String esIndex;
/**
* ES的TYPE
*/
private String esType;
/**
* ES唯一值ID
*/
private String esId;
protected BaseElasticSearchEntity() {
}
protected BaseElasticSearchEntity(String esIndex, String esType) {
this.esIndex = esIndex;
this.esType = esType;
}
protected BaseElasticSearchEntity(String esIndex, String esType, String esId) {
this.esIndex = esIndex;
this.esType = esType;
this.esId = esId;
}
/**
* 供子类封装查询条件
*
* @param boolQueryBuilder bool条件
*/
public abstract void packageElasticSearchBody(BoolQueryBuilder boolQueryBuilder);
}
操作ES的mapper
public interface ElasticSearchMapper<T extends BaseElasticSearchEntity> {
/**
* 创建ES索引库
*
* @param index index
* @param type type
* @param mapping 映射,为json字符串,
* 例如:{\"properties\":{\"name\":{\"type\":\"keyword\"},\"date1\":{\"type\":\"date\"},\"date2\":{\"type\":\"date\"},\"updateTime\":{\"type\":\"date\"}}}
* @return 是否创建成功
* @throws IOException 异常
*/
boolean createIndex(String index, String type, String mapping) throws IOException;
/**
* 删除索引
*
* @param index index
* @return 是否删除成功
* @throws IOException 异常
*/
boolean deleteIndex(String index) throws IOException;
/**
* 根据id查询数据是否存在于ES中
*
* @param entry index和type和id
* @return 是否存在
*/
boolean isExists(T entry);
/**
* 插入ES,指定id
*
* @param entry 实体
* @return 插入结果
*/
String insert(T entry);
/**
* 更新ES,指定id
*
* @param entry 实体
* @return 更新结果
*/
String update(T entry);
/**
* 删除ES,指定id
*
* @param entry index和type和id
* @return 删除结果
*/
String delete(T entry);
/**
* 根据id查询ES
*
* @param entry index和type和id
* @return 查询数据结果json字符串
*/
String selectById(T entry);
/**
* 多条件查询,正序排序
*
* @param entry 封装的查询条件
* @param sortField 排序字段的字段名,如:updateTime
* @param page 页码
* @param length 每页条数
* @return 查询结果,es封装
*/
ElasticSearchResponseEntity selectByMultiConditionAsc(T entry, String sortField, Integer page, Integer length);
/**
* 多条件查询,倒序排序
*
* @param entry 封装的查询条件
* @param sortField 排序字段的字段名,如:updateTime
* @param page 页码
* @param length 每页条数
* @return 查询结果,es封装
*/
ElasticSearchResponseEntity selectByMultiConditionDesc(T entry, String sortField, Integer page, Integer length);
}
mapper的实现类,具体增删改查方法
@Slf4j
@Repository("elasticSearchMapperImpl")
public class ElasticSearchMapperImpl<T extends BaseElasticSearchEntity> implements ElasticSearchMapper<T> {
@Autowired
private RestHighLevelClient restClient;
@Override
public boolean createIndex(String index, String type, String mapping) throws IOException {
CreateIndexRequest indexRequest = new CreateIndexRequest(index);
indexRequest.mapping(type, mapping, XContentType.JSON);
IndicesClient indicesClient = restClient.indices();
CreateIndexResponse createIndexResponse = indicesClient.create(indexRequest, RequestOptions.DEFAULT);
return createIndexResponse.isAcknowledged();
}
@Override
public boolean deleteIndex(String index) throws IOException {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(index);
IndicesClient indicesClient = restClient.indices();
AcknowledgedResponse delete = indicesClient.delete(deleteIndexRequest, RequestOptions.DEFAULT);
return delete.isAcknowledged();
}
@Override
public boolean isExists(T entry) {
GetRequest getRequest = new GetRequest(entry.getEsIndex(), entry.getEsType(), entry.getEsId());
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
try {
boolean exists = restClient.exists(getRequest, RequestOptions.DEFAULT);
log.info("查询ES是否存在数据,isExists:{},id:{}", exists, getRequest.id());
return exists;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String insert(T entry) {
IndexRequest request = new IndexRequest(entry.getEsIndex(), entry.getEsType(), entry.getEsId());
request.source(JSONObject.toJSONString(entry), XContentType.JSON);
request.create(true);
IndexResponse response;
try {
response = restClient.index(request, RequestOptions.DEFAULT);
String name = response == null ? null : response.getResult().name();
log.info("ES执行插入:index:{},type:{},id:{}", request.index(), request.type(), request.id());
return name;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String update(T entry) {
UpdateRequest request = new UpdateRequest(entry.getEsIndex(), entry.getEsType(), entry.getEsId());
request.doc(JSONObject.toJSONString(entry), XContentType.JSON);
UpdateResponse response;
try {
response = restClient.update(request, RequestOptions.DEFAULT);
String name = response == null ? null : response.getResult().name();
log.info("ES执行更新:index:{},type:{},id:{}", request.index(), request.type(), request.id());
return name;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String delete(T entry) {
DeleteRequest request = new DeleteRequest(entry.getEsIndex(), entry.getEsType(), entry.getEsId());
try {
DeleteResponse response = restClient.delete(request, RequestOptions.DEFAULT);
String name = response == null ? null : response.getResult().name();
log.info("ES执行删除:index:{},type:{},id:{}", request.index(), request.type(), request.id());
return name;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public String selectById(T entry) {
GetRequest request = new GetRequest(entry.getEsIndex(), entry.getEsType(), entry.getEsId());
try {
GetResponse response = restClient.get(request, RequestOptions.DEFAULT);
if (response.isExists()) {
return response.getSourceAsString();
} else {
return null;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public ElasticSearchResponseEntity selectByMultiConditionDesc(T entry, String sortField, Integer page, Integer length) {
return packageSelectData(selectByMultiCondition(entry, sortField, SortOrder.DESC, page, length));
}
@Override
public ElasticSearchResponseEntity selectByMultiConditionAsc(T entry, String sortField, Integer page, Integer length) {
return packageSelectData(selectByMultiCondition(entry, sortField, SortOrder.ASC, page, length));
}
/**
* 封装成ElasticSearchResponseEntity返回
*
* @param searchHits es查询的数据
* @return ElasticSearchResponseEntity
*/
private ElasticSearchResponseEntity packageSelectData(SearchHits searchHits) {
if (searchHits == null || searchHits.totalHits < 1) {
return new ElasticSearchResponseEntity(0L, new LinkedList<>());
}
List<String> list = new LinkedList<>();
Arrays.stream(searchHits.getHits()).forEach(hit -> {
String source = hit.getSourceAsString();
log.debug("ES查询数据:{}", source);
list.add(source);
});
return new ElasticSearchResponseEntity(searchHits.getTotalHits(), list);
}
/**
* 多条件查询,包含排序规则和分页功能
*
* @param entry 封装的查询条件
* @param sortField 排序字段的字段名
* 如果在mapping中未指定排序字段的类型为精确类型(如date或keyword),则此排序字段必须加上 .keyword 后缀,表示精准匹配,否则报错:[type=search_phase_execution_exception, reason=all shards failed]
* 例如:username.keyword,如果mapping指定了字段类型为date或者keyword,则直接传字段名即可,例如:keywordName1、updateTime
* @param sortOrder 排序规则,正序还是倒序
* @return 查询结果,es封装
*/
private SearchHits selectByMultiCondition(T entry, String sortField, SortOrder sortOrder, Integer page, Integer length) {
SearchRequest searchRequest = new SearchRequest(entry.getEsIndex());
searchRequest.types(entry.getEsType());
try {
BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
entry.packageElasticSearchBody(boolBuilder);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.sort(new FieldSortBuilder(sortField).order(sortOrder));
sourceBuilder.query(boolBuilder);
setPaging(sourceBuilder, page, length);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restClient.search(searchRequest, RequestOptions.DEFAULT);
return searchResponse.getHits();
} catch (Exception e) {
log.error("===》 es【多条件】查询,报错!entry={}", searchRequest.source().toString(), e);
return null;
}
}
/**
* 设置es查询的分页配置
*
* ElasticSearchConstant.MAX_SIZE = ES默认10000条,这个自定义的常量无法超过默认值
* @param sourceBuilder builder
* @param page 页码
* @param length 每页条数
*/
private void setPaging(SearchSourceBuilder sourceBuilder, Integer page, Integer length) {
if (page != null && length != null) {
length = Math.min(length, ElasticSearchConstant.MAX_SIZE);
int from = (page - 1) * length;
sourceBuilder.from(from);
sourceBuilder.size(length);
}
}
}
返回值封装实体
public class ElasticSearchResponseEntity {
private Long total;// 查询总数
private List<String> list;// 数据集合
public ElasticSearchResponseEntity(Long total, List<String> list) {
this.total = total;
this.list = list;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
使用:
0.ES查询所需的QueryBuilder的封装,简单封装一下,如有需要可以自行增减
public class QueryBuilder {
/**
* 大于等于
*/
public static final String GTE = "gte";
/**
* 小于等于
*/
public static final String LTE = "lte";
/**
* 大于
*/
public static final String GT = "gt";
/**
* 小于
*/
public static final String LT = "lt";
/**
* 不会对搜索词进行分词处理,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到。
*
* @param key
* @param value
* @return
*/
public static TermQueryBuilder term(String key, Object value) {
return QueryBuilders.termQuery(key, value);
}
/**
* 一次匹配多个值,即 in()查询
*
* @param key key
* @param values 值集合
* @return TermsQueryBuilder
*/
public static TermsQueryBuilder terms(String key, Collection<?> values) {
return QueryBuilders.termsQuery(key, values);
}
/**
* 一次匹配多个值,即 in()查询,keyword全值匹配,精确查询
*
* @param key
* @param values
* @return
*/
public static TermsQueryBuilder termsKeyword(String key, Collection<?> values) {
return QueryBuilders.termsQuery(key + ".keyword", values);
}
/**
* 会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到。
*
* @param key
* @param value
* @return
*/
public static MatchQueryBuilder match(String key, Object value) {
return QueryBuilders.matchQuery(key, value);
}
/**
* 分词模糊查询
*
* @param key
* @param value
* @return
*/
public static FuzzyQueryBuilder fuzzy(String key, Object value) {
return QueryBuilders.fuzzyQuery(key, value);
}
/**
* 范围查询
*
* @param key
* @param value
* @return
*/
public static RangeQueryBuilder range(String key, Object value, String rangeType) {
RangeQueryBuilder builder = QueryBuilders.rangeQuery(key);
switch (rangeType) {
case GTE:
return builder.gte(value);
case LTE:
return builder.lte(value);
case GT:
return builder.gt(value);
case LT:
return builder.lt(value);
default:
return builder.gt(value);
}
}
1.定义数据实体类,继承BaseElasticSearchEntity,并重写父类方法,封装查询条件(省略getter/setter/toString/空参构造器等)
public class Student extends BaseElasticSearchEntity {
private Long id;
private String name;
private Date birth;
private String nikename;
// 父类构造器,用来操作ES指定的索引和Type
public Student(String esIndex, String esType, String esId) {
super(esIndex, esType, esId);
}
public Student(String esIndex, String esType) {
super(esIndex, esType);
}
// 重写父类方法,封装查询的条件
@Override
public void packageElasticSearchBody(BoolQueryBuilder boolQueryBuilder) {
// 相当于IN查询
if (StringUtils.isNotEmpty(this.name)) {
List<String> nameList = new ArrayList<>(Arrays.asList(this.name.split(",")));
boolQueryBuilder.must(QueryBuilder.termsKeyword("name", nameList));
}
// 日期范围查询,日期存入yyyy-MM-dd HH:mm:ss格式时配置不当可能会报错,建议存入时间戳
if (StringUtils.isNotEmpty(this.startTime)) {
boolQueryBuilder.must(QueryBuilder.range("birth", DateUtil.convertTimeToLong(this.startTime, DateUtil.YYYY_MM_DD_HH_MM_SS), QueryBuilder.GTE));
}
if (StringUtils.isNotEmpty(this.endTime)) {
boolQueryBuilder.must(QueryBuilder.range("birth", DateUtil.convertTimeToLong(this.endTime, DateUtil.YYYY_MM_DD_HH_MM_SS), QueryBuilder.LTE));
}
// match精准查询
if (StringUtils.isNotEmpty(this.nikename)) {
boolQueryBuilder.must(QueryBuilder.match("nikename", this.carId));
}
// 其他查询要求可以自行百度
}
}
2.service层的操作,忽略了业务逻辑,省略接口,直接上实现类了
@Slf4j
@Service("studentElasticSearchService")
public class StudentElasticSearchServiceImpl implements StudentElasticSearchService {
@Autowired
@Qualifier("elasticSearchMapperImpl")
private ElasticSearchMapper<Student> elasticSearchMapper;
private final String TYPE = "student_type";
private final String INDEX_NAME = "student_index";
@Override
public boolean isExists(String id) {
return elasticSearchMapper.isExists(new Student(INDEX_NAME, TYPE, id));
}
@Override
public String insert(Student entry) {
entry.setEsIndex(INDEX_NAME);
entry.setEsType(TYPE);
entry.setEsId(entry.getId());
return elasticSearchMapper.insert(entry);
}
@Override
public Student selectById(String id) {
String jsonString = elasticSearchMapper.selectById(new Student(INDEX_NAME, TYPE, id));
return StringUtils.isEmpty(jsonString) ? null : JSONObject.parseObject(jsonString, Student.class);
}
@Override
public String update(Student entry) {
entry.setEsIndex(INDEX_NAME);
entry.setEsType(TYPE);
entry.setEsId(entry.getId());
return elasticSearchMapper.update(entry);
}
@Override
public String delete(String id) {
return elasticSearchMapper.delete(new Student(INDEX_NAME, TYPE, id));
}
@Override
public ElasticSearchResponseEntity selectByMultiCondition(Student entry, Integer page, Integer length) {
try {
entry.setEsIndex(INDEX_NAME);
entry.setEsType(TYPE);
return elasticSearchMapper.selectByMultiConditionDesc(entry, "birth", page, length);
} catch (Exception e) {
log.error("出错!", e);
}
return null;
}
}
3.直接调用service层的接口,传入封装的实体或者唯一id,即可操作ES的增删改查,代码就不用贴了,到这一步,傻子都会用了
如有更好的建议和写法,请告知我,先感谢能让我提升代码质量的任何朋友。