Spring

프로젝트 QueryDSL 적용기

쭈녁 2024. 2. 8. 19:26

 

 

 

사이드 프로젝트에 필터 및 검색기능을 추가해달라는 요청이 있었다.

기존 페이지네이션으로 결과 값을 주고 있던 API에 QueryDSL로 검색 필터를 적용하였다.

 

요청 사항:

  • DB에 대한 검색 기능
    • 키워드를 통해 제목, 내용에 포함된 DB 검색
    • positionCode로 일치하는 DB 검색
    • parentCode(positionCode의 부모) 로 검색
    • 상태 값을 통한 검색
  • 응답값 형식:
        id": 41,
        "name": "포지션 테스트용",
        "recruitStartDate": "2024-02-03",
        "recruitEndDate": "2024-02-03",
        "content": "테스트 용",
        "deposit": 200000,
        "count": 5,
        "positionCodeList":[
        	{
            "positionCode": 14,
            "name": "프론트",
            "parentCode": 11,
            "parentName": "개발"
          },
          {
            "positionCode": 15,
            "name": "백엔드",
            "parentCode": 11,
            "parentName": "개발"
          }
        ]

 

 

검색 값 DTO

@Getter
@Setter
public class ProjectSearchCond {
    private String keyword;
    private Integer positionCode;
    private Integer parentCode;
    private Integer status;
}

 

기본 DI 설정

@Repository
@Transactional
public class ProjectQueryRepository {
    private final EntityManager em;
    private final JPAQueryFactory query;

    public ProjectQueryRepository(EntityManager em, ProjectPositionRepository projectPositionRepository) {
        this.em = em;
        this.query = new JPAQueryFactory(em);
    }
}

 

검색에 대한 로직


    public Page<Project> searchProjectByPage(ProjectSearchCond cond, Pageable pageable) {
        String keyword = cond.getKeyword();
        Integer positionCode = cond.getPositionCode();
        Integer parentCode = cond.getParentCode();
        Integer status = cond.getStatus();
        
        List<Project> projects = query
                .select(project)
                .from(project)
                .join(project.positionCodeList, projectPosition)
                .where(nameLike(keyword), contentLike(keyword), statusSame(status), projectPositionSame(positionCode), projectPositionParentMatch(parentCode))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize() + 1).fetch();

        JPAQuery<Long> count = query.select(project.count())
                .from(project)
                .join(project.positionCodeList, projectPosition)
                .where(nameLike(keyword), contentLike(keyword), statusSame(status), projectPositionSame(positionCode));

        return PageableExecutionUtils.getPage(projects, pageable, count::fetchOne);
    }
    private BooleanExpression nameLike(String name) {
        if (StringUtils.hasText(name)) {
            return project.name.like("%" + name + "%");
        }
        return null;
    }
    private BooleanExpression contentLike(String content) {
        if (StringUtils.hasText(content)) {
            return project.name.like("%" + content + "%");
        }
        return null;
    }
    private BooleanExpression statusSame(Integer status) {
        if (status != null) {
            return project.status.eq(status);
        }
        return null;
    }
    private BooleanExpression projectPositionSame(Integer positionCode) {
        if (positionCode != null) {
            return projectPosition.positionCode.eq(positionCode);
        }
        return null;
    }
    private BooleanExpression projectPositionParentMatch(Integer parentCode) {
        if (parentCode != null) {
            return projectPosition.codeGroup.parent.id.eq(parentCode);
        }
        return null;
    }

 

검색에 필요한 값을 받아오는 DTO 객체 ProjectSearchCond 와 페이지네이션을 적용하기 위한 Pageable 객체를 받아왔다.

받아온 필터의 값을 기반으로 내부 메서드를 통해 제목, 내용, 포지션 코드 등에 대한 where 를 적용하여 필터 한 코드이다.

 

 

필터 요청 값

 

응답 값

{
  "code": 1,
  "message": "정상적으로 처리되었습니다.",
  "data": {
    "content": [
      {
        "id": 41,
        "name": "포지션 테스트용",
        "recruitStartDate": "2024-02-03",
        "recruitEndDate": "2024-02-03",
        "content": "테스트 용",
        "deposit": 200000,
        "count": 5,
        "positionCodeList": [
          {
            "positionCode": 14,
            "name": "프론트",
            "parentCode": 11,
            "parentName": "개발"
          },
          {
            "positionCode": 15,
            "name": "백엔드",
            "parentCode": 11,
            "parentName": "개발"
          },
          {
            "positionCode": 16,
            "name": "서버/인프라",
            "parentCode": 11,
            "parentName": "개발"
          }
        ]
      },
      {
        "id": 42,
        "name": "쿼리 DSL 확인 테스트,",
        "recruitStartDate": "2024-02-07",
        "recruitEndDate": "2024-02-15",
        "content": "내용내용",
        "deposit": 200000,
        "count": 5,
        "positionCodeList": [
          {
            "positionCode": 14,
            "name": "프론트",
            "parentCode": 11,
            "parentName": "개발"
          },
          {
            "positionCode": 15,
            "name": "백엔드",
            "parentCode": 11,
            "parentName": "개발"
          },
          {
            "positionCode": 16,
            "name": "서버/인프라",
            "parentCode": 11,
            "parentName": "개발"
          }
        ]
      },
      {
        "id": 45,
        "name": "포지션 확인 테스트",
        "recruitStartDate": "2024-02-07",
        "recruitEndDate": "2024-02-15",
        "content": "내용내용",
        "deposit": 200000,
        "count": 5,
        "positionCodeList": [
          {
            "positionCode": 16,
            "name": "서버/인프라",
            "parentCode": 11,
            "parentName": "개발"
          }
        ]
      }
    ],
    "number": 0,
    "size": 10,
    "totalPages": 1,
    "totalElement": 3,
    "numberOfElement": 3
  },
  "result": true
}

 

요청에 따른 응답 값이 페이지 객체에 감싸져서 잘 나옴을 확인 할 수 있다. 하지만 한가지 문제가 있었다.

 

실제 쿼리 문

 

전체를 조회하는데 나간 쿼리문의 길이이다... N+1 문제가 한곳도 아니고 여러곳에서 발생함을 확인 할 수있다.

지금은 10건에 대한 조회를 하기에 쿼리 속도에 문제가 크게 있지 않지만 데이터가 많아질 수록 성능에 심각한 문제가 생긴다. 해당 문제에 대한 해결 방안(JPA 케이스, QueryDSL케이스)에 대한 포스팅은 다음 글에서 정리 해볼 것이다.

 

 

'Spring' 카테고리의 다른 글

N+1 문제 해결(JPA)  (1) 2024.02.12
N+1 해결 (QueryDSL)  (0) 2024.02.11
Swagger 적용  (1) 2024.02.05
Spring Profile  (0) 2024.02.03
MVC 모델 타임리프 폼 (체크 박스 / 셀렉 박스 / 라디오박스)  (0) 2024.02.02