인프런 커뮤니티 질문&답변

백엔드 주니어 개발자님의 프로필 이미지
백엔드 주니어 개발자

작성한 질문수

실전! Querydsl

nullSafeBuilder메서드를 통해 null처리의 한계

해결된 질문

작성

·

296

·

수정됨

1

개인 프로젝트를 진행하던중 where절의 조건들을 체이닝하는 과정에서 아래코드를 보면 경우 제일 앞에있는 nameLike()메서드가 null일 경우 에러가 발생하였습니다. and 조건일 경우 , 로 체이닝하면 되었지만 or조건들은 무조건 or()을 통해 연결을 해주어야했습니다.

query.selectFrom(member)
.where(nameLike("infren").or(nameEq("김영한강사님")))
.fetch();

BooleanBuilder를 통해 조건을 처리해도되지만

인프런 게시판을 통해알게된 nullSafeBuilder메서드를 만들어서 null처리를 하는 방법을 알게되었습니다. 하지만 몇가지 한계점들을 발견하였습니다.

eq문에 null이 들어갈 경우에는 정상적으로 작동하였으나

like절, 또는 in절에는 상황에 따라서 null을 넣을 경우 like,in문 자체가 파라미터로 null을 받지 못함으로 인해서 eq와는 다르게 작동하는 것을 발견하였습니다.

몇 가지 경우의 테스트를 진행해보았습니다.

  1. eq절에 null을 넣을경우

  2. like절에 null을 넣을 경우 -> NullPointerException

  3. in절에 String타입의 null을 넣을 경우

  4. in절에 String[] 타입의 null을 넣을 경우

  5. in절에 List<> 타입의 null을 넣을 경우 -> NullPointerException

  6. in절에 객체(Team) null을 넣을 경우

@Test
void eq에Null을넣을때(){
    String name = null;
    List<Member> findMember = query.selectFrom(member)
            .where(nullSafeBuilder(()-> member.username.eq(name)))
            .fetch();
    assertEquals(findMember.size(),4);
}

@Test()
void like절에_Null을_넣을때(){
    String name = null;
    assertThrows(NullPointerException.class, () -> {
        List<Member> findMember = query.selectFrom(member)
                .where(nullSafeBuilder(()-> member.username.like(name)))
                .fetch();
    });
}

@Test
void in절에_String타입의_Null을_넣을때(){
    String name = null;
    List<Member> findMember = query.selectFrom(member)
            .where(nullSafeBuilder(()-> member.username.in(name)))
            .fetch();
    assertEquals(findMember.size(),4);
}

@Test
void in절에_String배열타입의_Null을_넣을때(){
    String name = null;
    List<Member> findMember = query.selectFrom(member)
            .where(nullSafeBuilder(()-> member.username.in(name)))
            .fetch();
    assertEquals(findMember.size(),4);
}

@Test
void in절에_List에_Null을_넣을때(){
    List<Team> team = null;
    assertThrows(NullPointerException.class, () -> {
        List<Member> findMember = query.selectFrom(member)
                .where(nullSafeBuilder(()-> member.team.in(team)))
                .fetch();
    });
}

@Test
void in절에_Team타입의_Null을_넣을때(){
    Team team = null;
    List<Member> findMember = query.selectFrom(member)
            .where(nullSafeBuilder(()-> member.team.in(team)))
            .fetch();
    assertEquals(findMember.size(),4);
}

public static BooleanBuilder nullSafeBuilder(Supplier<BooleanExpression> f) {
    try {
        return new BooleanBuilder(f.get());
    } catch (IllegalArgumentException e) {
        return new BooleanBuilder();
    }
}

where절안에서 사용하는 함수(like, in...등등)에 따라 파라미터 자체에 null을 받지 못함으로인해 nullSafeBuilder를 통해 해결할 수 없는 경우도 있는것 같습니다.

이러한 경우에는 강사님께서 알려주셨던 아래와 같은 방법으로 메서드를 통해 파라미터의 null처리를 해주고

private BooleanExpression nameLike(String name){
    return name != null ? member.username.like(name) : null;
}

체이닝할 때는 and조건이면 , 를 사용하고, or조건으로 체이닝을 해야할 경우에는 BooleanBuilder객체에 체이닝하는 방식으로 구현을 해야할 것 같습니다.

아래는 프로젝트에 적용했던 동적쿼리문 입니다. 앞서 말했듯이 and조건은 ,로 연결하고 or조건들은 Booleanbuilder객체에 체이닝을 하였습니다.

//페이징 처리를 하지않은 동적쿼리문 -> 테스트에서 사용
public List<Article> searchBooleanBuilder(ArticleSearchCond cond) {

    BooleanBuilder builder = new BooleanBuilder();
    builder.or(contentLike(cond.getContent()))//글 내용 keyword검색
            .or(nickNameLike(cond.getWriter()))//작성자(닉네임) keyword검색
            .or(nameLike(cond.getWriter()))//작성자(이름) keyword검색
            .or(tagArticleIn(cond.getArticlesByTagValue()))//태그 keyword검색
            .or(restaurantNameLike(cond.getRestaurantName()));//음식점명 keyword검색

    return query.selectFrom(article)
            .where(
                    followMembersIn(cond.getFollowMembers()),//팔로우한 유저로 검색
                    sidoEq(cond.getSido()),//시도로 검색
                    sigoonEq(cond.getSigoon()),//시군으로 검색
                    dongEq(cond.getDong()),//동으로 검색
                    latitudeBetween(cond.getLatitude()),//위도로 검색
                    longitudeBetween(cond.getLongitude()),//경도로 검색
                    categoryEq(cond.getCategory()),//음식점 카테고리로 검색
                    likeArticleIn(cond.getLikeArticles()),//좋아요누른 게시판 검색
                    builder//keyword조건 검색
            )
            .orderBy(article.id.desc())//아이디가 높은 것(최신순)으로 내림차순
            .limit(20)
            .fetch();
}

아래는 nullSafeBuilder의 한계를 모른 상태로 구현하였던 에러가 발생하는 코드입니다. radioBtnSearchCond(and조건들), keywordSearchCond(or조건으로 연결)안에 nullsafeBuilder로 null처리한 메서드들이 있습니다.

 public List<Article> searchByNullSafer(ArticleSearchCond cond) {
        return query.selectFrom(article)
                .where(
                    radioBtnSearchCond(cond)//라디로 버튼 검색 조건들
                    .and(keywordSearchCond(cond))//keyword로 검색 조건들
                )
                .orderBy(article.id.desc())//아이디가 높은 것(최신순)으로 내림차순
                .fetch();
    }

혹시 nullSafeBuilder를 구현 좋은 방법이 있던가, 다른 좋은 방법을 아시는 분이 있으면 알려주시면 감사하겠습니다.

답변 1

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 백엔드 개발자 취준생님

말씀하신 것 처럼 모든 곳에 적용할 수 있는 것은 아닙니다.

이런 경우 각각의 케이스에 맞도록 코드를 작성해야 하는데요.

이것은 뭔가 더 편리하게 한번에 해결하기는 어려울 것 같고, 여러 유틸리티 메서드를 만들어서 해결해야 할 것 같아요. 관련해서 도움 주실 수 있는 분은 답변 부탁드립니다.

감사합니다.

백엔드 주니어 개발자님의 프로필 이미지
백엔드 주니어 개발자

작성한 질문수

질문하기