사용자 정의 함수(match against) dialect 사용하기

ttomy·2023년 9월 26일
1

도입

querydsl을 통한 검색 기능에 full text search를 도입했다. 때문에 match against문을 사용해야 했다.
이때 이 dialect를 어떻게 해야 querydsl에서 사용할 수 있을까?

프로젝트의 환경은 아래와 같다.

springframework.boot' version '3.1.1'
org.springframework.boot:spring-boot-starter-data-jpa:3.1.1
org.hibernate.orm:hibernate-core:6.2.5.Final
com.querydsl:querydsl-jpa:5.0.0

방언 그냥 string으로 직접 쓰면 안되나?

아래와 같이 querydsl의 TemplateExpression을 이용해서 방언을 직접 작성하면 동작하지 않을까?

   public List<Cafe> findAllBy(final String cafeNameWord, final String addressWord) {
        return from(cafe)
                .where(
                        contains(cafe.name, cafeNameWord),
                        contains(cafe.address, addressWord))
                .fetch();
    }
    

    private BooleanExpression contains(final StringPath target, final String searchWord) {
        if (isBlank(searchWord)) {
            return null;
        }
        return booleanTemplate("match({0}) against ({1} in boolean mode) > 0", target, searchWord);
    }

아래와 같이 테스트를 실행해 확인해본다

   @DisplayName("카페 이름,주소로 카페를 조회한다")
    void findByNameAndAddress() {
        // given
        final String cafeName = "카페";
        final String address = "주소3";

        // when
        final List<Cafe> cafes = cafeCustomRepositoryImpl.findAllBy(cafeName, address);

        // then
        assertThat(cafes).extracting(Cafe::getName).containsOnly(cafe3.getName());
    }

헉 이런 예외와 함께 실패한다. hibernate에서 hql로 parsing을 하는 과정에서 문제가 생긴다.

dialect를 사용해보자

직접 match against문을 사용하면 파싱 과정에서 문제가 생긴다는 건 알겠다.
hql에서 특수한 방언,함수까지 다 지원해주지는 않기 때문이다.
그러니 직접 dialect를 등록해 사용해보자.

public class MySQLDialectConfig extends MySQL8Dialect {

    public MySQLDialectConfig() {
        super();

        this.registerFunction("function_test", new StandardSQLFunction("function_test", new StringType()));
    }
}

이런식으로 등록을 하면 된다는 글도 있다.
하지만 빨간색 글자가 뜨며 메서드의 import가 안된다? -> deprecated되었다.

https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#dialects
여기의 Dialect부분에서 보듯 version-specific dialects들이 deprecated되었다.

아 그럼 그냥 MySQLDialect을 사용하면 되지 않나?
그런데 registerFunction()을 불러올 수 없다..

왜지?

https://discourse.hibernate.org/t/migration-of-dialect-to-hibernate-6/6956
https://discourse.hibernate.org/t/user-defined-functions-in-dialect/6962
https://discourse.hibernate.org/t/hibernate-6-quarkush2dialect-registerfunction/7924/2
따로 registerFunction가 deprecated되었다는 내용은 찾지 못했으나, 위의 글들처럼 최신버전에서 registerFunction()을 사용하지 못한다는 사례가 있고 그에 대해 functionContributor를 사용하라 답변하고 있다.

hibernate 6.3의 query language guide에 간략하게 언급은 되고있다.

MetaDataBuilder를 통해 dialect를 등록하는 사례도 존재했는데 이는 deprecated되었다.
이 이미지에서도 functionContributor를 사용하라 설명하고 있는 모습이다.

그러니 functionContributor로 들어가 살펴보자.

functionContributor사용하기


beikov님의 답변에 따라 function을 등록해 사용해보겠다.

public class CustomFunctionContributor implements FunctionContributor {

    private static final String FUNCTION_NAME = "match_against";
    private static final String FUNCTION_PATTERN = "match (?1) against (?2 in boolean mode)";

    @Override
    public void contributeFunctions(final FunctionContributions functionContributions) {
        functionContributions.getFunctionRegistry()
                .registerPattern(FUNCTION_NAME, FUNCTION_PATTERN,
                        functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve(DOUBLE));
    }
}

그리고 org.hibernate.boot.model.FunctionContributor파일에
새로 만든 CustomFunctionContributor의 위치도 명시해주면 아래처럼
등록한 사용자 정의 함수를 사용할 수 있다.

    private BooleanExpression contains(final StringPath target, final String searchWord) {
        if (isBlank(searchWord)) {
            return null;
        }

        final String formattedSearchWord = "\"" + searchWord + "\"";
        return numberTemplate(Double.class, "function('match_against', {0}, {1})",
                target, formattedSearchWord)
                .gt(MATCH_THRESHOLD);
    }

1개의 댓글

comment-user-thumbnail
2024년 7월 19일

이 방식으로 했을 때 test는 어떻게 하셨나요??

답글 달기