У нас проблема в том, что на данный момент нам не разрешено использовать 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<>();
}
}