저고데

[ElasticSearch] 3. 검색 엔진을 구현해보자 본문

ElasticSearch

[ElasticSearch] 3. 검색 엔진을 구현해보자

진철 2024. 1. 30. 17:29
728x90
반응형

이전에 2편에서 관계형 데이터베이스와 엘라스틱 서치에 대한 성능 차이를 실험해보았다.(결과는 내 예상과 매우 달랐지만 ...)

아무튼 아무튼 이번에는 해당 검색 결과를 화면에 출력하는 검색 엔진을 만들어보도록 하겠다.

코드 수정하기
<form th:action="@{/search}" method="get">
    <label for="query">검색어:</label>
    <input type="text" id="query" name="query" />
    <label for="searchType">검색 항목:</label>
    <select id="searchType" name="searchType">
        <option value="name">이름</option>
        <option value="age">나이</option>
    </select>

    <button type="submit" class="btn btn-primary">검색</button>
</form>

home.html 파일에 다음과 같은 코드를 추가하여 검색 창을 만든다.

이 때, 사용자의 이름과 나이에 대한 정보로 검색이 가능하기 위해서 Thymeleaf의 select를 통해 "이름"과 "나이"를 선택할 수 있게 해준다.

@Repository
public interface UserRepository extends ElasticsearchRepository<User, String> {
    List<User> findByName(String name);
    @Query("{\"wildcard\":{\"name.keyword\":\"*?0*\"}}")
    List<User> findByNameWildcard(String name);

    @Query("{\"wildcard\":{\"age.keyword\":\"*?0*\"}}")
    List<User> findByAgeWildcard(String age);
}

이는 레포지토리 코드이다.

이전 게시글에서 언급했듯이, @Query 어노테이션 중에서 wildcard를 사용하여 해당 검색어와 완벽하게 일치하는 것만 찾는 것이 아니다.

SQL의 "like" 예약어처럼 해당 검색어가 포함된 모든 결과물을 반환할 수가 있다.

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List<User> findByNameWildcard(String name) {
        return userRepository.findByNameWildcard(name);
    }

    public List<User> findByAgeWildcard(String age) {
        return userRepository.findByAgeWildcard(age);
    }
}

다음으로 Service 코드이다.

Spring Boot에서 제공하는 ElasticsearchRepository의 자동 메소드를 통해서 접근이 가능하다.

@GetMapping("/search")
public String search(@RequestParam String query, @RequestParam String searchType, Model model) {
    if (searchType.equals("name")) {
        List<User> users = userService.findByNameWildcard(query);
        model.addAttribute("users", users);
    } else if (searchType.equals("age")) {
        List<User> users = userService.findByAgeWildcard(query);
        model.addAttribute("users", users);
    }
    return "home";
}

그리고 Controller 코드이다.

이전 home.html의 검색 창에서 "이름"과 "나이"를 카테고리로 선택하여 검색이 이루어진다.

따라서 카테고리를 나타내는 searchType을 @RequestParam 어노테이션으로 받아주고 검색어는 역시 같이 받아준다.

그리고 해당 목록들을 List를 통해서 html 화면에 전달한다.

<div th:unless="${users == null or #lists.isEmpty(users)}">
    <h2>검색 결과</h2>
    <table>
        <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Age</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="user : ${users}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.name}"></td>
            <td th:text="${user.age}"></td>
        </tr>
        </tbody>
    </table>
</div>
<div th:if="${users == null or #lists.isEmpty(users)}">
    <p>검색 결과가 없습니다.</p>
</div>

마지막으로 home.html에 다음과 같은 코드를 추가하여 화면에 출력하면 끝이다.

이제 결과를 살펴보자.

주소의 get 요청에서 알 수 있듯이, 이름 카테고리에 temp14라고 검색했을 때의 결과이다.

이름이 temp14로 일치하는 항목만 출력하지 않고 temp141, temp1410처럼 검색어가 포함된 모든 항목들이 포함된 것을 알 수가 있다.

문제 발생 ...

하지만 문제가 발생하였다.

이번에는 temp를 이름 카테고리로 검색을 하였다.

사진에서 알 수 있듯이 에러가 발생하였다.

temp라는 데이터의 갯수가 15000개이기 때문에 한 화면에서 출력할 수 있는 제한량을 초과했기 때문이라고 생각이 든다.

그리고 또 두 번째 문제점이 있었다.

"이름" 카테고리로 검색을 하였을 때는 문제가 없었지만, "나이" 카테고리로 검색을 했을 때, 위의 사진과 동일하게 500에러가 발생하였다.

포스트맨에서 알 수 있듯이 현재 엘라스틱 서치의 user에 저장된 데이터의 나이 타입은 정수형이다.

@GetMapping("/search")
public String search(@RequestParam String query, @RequestParam String searchType, Model model) {
    if (searchType.equals("name")) {
        List<User> users = userService.findByNameWildcard(query);
        model.addAttribute("users", users);
    } else if (searchType.equals("age")) {
        // List<User> users = userService.findByAgeWildcard(query); -> 기존 코드
        List<User> users = userService.findByAgeWildcard(Integer.parseInt(query));
        model.addAttribute("users", users);
    }
    return "home";
}

따라서 다음과 같이 String 형식의 query를 정수형으로 바꿔주었다. (기존 레포지토리, 서비스 코드 역시 변경해주었다.)

촬영을 못해서 이전 오류 화면으로 대처합니다.. 죄송합니다

하지만 이번에도 ... 계속 500에러가 발생하였다 ..

아마 int형이 아닌 다른 형식이라서 오류가 발생하는 것 같다.

분명 "이름" 카테고리에서 검색을 할 때는 문제가 없는 걸로 보아, 검색 알고리즘 자체는 원인이 아닌 것 같다.

아마 변수의 타입이 원인이지 않나 생각이 든다.

마무리

오늘은 이렇게 엘라스틱 서치를 이용하여 간단한 검색 엔진을 만들어보았다.

다음 시간에는 앞서 발생한 문제 중 하나인 temp를 검색했을 때, 데이터 초과로 발생한 문제를 해결해보도록 하겠다.

Pageable을 사용한다고 다른 분들이 그러시던데 아마 이를 사용할 듯하다.

그리고 다른 타입을 통한 검색도 해결해보도록 하겠다.

728x90
반응형