我们有一个问题,目前不允许使用ElasticSearch,因此我们需要使用MySQL实现搜索功能。一个理想的功能是前缀的标记化搜索,因此像
“敏捷的棕色狐狸跳过了懒狗” 搜索“跳转”时可以找到。我想我需要定义一个规则,例如(pseudocode):
(*)(beginning OR whitespace)(prefix)(*)
我认为使用JPA(标准API)可以做到这一点吗?但是,如果我们有两个条件呢?所有这些都必须由AND合并,例如上述规则应至少在一列中对两个术语得出TRUE。这意味着“跳狐”将导致命中,而“跳兔”则不会。使用Criteria API也可以吗?
或者您知道比Criteria API更好的解决方案吗?我听说Hibernate可以更优雅地执行LIKE查询(使用更少的代码),但是不幸的是我们使用EclipseLink。
基于以下答案,这是我的完整解决方案。这是一种简单的方法(不过“简单的JPA条件API”是矛盾的)。如果有人想使用它,请考虑进行一些重构
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<>();
}
}
答案 0 :(得分:0)
Criteria API当然不打算用于此目的,但可以用于create LIKE predicates。
因此,对于每个搜索词和要搜索的每个列,您都将创建如下内容:
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.
这将导致效率低下的查询!
我看到以下选择:
使用特定于数据库的功能。一些数据库具有某些文本搜索功能。 如果您可以将应用程序限制为一个或几个可能有效的数据库。
创建您自己的索引:使用适当的令牌生成器分析要搜索的列,并将生成的令牌放置在具有对原始表的反向引用的单独表中。 现在搜索所需的术语。 只要您只进行前缀搜索,数据库索引就应该能够保持这种合理的效率,并且比单独使用Criteria API所获得的索引更易于维护和更灵活。