(點選上方公眾號,可快速關註)
來源:琴水玉 ,
www.cnblogs.com/lovesqcc/p/8799187.html
概述
假設我們要從一個 ES 索引(相當於一張DB表)查詢資料,ES表有 order_no, order_type, state 等欄位, 而應用物件則有屬性 orderNo, orderType, state等。這樣,就會面臨“將應用物件的屬性與ES欄位對應起來”的問題。
固然可以透過註釋來說明,不過這樣顯得比較生硬。因為註釋並不起實際作用,程式碼裡還得寫一套對映關係,就會存在註釋與程式碼不一致的情況。 那麼,是否可以將這種對應關係的註釋用程式碼形式來解決呢? Java 註解可以解決這個問題。
實現
定義註解
首先定義註解類。註解類需要提供對應的ES欄位名 name、型別 type 以及是否必傳 required。
-
@Retention 指明註解在何時起作用,這裡是在執行時。
-
@Target 指明註解應用於何種物件,這裡應用於欄位。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface EsField {
/**
* 對應的ES欄位名
*/
String name();
/**
* 對應的ES欄位的值型別
* @return
*/
String type() default “”;
/**
* 是否必傳
*/
boolean required() default false;
}
應用領域物件
接著,將註解應用到應用領域物件。為簡潔,應用領域物件只有四個欄位。
@Data
public class CustomerDomain implements DomainSearch {
/** 店鋪ID */
@EsField(name=”shop_id”, required = true)
private Long shopId;
/** 訂單編號 */
@EsField(name=”order_no”)
private String orderNo;
/** 訂單狀態 */
@EsField(name=”state”, type=”list”)
private List
state;
/** 訂單型別 */
@EsField(name=”order_type”, type=”list”)
private List
orderType;
}
註解解析器
接著,需要提供註解解析器,將對應的對映關係轉成ES查詢物件的一部分。
-
使用介面的預設方法來實現,是為了支援不同的業務類自動可以轉化為ES查詢串;
-
註解解析器需要使用Java反射機制,來獲取相應的欄位,以及欄位上的註解定義,然後根據欄位的型別、值、註解定義來做相應處理;
-
使用反射來處理欄位時,由於欄位一般是私有的,因此必須先設定為可訪問的,處理完成後還原為不可訪問;
-
EsField field = f.getAnnotation(EsField.class) 用來獲取欄位上的註解資訊(name, type, required);Object value = f.get(customerDomain) 用來獲取欄位的值;欄位的其他型別資訊可以透過 Field 的方法拿到。
public interface DomainSearch {
Log logger = LogFactory.getLog(DomainSearch.class);
default String toEsQuery() {
Object customerDomain = this;
EsQuery esQuery = new EsQuery();
Field[] fields = this.getClass().getDeclaredFields();
for (Field f: fields) {
try {
if (Modifier.isStatic(f.getModifiers())) {
continue;
}
f.setAccessible(true);
Object value = f.get(customerDomain);
if (f.getAnnotation(EsField.class) != null) {
EsField field = f.getAnnotation(EsField.class);
if (field.required() && value == null) {
throw new RuntimeException(“field ‘” + field + “‘ is required. value is null”);
}
if (isNeedOmitted(value)) {
f.setAccessible(false);
continue;
}
if ((value instanceof List) && ((List)value).size() == 1) {
// 針對 List 中單個值做最佳化查詢
esQuery = esQuery.addTermFilter(field.name(), ((List)value).get(0));
}
else {
esQuery = esQuery.addTermFilter(field.name(), value);
}
}
f.setAccessible(false);
} catch (Exception ex) {
logger.error(“failed to build es query for field: ” + f.getName(), ex);
throw new RuntimeException(ex.getCause());
}
}
return esQuery.toJsonString();
}
/**
* 判斷是否需要忽略該欄位的查詢
* @param value 欄位值
* @return 是否要忽略
*/
default boolean isNeedOmitted(Object value) {
if (value == null) {
return true;
}
// 空字串搜尋值忽略
if ((value instanceof String) && StringUtils.isBlank(value.toString())) {
return true;
}
// 空串列串忽略
if ((value instanceof List) && ((List)value).isEmpty()) {
return true;
}
return false;
}
}
查詢物件
ES查詢物件將所有生成的查詢條件轉化為ES可以接受的查詢字串。
public class EsQuery {
private static int DEFAULT_SIZE = 100;
private final Map
termFilter; private final Map
rangeFilter; private final Map
matchFilter; private int size;
private String orderBy = null;
private String order = null;
// query 查詢語法, 是否需要 filtered, filter 這兩層
// 5.x 版本不再需要這兩層
private boolean isNeedFilterLayer = true;
private Integer from;
private final Map
mustNotTermFilter;
private final Map
shouldTermFilter; private Integer shouldMatchMinimum;
private List
includes; private List
excludes;
public EsQuery() {
this.termFilter = new HashMap<>();
this.rangeFilter = new HashMap();
this.matchFilter = new HashMap();
this.mustNotTermFilter = new HashMap<>();
this.shouldTermFilter = new HashedMap();
this.size = DEFAULT_SIZE;
this.includes = new ArrayList<>();
this.excludes = new ArrayList<>();
}
public EsQuery addTermFilter(String key, Object value) {
this.termFilter.put(key, value);
return this;
}
public EsQuery addMustNotTermFilter(String key, Object value) {
this.mustNotTermFilter.put(key, value);
return this;
}
public EsQuery addAllMustNotTermFilter(Map
mustNot) { if (mustNot != null && !mustNot.isEmpty()) {
this.mustNotTermFilter.putAll(mustNot);
}
return this;
}
public EsQuery addShouldTermFilter(String key, Object value) {
this.shouldTermFilter.put(key, value);
return this;
}
public EsQuery addAllShouldTermFilter(Map
should) { if (should != null && !should.isEmpty()) {
this.shouldTermFilter.putAll(should);
}
return this;
}
public EsQuery addRangeFilter(String key, long gte, long lte){
this.rangeFilter.put(key, new Range(gte, lte));
return this;
}
public EsQuery addMatchFilter(String key, Match value) {
this.matchFilter.put(key, value);
return this;
}
public EsQuery addIncludeFields(List
includes) { this.includes.addAll(includes);
return this;
}
public EsQuery addExcludeFields(List
excludes) { this.excludes.addAll(excludes);
return this;
}
@Override
public String toString() {
return toJsonString();
}
public String toJsonString() {
Map
finalQuery = new HashMap<>(); Map
queryMap = new HashMap<>(); Map
filteredMap = new HashMap<>(); Map
filterMap = new HashMap<>(); Map
boolMap = new HashMap<>();
List
List
List
if(!this.rangeFilter.isEmpty()){
for(Map.Entry
e: this.rangeFilter.entrySet()){ Map
rangeMap = new HashMap<>(); Map
rangeEntityMap = new HashMap<>(); rangeEntityMap.put(e.getKey(), e.getValue().toMap());
rangeMap.put(Constant.range, rangeEntityMap);
mustList.add(rangeMap);
}
}
if(!this.matchFilter.isEmpty()){
this.matchFilter.forEach(
(key, match) -> {
Map
matchEntityMap = new HashMap<>(); Map
matchMap = new HashMap<>(); Map
subMatchMap = new HashMap<>(); matchEntityMap.put(Constant.query, match.getQuery());
matchEntityMap.put(Constant.should_minum, match.getMinimumShouldMatch());
matchMap.put(key, matchEntityMap);
subMatchMap.put(Constant.match, matchMap);
mustList.add(subMatchMap);
});
}
boolMap.put(Constant.must, mustList);
if (!mustNotList.isEmpty())
boolMap.put(Constant.mustNot, mustNotList);
if (!shouldList.isEmpty()) {
// 有 minimum_should_match 不帶過濾器
boolMap.put(Constant.should, shouldList);
boolMap.put(Constant.should_minum, shouldMatchMinimum);
queryMap.put(Constant.bool, boolMap);
}
else {
if (isNeedFilterLayer) {
filterMap.put(Constant.bool, boolMap);
filteredMap.put(Constant.filter, filterMap);
queryMap.put(Constant.filtered, filteredMap);
}
else {
queryMap.put(Constant.bool, boolMap);
}
}
finalQuery.put(Constant.query, queryMap);
Map
orderMap = new HashMap<>(); Map
orderItem = new HashMap<>();
if(order != null && orderBy != null){
orderItem.put(Constant.order, this.order);
orderMap.put(this.orderBy, orderItem);
finalQuery.put(Constant.sort, orderMap);
}
Map
source = new HashMap<>(); if (!includes.isEmpty()) {
source.put(Constant.includes, this.includes);
}
if (!excludes.isEmpty()) {
source.put(Constant.excludes, this.excludes);
}
if (!source.isEmpty()) {
finalQuery.put(Constant.source, source);
}
finalQuery.put(Constant.size, this.size);
if (from != null) {
finalQuery.put(Constant.from, from.intValue());
}
return JSON.toJSONString(finalQuery);
}
public List
List
for (Map.Entry
e: termFilter.entrySet()){ Map
termMap = new HashMap<>(); Map
itemMap = new HashMap<>(); itemMap.put(e.getKey(), e.getValue());
if(e.getValue() instanceof List){
termMap.put(Constant.terms, itemMap);
}else{
termMap.put(Constant.term, itemMap);
}
termFilterList.add(termMap);
}
return termFilterList;
}
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public Integer getFrom() {
return from;
}
public void setFrom(Integer from) {
this.from = from;
}
public Map
getTermFilter() { return Collections.unmodifiableMap(termFilter);
}
public Map
getRangeFilter() { return Collections.unmodifiableMap(rangeFilter);
}
public Map
getMustNotTermFilter() { return Collections.unmodifiableMap(mustNotTermFilter);
}
public Map
getShouldTermFilter() { return Collections.unmodifiableMap(shouldTermFilter);
}
public Map
getMatchFilter() { return matchFilter;
}
public void setShouldMatchMinimum(Integer shouldMatchMinimum) {
this.shouldMatchMinimum = shouldMatchMinimum;
}
public Integer getShouldMatchMinimum() {
return shouldMatchMinimum;
}
public Map
getRangeMap(String key) { return Collections.unmodifiableMap(rangeFilter.get(key).toMap());
}
public List
getIncludes() { return Collections.unmodifiableList(includes);
}
public boolean isNeedFilterLayer() {
return isNeedFilterLayer;
}
public void setNeedFilterLayer(boolean needFilterLayer) {
isNeedFilterLayer = needFilterLayer;
}
@Override
public boolean equals(Object o) {
// for you to write
}
@Override
public int hashCode() {
// for you to write
}
小結
透過ES搜尋示例,展示瞭如何運用註解自動化處理領域物件屬性與底層ES儲存欄位之間的對應關係。實際上,如果想為應用物件或元件新增某種說明或註釋,不妨先想想是否可以透過註解自動化處理。註解亦可用於框架自動處理物件與元件的整合。Spring框架的Resource, Component, AOP,以及 Plugin 化設計思想等都是好的應用例子。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能