我正在使用JPA 2.1(将Hibernate 4作为实现)和Spring Data JPA 1.9.0。如何实现全文搜索?
我的情况如下。我有一个User实体,在UI上有一个表,其中显示了大多数用户属性,我希望用户在文本框中输入搜索词并搜索所有属性。
User
我看到2个选项可以做到这一点:
ORs
LIKE % :searchString %
选项1 的性能不好,但是写起来很不错。
选项2 是高性能的,因为在DB端执行但编写起来很麻烦。
现在我正在使用选项1,因为我需要将boolean转换为"yes"/"no"并且还需要一个配置文件枚举,我想在其中按字段描述而不是实际枚举值进行搜索。
"yes"/"no"
在User实体中,我有一个方法,该方法返回要搜索的所有字段,并用空格分隔:
public String getSearchString(){ return StringUtils.join( Arrays.asList( login, firstName, lastName, email, active ? "yes" : "no", profile.getDescription()) , " "); }
我在一项服务中从DB加载所有用户,并通过此搜索字符串进行过滤:
@Override public List<User> getUsers(final String searchText) { final List<User> users = getUsers(); if(StringUtils.isBlank(searchText)){ return users; } CollectionUtils.filter(users, new Predicate<User>() { @Override public boolean evaluate(User object) { return StringUtils.containsIgnoreCase(object.getSearchString(), searchText); } }); return users; }
在JPQL的另一端,我最终遇到这样的查询,我认为这不是实现此功能的最好,最简单的方法。将布尔值转换为“是”和“否”也存在问题。
@Query("SELECT r FROM User r WHERE " + "r.firstname LIKE '%' || :searchString || '%' " + "OR r.lastname LIKE '%' || :searchString || '%' " + "OR r.login LIKE '%' || :searchString || '%' " + "OR r.profile.description LIKE '%' || :searchString || '%' " + "OR r.active LIKE '%' || :searchString || '%' " + "OR r.email LIKE '%' || :searchString || '%'") List<User> selectUsers(@Param("searchString")String searchString, Pageable page);
有没有更好的解决方案来解决这个问题?
通过在每个持久存储上保存搜索字符串并更新到数据库来解决此问题。首先为searchString创建一列:
@Column(name = "SEARCH_STRING", length = 1000) private String searchString;
存储很便宜,DB上的开销并不大。
然后保存更新并继续:
@PreUpdate @PrePersist void updateSearchString() { final String fullSearchString = StringUtils.join(Arrays.asList( login, firstName, lastName, email, Boolean.TRUE.equals(active) ? "tak" : "nie", profile.getDescription()), " "); this.searchString = StringUtils.substring(fullSearchString, 0, 999); }
然后我可以使用以下常规JPQL查询LIKE:
LIKE
SELECT u FROM User u WHERE u.searchString LIKE '%' || :text || '%'
或使用示例查询:
ExampleMatcher matcher = ExampleMatcher.matching(). withMatcher("searchString", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());