JPA Criteria API - возможно ли выполнять поиск с префиксами, с использованием токенов? - PullRequest
0 голосов
/ 05 декабря 2018

У нас проблема в том, что на данный момент нам не разрешено использовать ElasticSearch, поэтому нам нужно реализовать функцию поиска с MySQL.Одной из желаемых функций является поиск по префиксу с токенами, поэтому такое предложение, как

«Быстрая коричневая лиса, прыгнувшая через ленивую собаку», может быть найдено при поиске слова «прыжок».Я думаю, мне нужно определить правило вроде (псевдокод):

(*)(beginning OR whitespace)(prefix)(*)

Я предполагаю, что это можно сделать с помощью JPA (Criteria API)?Но что, если у нас есть два условия?Все они должны быть объединены с помощью AND, например, приведенное выше правило должно приводить к TRUE для обоих терминов хотя бы в одном столбце.Это означает, что «прыгать лиса» может привести к попаданию, но «прыгать кролик» не будет.Возможно ли это с Criteria API?

Или вы знаете лучшее решение, чем Criteria API?Я слышал, что Hibernate может выполнять LIKE-запросы более элегантно (с меньшим количеством кода), но, к сожалению, мы используем EclipseLink.

Основываясь на ответе ниже, вот мое полное решение.Это все в одном методе, чтобы упростить его здесь (хотя «простой API критериев JPA» - это оксюморон).Если кто-то хочет использовать его, рассмотрите возможность рефакторинга

public List<Customer> findMatching(String searchPhrase) {
    List<String> searchTokens = TextService.splitPhraseIntoNonEmptyTokens(searchPhrase);
    if (searchTokens.size() < 1 || searchTokens.size() > 5) { // early out and denial of service attack prevention
        return new ArrayList<>();
    }

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Customer> criteriaQuery = criteriaBuilder.createQuery(Customer.class);

    Root<Customer> rootEntity = criteriaQuery.from(Customer.class);

    Predicate[] orClausesArr = new Predicate[searchTokens.size()];

    for (int i = 0; i < searchTokens.size() ; i++) {
        // same normalization methods are used to create the indexed searchable data
        String assumingKeyword = TextService.normalizeKeyword(searchTokens.get(i));
        String assumingText = TextService.normalizeText(searchTokens.get(i));
        String assumingPhoneNumber = TextService.normalizePhoneNumber(searchTokens.get(i));

        String assumingKeywordInFirstToken = assumingKeyword + '%';
        String assumingTextInFirstToken = assumingText + '%';
        String assumingPhoneInFirstToken = assumingPhoneNumber + '%';
        String assumingTextInConsecutiveToken = "% " + assumingText + '%';

        Predicate query = criteriaBuilder.or(
            criteriaBuilder.like(rootEntity.get("normalizedCustomerNumber"), assumingKeywordInFirstToken),
            criteriaBuilder.like(rootEntity.get("normalizedPhone"), assumingPhoneInFirstToken),
            criteriaBuilder.like(rootEntity.get("normalizedFullName"), assumingTextInFirstToken),
            // looking for a prefix after a whitespace:
            criteriaBuilder.like(rootEntity.get("normalizedFullName"), assumingTextInConsecutiveToken)
        );
        orClausesArr[i] = query;
    }

    criteriaQuery = criteriaQuery
            .select(rootEntity) // you can also select only the display columns and ignore the normalized/search columns
            .where(criteriaBuilder.and(orClausesArr))
            .orderBy(
                    criteriaBuilder.desc(rootEntity.get("customerUpdated")),
                    criteriaBuilder.desc(rootEntity.get("customerCreated"))
            );
    try {
        return entityManager
                .createQuery(criteriaQuery)
                .setMaxResults(50)
                .getResultList();
    } catch (NoResultException nre) {
        return new ArrayList<>();
    }
}

1 Ответ

0 голосов
/ 05 декабря 2018

Criteria API, конечно, не предназначен для этого, но его можно использовать для создания предикатов LIKE .

Так что для каждого поискового запроса и каждого столбца, который вы хотите найти, вы создадите что-тонапример:

column like :term + '%'
or column like ' ' + :term + '%'
or column like ',' + :term + '%'
// repeat for all other punctuation marks and forms of whitespace you want to support.

Это создаст ужасно неэффективные запросы!

Я вижу следующие альтернативы:

  1. Использование специфических функций базы данных.Некоторые базы данных имеют некоторые возможности текстового поиска.Если вы можете ограничить свое приложение одной или несколькими базами данных, которые могут работать.

  2. Создайте свой собственный индекс: используйте подходящий токенизатор для анализа столбцов, которые вы хотите найти, и поместите полученные токены вотдельная таблица с обратными ссылками на исходную таблицу.Теперь найдите, какие термины вы ищете.Пока вы выполняете только префиксные поиски, индексы базы данных должны быть в состоянии поддерживать этот разумный уровень эффективности, и его проще поддерживать и более гибким, чем то, что вы можете получить, используя отдельный API Criteria.

...