ElasticSearch 도구 클래스 패키지(ElasticsearchTemplate 기반)

65335 단어
1. 추상적 인터페이스 정의
  1 public abstract class SearchQueryEngine {
  2 
  3     @Autowired
  4     protected ElasticsearchTemplate elasticsearchTemplate;
  5 
  6     public abstract int saveOrUpdate(List list);
  7 
  8     public abstract  List aggregation(T query, Class clazz);
  9 
 10     public abstract  Page scroll(T query, Class clazz, Pageable pageable, ScrollId scrollId);
 11 
 12     public abstract  List find(T query, Class clazz, int size);
 13 
 14     public abstract  Page find(T query, Class clazz, Pageable pageable);
 15 
 16     public abstract  R sum(T query, Class clazz);
 17 
 18     protected Document getDocument(T t) {
 19         Document annotation = t.getClass().getAnnotation(Document.class);
 20         if (annotation == null) {
 21             throw new SearchQueryBuildException("Can't find annotation @Document on " + t.getClass().getName());
 22         }
 23         return annotation;
 24     }
 25 
 26     /**
 27      *  , column 
 28      *
 29      * @param field
 30      * @param column
 31      * @return
 32      */
 33     protected String getFieldName(Field field, String column) {
 34         return StringUtils.isNotBlank(column) ? column : field.getName();
 35     }
 36 
 37     /**
 38      *  
 39      *
 40      * @param field
 41      * @param obj
 42      * @param value
 43      */
 44     protected void setFieldValue(Field field, Object obj, Object value) {
 45         boolean isAccessible = field.isAccessible();
 46         field.setAccessible(true);
 47         try {
 48             switch (field.getType().getSimpleName()) {
 49                 case "BigDecimal":
 50                     field.set(obj, new BigDecimal(value.toString()).setScale(5, BigDecimal.ROUND_HALF_UP));
 51                     break;
 52                 case "Long":
 53                     field.set(obj, new Long(value.toString()));
 54                     break;
 55                 case "Integer":
 56                     field.set(obj, new Integer(value.toString()));
 57                     break;
 58                 case "Date":
 59                     field.set(obj, new Date(Long.valueOf(value.toString())));
 60                     break;
 61                 default:
 62                     field.set(obj, value);
 63             }
 64         } catch (IllegalAccessException e) {
 65             throw new SearchQueryBuildException(e);
 66         } finally {
 67             field.setAccessible(isAccessible);
 68         }
 69     }
 70 
 71     /**
 72      *  
 73      *
 74      * @param field
 75      * @param obj
 76      * @return
 77      */
 78     protected Object getFieldValue(Field field, Object obj) {
 79         boolean isAccessible = field.isAccessible();
 80         field.setAccessible(true);
 81         try {
 82             return field.get(obj);
 83         } catch (IllegalAccessException e) {
 84             throw new SearchQueryBuildException(e);
 85         } finally {
 86             field.setAccessible(isAccessible);
 87         }
 88     }
 89 
 90     /**
 91      *  es value 
 92      *
 93      * @param value
 94      * @return
 95      */
 96     protected Object formatValue(Object value) {
 97         if (value instanceof Date) {
 98             return ((Date) value).getTime();
 99         } else {
100             return value;
101         }
102     }
103 
104     /**
105      *  
106      *
107      * @param t
108      * @return
109      */
110     protected int getNumberOfShards(T t) {
111         return Integer.parseInt(elasticsearchTemplate.getSetting(getDocument(t).index()).get(IndexMetaData.SETTING_NUMBER_OF_SHARDS).toString());
112     }
113 }

 
2. 인터페이스 구현
  1 @Component
  2 @ComponentScan
  3 public class SimpleSearchQueryEngine extends SearchQueryEngine {
  4 
  5     private int numberOfRowsPerScan = 10;
  6 
  7     @Override
  8     public int saveOrUpdate(List list) {
  9         if (CollectionUtils.isEmpty(list)) {
 10             return 0;
 11         }
 12 
 13         T base = list.get(0);
 14         Field id = null;
 15         for (Field field : base.getClass().getDeclaredFields()) {
 16             BusinessID businessID = field.getAnnotation(BusinessID.class);
 17             if (businessID != null) {
 18                 id = field;
 19                 break;
 20             }
 21         }
 22         if (id == null) {
 23             throw new SearchQueryBuildException("Can't find @BusinessID on " + base.getClass().getName());
 24         }
 25 
 26         Document document = getDocument(base);
 27         List bulkIndex = new ArrayList<>();
 28         for (T t : list) {
 29             UpdateQuery updateQuery = new UpdateQuery();
 30             updateQuery.setIndexName(document.index());
 31             updateQuery.setType(document.type());
 32             updateQuery.setId(getFieldValue(id, t).toString());
 33             updateQuery.setUpdateRequest(new UpdateRequest(updateQuery.getIndexName(), updateQuery.getType(), updateQuery.getId()).doc(JSONObject.toJSONString(t, SerializerFeature.WriteMapNullValue)));
 34             updateQuery.setDoUpsert(true);
 35             updateQuery.setClazz(t.getClass());
 36             bulkIndex.add(updateQuery);
 37         }
 38         elasticsearchTemplate.bulkUpdate(bulkIndex);
 39         return list.size();
 40     }
 41 
 42     @Override
 43     public  List aggregation(T query, Class clazz) {
 44         NativeSearchQueryBuilder nativeSearchQueryBuilder = buildNativeSearchQueryBuilder(query);
 45         nativeSearchQueryBuilder.addAggregation(buildGroupBy(query));
 46         Aggregations aggregations = elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), new AggregationResultsExtractor());
 47         try {
 48             return transformList(null, aggregations, clazz.newInstance(), new ArrayList());
 49         } catch (Exception e) {
 50             throw new SearchResultBuildException(e);
 51         }
 52     }
 53 
 54     /**
 55      *  Aggregations List
 56      *
 57      * @param terms
 58      * @param aggregations
 59      * @param baseObj
 60      * @param resultList
 61      * @param 
 62      * @return
 63      * @throws NoSuchFieldException
 64      * @throws IllegalAccessException
 65      * @throws InstantiationException
 66      */
 67     private  List transformList(Aggregation terms, Aggregations aggregations, R baseObj, List resultList) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
 68         for (String column : aggregations.asMap().keySet()) {
 69             Aggregation childAggregation = aggregations.get(column);
 70             if (childAggregation instanceof InternalSum) {
 71                 //  @Sum
 72                 if (!(terms instanceof InternalSum)) {
 73                     R targetObj = (R) baseObj.getClass().newInstance();
 74                     BeanUtils.copyProperties(baseObj, targetObj);
 75                     resultList.add(targetObj);
 76                 }
 77                 setFieldValue(baseObj.getClass().getDeclaredField(column), resultList.get(resultList.size() - 1), ((InternalSum) childAggregation).getValue());
 78                 terms = childAggregation;
 79             } else {
 80                 Terms childTerms = (Terms) childAggregation;
 81                 for (Terms.Bucket bucket : childTerms.getBuckets()) {
 82                     if (CollectionUtils.isEmpty(bucket.getAggregations().asList())) {
 83                         //  @Sum
 84                         R targetObj = (R) baseObj.getClass().newInstance();
 85                         BeanUtils.copyProperties(baseObj, targetObj);
 86                         setFieldValue(targetObj.getClass().getDeclaredField(column), targetObj, bucket.getKey());
 87                         resultList.add(targetObj);
 88                     } else {
 89                         setFieldValue(baseObj.getClass().getDeclaredField(column), baseObj, bucket.getKey());
 90                         transformList(childTerms, bucket.getAggregations(), baseObj, resultList);
 91                     }
 92                 }
 93             }
 94         }
 95         return resultList;
 96     }
 97 
 98     @Override
 99     public  Page scroll(T query, Class clazz, Pageable pageable, ScrollId scrollId) {
100         if (pageable.getPageSize() % numberOfRowsPerScan > 0) {
101             throw new SearchQueryBuildException("Page size must be an integral multiple of " + numberOfRowsPerScan);
102         }
103         SearchQuery searchQuery = buildNativeSearchQueryBuilder(query).withPageable(new PageRequest(pageable.getPageNumber(), numberOfRowsPerScan / getNumberOfShards(query), pageable.getSort())).build();
104         if (StringUtils.isEmpty(scrollId.getValue())) {
105             scrollId.setValue(elasticsearchTemplate.scan(searchQuery, 10000l, false));
106         }
107         Page page = elasticsearchTemplate.scroll(scrollId.getValue(), 10000l, clazz);
108         if (page == null || page.getContent().size() == 0) {
109             elasticsearchTemplate.clearScroll(scrollId.getValue());
110         }
111         return page;
112     }
113 
114     @Override
115     public  List find(T query, Class clazz, int size) {
116         // Caused by: QueryPhaseExecutionException[Result window is too large, from + size must be less than or equal to: [10000] but was [2147483647].
117         // See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level parameter.]
118         if (size % numberOfRowsPerScan > 0) {
119             throw new SearchQueryBuildException("Parameter 'size' must be an integral multiple of " + numberOfRowsPerScan);
120         }
121         int pageNum = 0;
122         List result = new ArrayList<>();
123         ScrollId scrollId = new ScrollId();
124         while (true) {
125             Page page = scroll(query, clazz, new PageRequest(pageNum, numberOfRowsPerScan), scrollId);
126             if (page != null && page.getContent().size() > 0) {
127                 result.addAll(page.getContent());
128             } else {
129                 break;
130             }
131             if (result.size() >= size) {
132                 break;
133             } else {
134                 pageNum++;
135             }
136         }
137         elasticsearchTemplate.clearScroll(scrollId.getValue());
138         return result;
139     }
140 
141     @Override
142     public  Page find(T query, Class clazz, Pageable pageable) {
143         NativeSearchQueryBuilder nativeSearchQueryBuilder = buildNativeSearchQueryBuilder(query).withPageable(pageable);
144         return elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), clazz);
145     }
146 
147     @Override
148     public  R sum(T query, Class clazz) {
149         NativeSearchQueryBuilder nativeSearchQueryBuilder = buildNativeSearchQueryBuilder(query);
150         for (SumBuilder sumBuilder : getSumBuilderList(query)) {
151             nativeSearchQueryBuilder.addAggregation(sumBuilder);
152         }
153         Aggregations aggregations = elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), new AggregationResultsExtractor());
154         try {
155             return transformSumResult(aggregations, clazz);
156         } catch (Exception e) {
157             throw new SearchResultBuildException(e);
158         }
159     }
160 
161     private  R transformSumResult(Aggregations aggregations, Class clazz) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
162         R targetObj = clazz.newInstance();
163         for (Aggregation sum : aggregations.asList()) {
164             if (sum instanceof InternalSum) {
165                 setFieldValue(targetObj.getClass().getDeclaredField(sum.getName()), targetObj, ((InternalSum) sum).getValue());
166             }
167         }
168         return targetObj;
169     }
170 
171     private NativeSearchQueryBuilder buildNativeSearchQueryBuilder(T query) {
172         Document document = getDocument(query);
173         NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
174                 .withIndices(document.index())
175                 .withTypes(document.type());
176 
177         QueryBuilder whereBuilder = buildBoolQuery(query);
178         if (whereBuilder != null) {
179             nativeSearchQueryBuilder.withQuery(whereBuilder);
180         }
181 
182         return nativeSearchQueryBuilder;
183     }
184 
185     /**
186      *  
187      *
188      * @param query
189      * @return
190      */
191     private BoolQueryBuilder buildBoolQuery(T query) {
192         BoolQueryBuilder boolQueryBuilder = boolQuery();
193         buildMatchQuery(boolQueryBuilder, query);
194         buildRangeQuery(boolQueryBuilder, query);
195         BoolQueryBuilder queryBuilder = boolQuery().must(boolQueryBuilder);
196         return queryBuilder;
197     }
198 
199     /**
200      * and or  
201      *
202      * @param boolQueryBuilder
203      * @param query
204      */
205     private void buildMatchQuery(BoolQueryBuilder boolQueryBuilder, T query) {
206         Class clazz = query.getClass();
207         for (Field field : clazz.getDeclaredFields()) {
208             MatchQuery annotation = field.getAnnotation(MatchQuery.class);
209             Object value = getFieldValue(field, query);
210             if (annotation == null || value == null) {
211                 continue;
212             }
213             if (Container.must.equals(annotation.container())) {
214                 boolQueryBuilder.must(matchQuery(getFieldName(field, annotation.column()), formatValue(value)));
215             } else if (should.equals(annotation.container())) {
216                 if (value instanceof Collection) {
217                     BoolQueryBuilder shouldQueryBuilder = boolQuery();
218                     Collection tmp = (Collection) value;
219                     for (Object obj : tmp) {
220                         shouldQueryBuilder.should(matchQuery(getFieldName(field, annotation.column()), formatValue(obj)));
221                     }
222                     boolQueryBuilder.must(shouldQueryBuilder);
223                 } else {
224                     boolQueryBuilder.must(boolQuery().should(matchQuery(getFieldName(field, annotation.column()), formatValue(value))));
225                 }
226             }
227         }
228     }
229 
230     /**
231      *  
232      *
233      * @param boolQueryBuilder
234      * @param query
235      */
236     private void buildRangeQuery(BoolQueryBuilder boolQueryBuilder, T query) {
237         Class clazz = query.getClass();
238         for (Field field : clazz.getDeclaredFields()) {
239             RangeQuery annotation = field.getAnnotation(RangeQuery.class);
240             Object value = getFieldValue(field, query);
241             if (annotation == null || value == null) {
242                 continue;
243             }
244             if (Operator.gt.equals(annotation.operator())) {
245                 boolQueryBuilder.must(rangeQuery(getFieldName(field, annotation.column())).gt(formatValue(value)));
246             } else if (Operator.gte.equals(annotation.operator())) {
247                 boolQueryBuilder.must(rangeQuery(getFieldName(field, annotation.column())).gte(formatValue(value)));
248             } else if (Operator.lt.equals(annotation.operator())) {
249                 boolQueryBuilder.must(rangeQuery(getFieldName(field, annotation.column())).lt(formatValue(value)));
250             } else if (Operator.lte.equals(annotation.operator())) {
251                 boolQueryBuilder.must(rangeQuery(getFieldName(field, annotation.column())).lte(formatValue(value)));
252             }
253         }
254     }
255 
256     /**
257      * Sum 
258      *
259      * @param query
260      * @return
261      */
262     private List getSumBuilderList(T query) {
263         List list = new ArrayList<>();
264         Class clazz = query.getClass();
265         for (Field field : clazz.getDeclaredFields()) {
266             Sum annotation = field.getAnnotation(Sum.class);
267             if (annotation == null) {
268                 continue;
269             }
270             list.add(AggregationBuilders.sum(field.getName()).field(field.getName()));
271         }
272         if (CollectionUtils.isEmpty(list)) {
273             throw new SearchQueryBuildException("Can't find @Sum on " + clazz.getName());
274         }
275         return list;
276     }
277 
278 
279     /**
280      * GroupBy 
281      *
282      * @param query
283      * @return
284      */
285     private TermsBuilder buildGroupBy(T query) {
286         List sumList = new ArrayList<>();
287         Object groupByCollection = null;
288         Class clazz = query.getClass();
289         for (Field field : clazz.getDeclaredFields()) {
290             Sum sumAnnotation = field.getAnnotation(Sum.class);
291             if (sumAnnotation != null) {
292                 sumList.add(field);
293             }
294             GroupBy groupByannotation = field.getAnnotation(GroupBy.class);
295             Object value = getFieldValue(field, query);
296             if (groupByannotation == null || value == null) {
297                 continue;
298             } else if (!(value instanceof Collection)) {
299                 throw new SearchQueryBuildException("GroupBy filed must be collection");
300             } else if (CollectionUtils.isEmpty((Collection) value)) {
301                 continue;
302             } else if (groupByCollection != null) {
303                 throw new SearchQueryBuildException("Only one @GroupBy is allowed");
304             } else {
305                 groupByCollection = value;
306             }
307         }
308         Iterator iterator = ((Collection) groupByCollection).iterator();
309         TermsBuilder termsBuilder = recursiveAddAggregation(iterator, sumList);
310         return termsBuilder;
311     }
312 
313     /**
314      *  Aggregation
315      *
316      * @param iterator
317      * @return
318      */
319     private TermsBuilder recursiveAddAggregation(Iterator iterator, List sumList) {
320         String groupBy = iterator.next();
321         TermsBuilder termsBuilder = AggregationBuilders.terms(groupBy).field(groupBy).size(0);
322         if (iterator.hasNext()) {
323             termsBuilder.subAggregation(recursiveAddAggregation(iterator, sumList));
324         } else {
325             for (Field field : sumList) {
326                 termsBuilder.subAggregation(AggregationBuilders.sum(field.getName()).field(field.getName()));
327             }
328             sumList.clear();
329         }
330         return termsBuilder.order(Terms.Order.term(true));
331     }

3. scrollId 값 객체 저장
import lombok.Data;

@Data
public class ScrollId {

    private String value;

}

4. 조회 조작을 판단하는 데 사용되는 매거류
public enum Operator {
    gt, gte, lt, lte
}
public enum Container {
    must, should
}

좋은 웹페이지 즐겨찾기