Как реализовать простой полнотекстовый поиск в JPA (Spring Data JPA)?
Я использую JPA 2.1 (Hibernate 4 как impl) и Spring Data JPA 1.9.0. Как реализовать полнотекстовый поиск?
мой сценарий выглядит следующим образом. У меня есть User
entity и на UI a есть таблица, в которой отображаются большинство свойств пользователей, и я хочу, чтобы пользователь дал текстовое поле, введите там поисковый запрос и поиск во всех свойствах.
Я вижу 2 варианта:
- загрузите всех пользователей пользователей из БД и отфильтруйте их на Java
- напишите запрос JPQL со многими
ORs
иLIKE % :searchString %
1 это не хорошо для производительности, но довольно приятно писать.
2 является performant beacuse выполняется на стороне БД, но громоздко писать.
прямо сейчас я подаю в суд Вариант 1, потому что мне нужно перевести boolean в "yes"/"no"
а также есть перечисление профиля, где я хочу искать по его описанию поля, а не по фактическому значению перечисления.
в сущности пользователя у меня есть метод, который возвращает все поля, которые я хочу искать отдельно пробелами:
public String getSearchString(){
return StringUtils.join(
Arrays.asList(
login,
firstName,
lastName,
email,
active ? "yes" : "no",
profile.getDescription())
, " ");
}
в сервисе я загружаю всех пользователей из БД и фильтрую по этой строке поиска:
@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 я заканчиваю с такими запросами, которые я не думаю, что это самый приятный и простой способ реализовать эту функциональность. Также существует проблема с переводом boolean на " да " и "нет."
@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);
есть ли лучшее решение этой проблемы?
2 ответов
решил это, сохранив строку поиска при каждом сохранении и обновлении в БД. Сначала был создан столбец для строки поиска:
@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
:
SELECT u FROM User u WHERE u.searchString LIKE '%' || :text || '%'
или через Запрос По Примеру:
ExampleMatcher matcher = ExampleMatcher.matching().
withMatcher("searchString", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());
Ну, в большинстве случаев, 1 - это не совсем альтернатива. Если ваше приложение вырастет из регистров, проблемы с памятью и производительностью поразят вас через некоторое время. Я вам гарантирую.
Я не вижу проблемы в 2. Не мощный, но это просто понять.
Если производительность базы данных является проблемой, вы можете создать собственный запрос для вызова собственной функции базы данных для выполнения полнотекстового поиска. Не будет JPQL запрос, но для такого рода запросов вы пытаетесь сделать это решение может быть использовано.