본문 바로가기
Java 웹 프로그래밍

[원티드 프리온보딩] 검색 기능의 구현

by irerin07 2023. 10. 7.
728x90

고민중인 것

1. 검색 기능을 구현시 어떤 식으로 구현할 것인가 고민이다.

 

채용 공고를 키워드로 검색하여 해당하는 채용공고들을 보여줘야 하는데

 

현재 채용공고 테이블에는 4개의 컬림이 있고, 회사 테이블에는 3개의 컬럼이 있다.

 

간단하게 생각나는 방법으로는 단순하게 모든 컬럼에서 해당 키워드가 있는지 검색을 하는 것이다.

(채용 포지션, 요구 스킬, 채용 보상금, 채용 내용, 회사명, 국가, 지역)

 

테이블에 데이터가 몇건 없다면 사실 큰 문제가 없으리라 생각하지만 데이터가 엄청나게 많다면 이는 분명 문제가 될 것이라고 생각한다.

 

그 다음으로 생각한 것이 

채용 포지션, 요구 스킬, 채용 보상금, 채용 내용, 회사명, 국가, 지역 모두를 하나로 합쳐서 하나의 컬럼(예를 들면 키워드라는 이름의 컬럼)을 만들어 해당 컬럼에서 유저가 검색한 키워드가 포함되어 있는지를 검색하는 것이다.

 

모든 컬럼을 돌지 않고 하나의 컬럼에만 의존하기 때문에 이전보다는 좋은 방법이지 않을까? 하고 생각은 하고 있다.

 

그 다음은 조금 시간이 걸리더라도 엘라스틱서치 혹은 루씬을 사용하는 것이다.

이건 너무 과한것 같아 일단은 생각만 하고 있다.

 

다른 좋은 방법이 없는지 좀 더 찾아봐야겠다.

 

-------

 

알아낸 몇가지 방법 중 Example Matcher와 JPA Specification 이라는 것을 알게 되었다.

 

1. ExampleMatcher

@Override
	public Page<EmployeeProjectView> findEmployeeProjectsExampleMatcher(EmployeeRequestDTO employeeRequestDTO)
	{
		/* Build Search object */
		EmployeeProjectView employeeProjectView=new EmployeeProjectView();
		employeeProjectView.setEmployeeId(employeeRequestDTO.getEmployeeId());
		employeeProjectView.setLastName(employeeRequestDTO.getFilterText());
		employeeProjectView.setFirstName(employeeRequestDTO.getFilterText());
		try
		{
			employeeProjectView.setProjectId(Long.valueOf(employeeRequestDTO.getFilterText()));
			employeeProjectView.setProjectBudget(Double.valueOf(employeeRequestDTO.getFilterText()));
		}
		catch (Exception e)
		{
			log.debug("Supplied filter text is not a Number");
		}
		employeeProjectView.setProjectName(employeeRequestDTO.getFilterText());
		employeeProjectView.setProjectLocation(employeeRequestDTO.getFilterText());

		/* Build Example and ExampleMatcher object */
		ExampleMatcher customExampleMatcher = ExampleMatcher.matchingAny()
				.withMatcher("firstName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
				.withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
				.withMatcher("projectId", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
				.withMatcher("projectName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
				.withMatcher("projectLocation", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
				.withMatcher("projectBudget", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase());

		Example<EmployeeProjectView> employeeExample= Example.of(employeeProjectView, customExampleMatcher);

		/* Get employees based on search criteria*/
		return employeeProjectViewRepository.findAll(employeeExample, PageRequest.of(employeeRequestDTO.getCurrentPageNumber(),
				employeeRequestDTO.getPageSize(), Sort.by(employeeRequestDTO.getSortColumnName()).descending()));
	}

 

 

 

2. JPA Specification

private Specification<EmployeeProjectView> getSpecification(EmployeeRequestDTO employeeRequestDTO)
{
	//Build Specification with Employee Id and Filter Text
	return (root, criteriaQuery, criteriaBuilder) ->
	{
		criteriaQuery.distinct(true);
		//Predicate for Employee Id
		Predicate predicateForEmployee = criteriaBuilder.equal(root.get("employeeId"), employeeRequestDTO.getEmployeeId());

		if (isNotNullOrEmpty(employeeRequestDTO.getFilterText()))
		{
			//Predicate for Employee Projects data
			Predicate predicateForData = criteriaBuilder.or(
					criteriaBuilder.like(root.get("firstName"), "%" + employeeRequestDTO.getFilterText() + "%"),
					criteriaBuilder.like(root.get("lastName"), "%" + employeeRequestDTO.getFilterText() + "%"),
					criteriaBuilder.like(root.get("projectId").as(String.class), "%" + employeeRequestDTO.getFilterText() + "%"),
					criteriaBuilder.like(root.get("projectName"), "%" + employeeRequestDTO.getFilterText() + "%"),
					criteriaBuilder.like(root.get("projectBudget").as(String.class), "%" + employeeRequestDTO.getFilterText() + "%"),
					criteriaBuilder.like(root.get("projectLocation"), "%" + employeeRequestDTO.getFilterText() + "%"));

			//Combine both predicates
			return criteriaBuilder.and(predicateForEmployee, predicateForData);
		}
		return criteriaBuilder.and(predicateForEmployee);
	};
}

 

두가지 방법 모두 내가 검색하고자 하는 컬럼을 미리 지정한 다음 그 조건으로 쿼리를 만들어 주는 기능으로 보인다.

 

장점이라면 복잡한 쿼리를 내가 직접 만들 필요가 없다는 것인데 

 

단점이라면 역시 컬럼이 추가되거나 변경 되었을때 조금 불편할 수 있다는 것이다.

 

다만 현재로서는 위 두가지 방법이 가장 좋아 보인다.

 

물론 내가 직접 @Query 어노테이션을 사용하여 검색 쿼리를 작성 할 수도 있지만 우선 위의 두 방법을 사용해보고 정하면 좋을 것 같다.

728x90