原文出处:琴水玉
概述
假设我们要从一个 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<Integer> state;
/** 订单类型 */
@EsField(name="order_type", type="list")
private List<Integer> 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<String, Object> termFilter;
private final Map<String, Range> rangeFilter;
private final Map<String, Match> 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<String, Object> mustNotTermFilter;
private final Map<String,Object> shouldTermFilter;
private Integer shouldMatchMinimum;
private List<String> includes;
private List<String> 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<String,Object> 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<String,Object> 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<String> includes) {
this.includes.addAll(includes);
return this;
}
public EsQuery addExcludeFields(List<String> excludes) {
this.excludes.addAll(excludes);
return this;
}
@Override
public String toString() {
return toJsonString();
}
public String toJsonString() {
Map<String, Object> finalQuery = new HashMap<>();
Map<String, Object> queryMap = new HashMap<>();
Map<String, Object> filteredMap = new HashMap<>();
Map<String, Object> filterMap = new HashMap<>();
Map<String, Object> boolMap = new HashMap<>();
List<Object> mustList = obtainTermFilterList(this.termFilter);
List<Object> mustNotList = obtainTermFilterList(this.mustNotTermFilter);
List<Object> shouldList = obtainTermFilterList(this.shouldTermFilter);
if(!this.rangeFilter.isEmpty()){
for(Map.Entry<String, Range> e: this.rangeFilter.entrySet()){
Map<String, Object> rangeMap = new HashMap<>();
Map<String, Object> 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<String, String> matchEntityMap = new HashMap<>();
Map<String, Map> matchMap = new HashMap<>();
Map<String, 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<String, Object> orderMap = new HashMap<>();
Map<String, Object> 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<String,Object> 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<Object> obtainTermFilterList(Map<String, Object> termFilter) {
List<Object> termFilterList = new ArrayList<>();
for (Map.Entry<String, Object> e: termFilter.entrySet()){
Map<String, Object> termMap = new HashMap<>();
Map<String, Object> 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<String, Object> getTermFilter() {
return Collections.unmodifiableMap(termFilter);
}
public Map<String, Range> getRangeFilter() {
return Collections.unmodifiableMap(rangeFilter);
}
public Map<String, Object> getMustNotTermFilter() {
return Collections.unmodifiableMap(mustNotTermFilter);
}
public Map<String, Object> getShouldTermFilter() {
return Collections.unmodifiableMap(shouldTermFilter);
}
public Map<String, Match> getMatchFilter() {
return matchFilter;
}
public void setShouldMatchMinimum(Integer shouldMatchMinimum) {
this.shouldMatchMinimum = shouldMatchMinimum;
}
public Integer getShouldMatchMinimum() {
return shouldMatchMinimum;
}
public Map<String, Object> getRangeMap(String key) {
return Collections.unmodifiableMap(rangeFilter.get(key).toMap());
}
public List<String> 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
}
public class EsQuery {
private static int DEFAULT_SIZE = 100;
private final Map<String, Object> termFilter;
private final Map<String, Range> rangeFilter;
private final Map<String, Match> 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<String, Object> mustNotTermFilter;
private final Map<String,Object> shouldTermFilter;
private Integer shouldMatchMinimum;
private List<String> includes;
private List<String> 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<String,Object> 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<String,Object> 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<String> includes) {
this.includes.addAll(includes);
return this;
}
public EsQuery addExcludeFields(List<String> excludes) {
this.excludes.addAll(excludes);
return this;
}
@Override
public String toString() {
return toJsonString();
}
public String toJsonString() {
Map<String, Object> finalQuery = new HashMap<>();
Map<String, Object> queryMap = new HashMap<>();
Map<String, Object> filteredMap = new HashMap<>();
Map<String, Object> filterMap = new HashMapnull