[Co Labor] 검색 기능 구현

2024. 9. 4. 21:40프로젝트: Co Laobr

요구사항 정리

  • 검색하려는 키워드를 채용 공고, 기업 리뷰, 기업 정보 중에서 검색
  • 채용 공고 중 title, description 엔티티에서 검색
  • 기업 리뷰 중 pros, cons 엔티티에서 검색
  • 기업 정보 중 name, address, description 엔티티에서 검색

Repository

EnterpriseRepository

package pelican.co_labor.repository.enterprise;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import pelican.co_labor.domain.enterprise.Enterprise;

import java.util.List;

@Repository
public interface EnterpriseRepository extends JpaRepository<Enterprise, Long> {

    @Query("SELECT e FROM Enterprise e WHERE LOWER(e.name) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(e.address) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(e.description) LIKE LOWER(CONCAT('%', :keyword, '%'))")
    List<Enterprise> searchEnterprises(@Param("keyword") String keyword);
}

 

  • @Query("SELECT e FROM Enterprise e WHERE e.name LIKE %:keyword% OR e.address LIKE %:keyword% OR e.description LIKE %:keyword%") List<Enterprise> searchEnterprises(@Param("keyword") String keyword);
    : 특정 키워드를 사용하여 Enterprise 엔티티를 검색하는 커스텀 쿼리를 정의한다. @Param 으로 메서드의 파라미터와 쿼리의 %:keyword% 를 매핑하는 역할을 한다.

ReviewRepository

package pelican.co_labor.repository.review;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import pelican.co_labor.domain.review.Review;

import java.util.List;

@Repository
public interface ReviewRepository extends JpaRepository<Review, Long> {
    @Query("SELECT r FROM Review r WHERE LOWER(r.pros) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(r.cons) LIKE LOWER(CONCAT('%', :keyword, '%'))")
    List<Review> searchReviews(@Param("keyword") String keyword);
}

JobRepository

package pelican.co_labor.repository.job;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import pelican.co_labor.domain.job.Job;

import java.util.List;

@Repository
public interface JobRepository extends JpaRepository<Job, Long> {
    @Query("SELECT j FROM Job j WHERE LOWER(j.title) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(j.description) LIKE LOWER(CONCAT('%', :keyword, '%'))")
    List<Job> searchJobs(@Param("keyword") String keyword);
}

Service

SearchService

package pelican.co_labor.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pelican.co_labor.domain.enterprise.Enterprise;
import pelican.co_labor.domain.job.Job;
import pelican.co_labor.domain.review.Review;
import pelican.co_labor.repository.enterprise.EnterpriseRepository;
import pelican.co_labor.repository.job.JobRepository;
import pelican.co_labor.repository.review.ReviewRepository;

import java.util.List;

@Service
public class SearchService {

    @Autowired
    private JobRepository jobRepository;

    @Autowired
    private ReviewRepository reviewRepository;

    @Autowired
    private EnterpriseRepository enterpriseRepository;

    public List<Job> searchJobs(String keyword) {
        return jobRepository.searchJobs(keyword);
    }

    public List<Review> searchReviews(String keyword) {
        return reviewRepository.searchReviews(keyword);
    }

    public List<Enterprise> searchEnterprises(String keyword) {
        return enterpriseRepository.searchEnterprises(keyword);
    }
}
  • 비즈니스 로직을 독립적으로 테스트하기 위해 서비스 계층으로 분리하였다.

Controller

SearchController

package pelican.co_labor.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import pelican.co_labor.domain.enterprise.Enterprise;
import pelican.co_labor.domain.job.Job;
import pelican.co_labor.domain.review.Review;
import pelican.co_labor.service.SearchService;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/search")
public class SearchController {

    @Autowired
    private SearchService searchService;

    @GetMapping
    public Map<String, Object> search(@RequestParam String keyword) {
        Map<String, Object> response = new HashMap<>();

        List<Job> jobs = searchService.searchJobs(keyword);
        List<Review> reviews = searchService.searchReviews(keyword);
        List<Enterprise> enterprises = searchService.searchEnterprises(keyword);

        response.put("jobs", jobs);
        response.put("reviews", reviews);
        response.put("enterprises", enterprises);

        return response;
    }
}

  • http://localhost:8080/search?keyword=tech 와 같은 링크로 요청하면 된다.

이제 Postman으로 테스트하면 되는데 일단 더미데이터를 기반으로 테스트해봤다.

각 엔티티에서 OneToMany, ManyToOne 모두 관계를 설정했더니 연관된 관계를 모두 JSON으로 리턴하면서 review와 labor_user가 서로를 호출하는 무한루프가 발생했고 OneToMany 관계를 모두 삭제했다. 그 후 결과는 다음과 같다.

{
    "reviews": [
        {
            "review_id": 1,
            "laborUser": {
                "labor_user_id": 1,
                "password": "password123",
                "name": "Alice Johnson",
                "email": "alice.johnson@example.com",
                "created_at": "2024-07-09T18:29:26.624766"
            },
            "enterprise": {
                "enterprise_id": 1,
                "name": "Tech Company",
                "address": "123 Tech Street",
                "description": "Leading tech company in the industry.",
                "created_at": "2024-07-09T18:29:26.568681"
            },
            "title": "Tech Place to Work",
            "rating": 5,
            "promotion_rating": 4,
            "salary_rating": 3,
            "balance_rating": 5,
            "culture_rating": 4,
            "management_rating": 4,
            "pros": "Great work-life balance and culture.",
            "cons": "Salary could be higher.",
            "like_count": 10,
            "created_at": "2024-07-09T18:29:26.661496",
            "modified_at": "2024-07-09T18:29:26.661496"
        }
    ],
    "jobs": [
        {
            "job_id": 1,
            "enterpriseUser": {
                "enterprise_user_id": 1,
                "password": "password123",
                "name": "John Doe",
                "email": "john.doe@techcompany.com",
                "created_at": "2024-07-09T18:29:26.614783",
                "enterprise": {
                    "enterprise_id": 1,
                    "name": "Tech Company",
                    "address": "123 Tech Street",
                    "description": "Leading tech company in the industry.",
                    "created_at": "2024-07-09T18:29:26.568681"
                }
            },
            "enterprise": {
                "enterprise_id": 1,
                "name": "Tech Company",
                "address": "123 Tech Street",
                "description": "Leading tech company in the industry.",
                "created_at": "2024-07-09T18:29:26.568681"
            },
            "title": "Tech Engineer",
            "description": "Develop and maintain software solutions.",
            "gender": "Any",
            "age": "Any",
            "views": 100,
            "dead_date": "2024-08-08T18:29:26.639517",
            "created_at": "2024-07-09T18:29:26.645533",
            "modified_at": "2024-07-09T18:29:26.645533"
        }
    ],
    "enterprises": [
        {
            "enterprise_id": 1,
            "name": "Tech Company",
            "address": "123 Tech Street",
            "description": "Leading tech company in the industry.",
            "created_at": "2024-07-09T18:29:26.568681"
        }
    ]
}
  • review : laborUser, enterprise 정상 출력
  • job : enterpriseUser, enterprsie 정상 출력
  • enterprise : 연관된 엔티티 출력하지 않음.
    • 원래 enterprise와 연관된 job, review를 출력할까 생각했지만 해당 컨트롤러는 검색이므로 기업 정보만 출력하고 기업 상세 컨트롤러에서 job, review 등을 출력하는 편이 더 좋다고 판단하여 그대로 두기로 결정하였다.