본문 바로가기

Elastic Stack/Elastic Search

Spring Boot - ElasticSearch RestHighLevelClient 검색 조건 중첩하기

 지난 게시글에서는 검색 정렬에 대해서 다뤄봤습니다. 

2021.10.17 - [Elastic Stack/Elastic Search] - Spring Boot - ElasticSearch RestHighLevelClient 특정 필드로 정렬하기

 

Spring Boot - ElasticSearch RestHighLevelClient 특정 필드로 정렬하기

 엘라스틱 서치의 RestHighLevelClient를 사용하여 검색 결과 문서의 정렬하는 방법을 소개하겠습니다. 소스코드 먼저 살펴보겠습니다. Controller @RestController public class TestController { @Autowired Re..

super-devstory.tistory.com

 이번에는 검색 조건을 거는 부분. 검색 조건이 중첩되는 부분을 보겠습니다. 소스 먼저 보시죠! 

Controller

@RestController
public class TestController {
	
	@Autowired
	TestService testService;
	
	@RequestMapping("/test/test.do")
	List<Map<String,Object>> test(
			@RequestParam(value="sort",defaultValue = "") String sort,
			@RequestParam(value="department",defaultValue="") String department,
			@RequestParam(value="publisher",defaultValue="") String publisher,
			@RequestParam(value="age",defaultValue="") String age
		) throws Exception{
		
		return testService.sendHighLevelApi("combook_*", sort, department, publisher, age);
	}
}

지난번에 다뤘던 sort 파라미터 뿐만 아니라 department(분야), publisher(출판사), age(적정 연령)에 대한 파라미터를 받았습니다. 각 파라미터들을 메서드에 전달합니다. 

 

ServiceImpl 

@Slf4j
@Service
public class TestServiceImpl implements TestService{
	
	@Autowired
	RestHighLevelClient client;
	
	@Override
	public List<Map<String, Object>> sendHighLevelApi(String indexName, String sort, String department, String publisher,
			String age) throws Exception {
		ArrayList<Map<String,Object>> list = null;
		if("".equals(sort)) sort = "date";
		log.debug("sort: "+sort);
		log.debug("publisher: " + publisher);
		log.debug("department: " + department);
		log.debug("age: " + age);
		
		try {
			SearchRequest searchRequest = new SearchRequest(indexName);
			SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
			searchSourceBuilder.size(10000);
			searchSourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS));
			
			BoolQueryBuilder query = new BoolQueryBuilder();
			
			if(!"".equals(publisher)) {
				query.must(QueryBuilders.matchQuery("publisher.keyword",publisher));
			}
			
			if(!"".equals(department)) {
				query.must(QueryBuilders.matchQuery("department.keyword", department));
			}
			
			if(!"".equals(age)) {
				if("초등3학년이상".equals(age) || "초등전학년".equals(age)) {
					age = age.concat("-"+age);
				}
				String minAge = age.split("-")[0];
				String maxAge = age.split("-")[1].replaceAll("세", "");
				query.must(QueryBuilders.matchQuery("min_age.keyword", minAge));
				query.must(QueryBuilders.matchQuery("max_age.keyword", maxAge));
			}
			
			if("".equals(publisher) && "".equals(department) && "".equals(age) ) {
				searchSourceBuilder.query(QueryBuilders.matchAllQuery());
			} else {
				searchSourceBuilder.query(query);
			}
			
			// sort
			if("date".equals(sort)) {
				searchSourceBuilder.sort(new FieldSortBuilder("reg_date.keyword").order(SortOrder.ASC)); // 등록일순 정렬
			} else if("cheap".equals(sort) ) {
				searchSourceBuilder.sort(new FieldSortBuilder("price").order(SortOrder.ASC)); // 등록일순 정렬
			} else if("expensive".equals(sort) ) {
				searchSourceBuilder.sort(new FieldSortBuilder("price").order(SortOrder.DESC)); // 등록일순 정렬
			}
			searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));  // score 높은순 (default)
			searchRequest.source(searchSourceBuilder);
			
			SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
			
			
			SearchHits hits = searchResponse.getHits();
			SearchHit[] searchHits = hits.getHits();
			list = new ArrayList<Map<String,Object>>();
			for (SearchHit hit : searchHits) {
				Map<String, Object> sourceAsMap = hit.getSourceAsMap();
				// log.debug("sourceAsMap: " + sourceAsMap);
				list.add(sourceAsMap);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		
		return list;
	}
}

여러 조건을 중첩할 때에는 BoolQueryBuilder를 사용합니다. 

 BoolQueryBuilder에서는 must, should 이 두가지를 제일 많이 사용합니다. 

 must는 반드시 일치해야 검색 결과가 도출됩니다. (AND 조건)

 should는 여러 개 중에 하나만 일치하여도 검색 결과가 도출됩니다. (OR 조건)

 저는 must를 사용하여 AND 조건으로 검색 조건을 중첩해보겠습니다! 

must의 인자로 QueryBuilder가 들어갑니다. 여러가지 querybuilder 가 있는데, 저는 matchQuery를 사용하였고 matchQuery의 사용 방법은 ("필드명", 쿼리 값)입니다. 이때 필드명. keyword를 하는 이유는 필드명과 정확히 일치할 때 검색 결과가 도출됩니다. keyword가 빠진다면 '도서출판 아람'으로 검색했을 때 '도서출판 무지개', '도서출판 아람' 이렇게 두 결과가 나옵니다. 

 publisher, department, age의 값이 있을 때 boolquerybuilder에 must메서드로 검색 조건을 추가합니다. 파라미터 3개의 값이 모두 있을 때에는 제 소스의 경우에는 must가 4번 걸리게 되겠네요 ! 

결과 확인

publisher=교원

출판사가 교원인 문서 4건이 결과로 나왔습니다. 

 

publisher=교원, age=초등전학년

출판사가 교원, 적정 연령이 초등 전 학년인 문서 2건이 검색 결과로 나왔습니다. 

 

publisher=교원, age=초등전학년, department=과학동화

출판사가 교원, 적정 연령이 초등 전 학년, 분야가 과학동화인 문서 1건이 검색 결과로 나왔습니다.