一、Java 通用连接池准备
@Configuration
@Order(-1)
public class EsConfiguration {
@Value("${spring.elasticsearch.rest.username}")
private String username;
@Value("${spring.elasticsearch.rest.password}")
private String password;
@Value("${spring.elasticsearch.rest.uris}")
private String url;
@Bean(name = "esHighLevelClient")
@Order(-1)
public RestHighLevelClient client(){
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials(username,password));
RestClientBuilder restClientBuilder = getRestClientBuilder(url)
.setHttpClientConfigCallback(httpAsyncClientBuilder -> {
httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
.setKeepAliveStrategy(((response, context) ->
// 注意: 这块需要根据系统的所需的连接时长来自己调试,如果处理不当可能在查询时导致ES关闭造成异常
TimeUnit.MINUTES.toMicros(3)));
});
return new RestHighLevelClient(restClientBuilder);
}
public static RestClientBuilder getRestClientBuilder(String esUrl){
return RestClient.builder(createHttpHost(URI.create(esUrl)));
}
public static HttpHost createHttpHost(URI uri){
if(StrUtil.isEmpty(uri.getUserInfo())){
return HttpHost.create(uri.toString());
}
try {
return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
uri.getQuery(), uri.getFragment()).toString());
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
}
二、自定义查询条件注解@ConditionIn @ConditionLike @ConditionIs 解析
// 自定义注解
// 定义枚举类:查询方式
public enum Condition implements Serializable {
Gt,
Gte,
In,
Is,
Lt,
Lte,
Ne,
Like
}
// 1.base 注解
/**
* @Author
* @Date 2021/6/4 17:19
* @Version 1.0
*/
@Target({ElementType.TYPE})
@Retention(RUNTIME)
public @interface BaseCondition {
Class makeCondition();
}
// 2. 注解创建
/**
* 模糊查询,应用于AbsMongoQuery的子类
* @Author
* @Date 2021/4/13 16:51
* @Version 1.0
*/
@Target({ElementType.FIELD})
@Retention(RUNTIME)
@BaseCondition(makeCondition = ConditionLikeImpl.class)
public @interface ConditionLike {
String key() default "";
}
// 3. 实现base
public interface ICondition {
/**
* 获取key的值
* @param an
* @return
*/
String getKey(Annotation an, Field field);
/**
* 获取 条件
* @param an
* @return
*/
Condition getCondtion(Annotation an);
/**
* 获取条件对应的值
* @param an
* @param field
* @param obj
* @return
*/
default Object getCondtionValue(Annotation an, Field field, Object obj) {
// 获取值
field.setAccessible(true);
Object value = null;
try {
value = field.get(obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (null == value) {
return null;
}
if (value instanceof String) {
if (StringUtils.isEmpty(value)) {
return null;
}
} else if (value instanceof List){
if (CollectionUtils.isEmpty((List)value)) {
return null;
}
} else if (value.getClass().isArray()) {
Object[] array = (Object[]) value;
if (array.length == 0) {
return null;
}
}
return value;
}
}
// 4. key value 获取
public class ConditionLikeImpl implements ICondition {
@Override
public String getKey(Annotation an, Field field) {
if (an instanceof ConditionLike) {
return ((ConditionLike) an).key();
}
return null;
}
@Override
public Condition getCondtion(Annotation an) {
return Condition.Like;
}
}
@Data
public class MongoCondition implements Serializable {
public MongoCondition(String key, MongoFieldCondition fieldCondition) {
this.key = key;
this.fieldConditionList = new ArrayList<MongoFieldCondition>();
this.fieldConditionList.add(fieldCondition);
}
/**
* key
*/
private String key;
/**
* 多条件
*/
private List<MongoFieldCondition> fieldConditionList;
}
/**
* 生成搜索条件: 排序,分页,条件查询
* @param queryBo
* @return
*/
private MongoQueryDto convertQueryDto(AbsMongoQuery queryBo) {
MongoQueryDto mongoQueryDto = new MongoQueryDto();
if (null != queryBo) {
// 排序
mongoQueryDto.setOrderByList(queryBo.getOrderByList());
// 分页
mongoQueryDto.setPageNumber(queryBo.getPageNumber());
mongoQueryDto.setPageSize(queryBo.getPageSize());
// 条件
mongoQueryDto.setCondtionList(convertQueryCondition(queryBo));
}
return mongoQueryDto;
}
/**
* 生成条件查询集合
* @param queryBo
* @return
*/
private List<MongoCondition> convertQueryCondition(AbsMongoQuery queryBo) {
List<MongoCondition> list = new ArrayList<>();
Arrays.stream(queryBo.getClass().getDeclaredFields()).forEach(
field -> {
// 获取注解
Annotation[] annnos = field.getDeclaredAnnotations();
// 获取条件key
String key = queryConditionKey(annnos, field);
// 配置条件以及对应的条件值
MongoFieldCondition mongoCondition = makeCondition(annnos, field, queryBo);
// 配置可以以及条件
configConition(list, key, mongoCondition);
}
);
return list;
}
/**
* 获取查询的key
* @param annnos
* @param field
* @return
*/
private String queryConditionKey(Annotation[] annnos, Field field) {
for (Annotation an : annnos) {
// 反射取出 属性名称
ICondition condition = getICondition(an, field);
if (null != condition) {
return condition.getKey(an, field);
}
}
return null;
}
/**
* 生成单条件
* @param annnos
* @param field
* @param queryBo
* @return
*/
private MongoFieldCondition makeCondition(Annotation[] annnos, Field field, AbsMongoQuery queryBo) {
MongoFieldCondition mongoFieldCondition = new MongoFieldCondition();
for (Annotation an : annnos) {
// 反射取出 属性名称
ICondition condition = getICondition(an, field);
if (null != condition) {
mongoFieldCondition.setCondition(condition.getCondtion(an));
mongoFieldCondition.setValue(condition.getCondtionValue(an, field, queryBo));
return mongoFieldCondition;
}
}
return null;
}
/**
* 配置条件
* @param list
* @param key
* @param mongoFieldCondition
*/
private void configConition(List<MongoCondition> list, String key, MongoFieldCondition mongoFieldCondition) {
// 判断条件
if (StringUtils.isEmpty(key)
|| null == mongoFieldCondition
|| Objects.isNull(mongoFieldCondition.getCondition())
|| Objects.isNull(mongoFieldCondition.getValue())) {
return;
}
// 存在对应key则添加字段条件
for (MongoCondition cd : list) {
if (key.equals(cd.getKey())) {
cd.getFieldConditionList().add(mongoFieldCondition);
return;
}
}
// 不存在则添加条件
MongoCondition mongoCondition = new MongoCondition(key, mongoFieldCondition);
list.add(mongoCondition);
}
protected ICondition getICondition(Annotation an, Field field) {
String annoName = an.annotationType().getName();
if (annoName.startsWith("com.wti.mongodb.dto.query.annotation")) {
AnnotationAttributes annotationAttributes
= AnnotatedElementUtils.getMergedAnnotationAttributes(field, BaseCondition.class);
if (null != annotationAttributes
&& annotationAttributes.containsKey("makeCondition")) {
Object makeConditionValue = annotationAttributes.get("makeCondition");
if (makeConditionValue instanceof Class) {
Class clazz = (Class)makeConditionValue;
try {
Object makeContionInstance = clazz.newInstance();
if (makeContionInstance instanceof ICondition) {
return ((ICondition)makeContionInstance);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}
return null;
}
三、ES 操作
3.1 查询全部
@Resource
@Qualifier("esHighLevelClient")
private RestHighLevelClient client;
/**
* es 默认未节点
*/
private static final String INVENTORY = "_inventory";
/**
* 使用.keyword 查询
*/
private static final String KEYWORD = ".keyword";
public void testSearchAll() throws IOException, ParseException {
// 搜索请求对象
SearchRequest searchRequest = new SearchRequest("address");
// 指定类型
searchRequest.types("_doc");
// 搜索源构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
// 设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段
searchSourceBuilder.fetchSource(new String[]{"name","address","location","timestamp"},new String[]{});
// 向搜索请求对象中设置搜索源
searchRequest.source(searchSourceBuilder);
// 执行搜索,向ES发起http请求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索结果
SearchHits hits = searchResponse.getHits();
// 匹配到的总记录数
long totalHits = hits.getTotalHits();
// 得到匹配度高的文档
SearchHit[] searchHits = hits.getHits();
// 日期格式化对象
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(SearchHit hit:searchHits){
// 文档的主键
String id = hit.getId();
// 源文档内容
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
// 日期
Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
System.out.println(name);
// 方式二:
String sourceAsString = hit.getSourceAsString();
T t = JSON.parseObject(sourceAsString,tClass );
}
}
}
3.2 精准匹配
TermQuery为精确查询,在搜索时会整体匹配关键字,不再将关键字分词
// dsl
{
"query": {
"term": { // 查询的方式为 term 精确查询
"esName": "spring框架" // 查询的字段为 name 关键字是 spring
}
}
}
// java API
// 搜索方式
// termQuery 精确查询
searchSourceBuilder.query(QueryBuilders.termQuery("esName", "spring框架"));
queryBuilder.termsQuery("key", obj1, obj2..) //一次匹配多个值
3.3 全文检索 MatchQuery
MatchQuery 即全文检索,会对关键字进行分词后匹配词条。
query:搜索的关键字,对于英文关键字如果有多个单词则中间要用半角逗号分隔,而对于中文关键字中间可以用
逗号分隔也可以不用
operator:设置查询的结果取交集还是并集,并集用 or, 交集用 and
//DSL
{
"query": {
"match": {
"description": {
"query": "spring框架",
"operator": "or"
}
}
}
}
// JAVA API
// matchQuery全文检索
searchSourceBuilder.query(QueryBuilders.matchQuery("esName", "Spring框架"));
QueryBuilders.multiMatchQuery(value, key1, key2, key3);
3.4 多字段联合搜索 MultiQuery
MultiQuery可以通过 fields 属性来设置多个域联合查找。
// DLS
/**{
"query": {
"multi_match": {
"query": "spring框架",
"minimum_should_match": "80%",
"fields": ["esName", "description"]
}
}
}*/
// JAVA API
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("spring框架", "esName", "address").minimumShouldMatch("80%"));
//注:“spring开发框架”会被分为三个词:spring、开发、框架
//设置"minimum_should_match": "80%"表示,三个词在文档的匹配占比为80%,即3*0.8=2.4,向下取整得2,表示至少有两个词在文档中要匹///配成功。
//提升boost:
//在多域联合查询的时候,可以通过 boost //来设置某个域在计算得分时候的比重,比重越高的域当他符合条件时计算的得分越高,相应的该记录也更靠前。通过在 fields //中给相应的字段用 ^权重倍数来实现。
// DSL
"fields": ["name^10", "address"]
// JAVA API
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring框架", "esName", "address").field("esName", 10)); // 设置 name 10倍权重
3.5 布尔查询 BoolQuery
布尔查询对应于Lucene的BooleanQuery查询,实现将多个查询组合起来,有三个可选的参数:
must: 文档必须匹配must所包括的查询条件,相当于 “AND”
should: 文档应该匹配should所包括的查询条件其中的一个或多个,相当于 “OR”
must_not: 文档不能匹配must_not所包括的该查询条件,相当于“NOT”
// DSL
/**{
"query": {
"bool": { // 布尔查询
"must": [ // 查询条件 must 表示数组中的查询方式所规定的条件都必须满足
{
"multi_match": {
"query": "spring框架",
"minimum_should_match": "50%",
"fields": [
"esName^10",
"description"
]
}
},
{
"term": {
"type": "餐饮"
}
}
]
}
}
}*/
// JAVA API
// 首先构造多关键字查询条件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "esName", "address").field("esName", 10);
// 然后构造精确匹配查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("type", "201002");
// 组合两个条件,组合方式为 must 全满足
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
boolQueryBuilder.must(termQueryBuilder);
// 将查询条件封装给查询对象
searchSourceBuilder.query(boolQueryBuilder);
3.6 模糊匹配WildcardQuery
对于es关键字或单词的查询我们可以借助QueryBuilders.wildcardQuery方法来操作,只需要指定es中对应的列和要查询的内容即可:
boolQueryBuilder.must(QueryBuilders.wildcardQuery("name", "spring"+"*"));
3.7 范围匹配RangeQuery
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
3.8 分页查询
// 设置查询的起始位置,默认是0
sourceBuilder.from(0);
// 设置查询结果的页大小,默认是10
sourceBuilder.size(10);
3.9 过滤
定义过滤器查询,是在原本查询结果的基础上对数据进行筛选,因此省略了重新计算的分的步骤,效率更高。并且方便缓存。推荐尽量使用过虑器去实现查询或者过虑器和查询共同使用,过滤器在布尔查询中使用。
boolQueryBuilder.filter(QueryBuilders.termQuery("type", "餐饮"));
3.10 排序
SearchSourceBuilder允许增加一或多个排序参数SortBuilder,有四个具体实现FieldSortBuilder, ScoreSortBuilder, GeoDistanceSortBuilder 和 ScriptSortBuilder。
注:支持对 keyword、date、float 等类型添加排序,text类型的字段不允许排序
// 默认排序。根据_score倒序
sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
// 根据_id升序
sourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));
searchBuilder.sort("type", SortOrder.DESC);
3.11 高亮
// 高亮查询
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<em>"); // 高亮前缀
highlightBuilder.postTags("</em>"); // 高亮后缀
highlightBuilder.fields().add(new HighlightBuilder.Field("esName")); // 高亮字段
// 添加高亮查询条件到搜索源
searchSourceBuilder.highlighter(highlightBuilder);
3.12 扩展 boolQury 嵌套查询
BoolQueryBuilder bu= QueryBuilders.boolQuery();
BoolQueryBuilder builder = QueryBuilders.boolQuery();
for (Map<String, String> sitfTrack : sitfTracks) {
builder.should(QueryBuilders.matchQuery("sourceCode",sitfTrack.get("interfaceCode")));
}
//注意这里
bu.must(builder);
bu.must(QueryBuilders.termQuery("batchNum.keyword", traceLog.getBatchNum()));