[Из песочницы] Conditional indexing. Оптимизируем процесс полнотекстового поиска

61a33e4cf0f54da396353995d0e9541d.jpgВ этой статье я хочу поговорить про интеграцию Apache Lucene и Hibernate Search. Если быть более точным, то про один из механизмов Hibernate Search, который может здорово увеличить производительность на проекте с полнотекстовым поиском.Ни для кого, кто работал с перечисленными выше технологиями, не секрет, что для полнотекстового поиска необходима индексация. Иначе говоря, при добавлении и изменении записей в БД необходимо добавлять/изменять индексы, по которым, собственно, и будет осуществляться полнотекстовый поиск. За данный процесс и отвечает Apache Lucene. А вот как мы уведомляем Люцену, что данную сущность необходимо индексировать:

@Entity @Indexed public class SomeEntity { @Id @GeneratedValue private Integer id;

@Field private String indexedField;

private String unindexedField;

//getters and setters } В приведенном выше классе аннотация @Indexed говорит о том, что данная сущность индексируется Люценой. Аннотация @Field указывает, какие именно поля будут индексироваться. Т.к. аннотация @Field надвешена только над полем indexedField, это значит, что мы сможем осуществлять полнотекстовый поиск только по этому полю.Примечание. Для нормального функционирования Люцены необходимы и другие настройки кроме данных аннотаций. Но так как статья посвящена не настройке Люцены в целом, а лишь оптимизации процесса индексирования, то эти подробности мы опустим.

Теперь давайте рассмотрим пример индексации некоторой сущности. Предположим, что у нас есть сайт объявлений. А вот и наша сущность:

@Entity public class Ad { @Id @GeneratedValue private Integer id;

private String text;

private AdStatus status;

//getters and setters } Мы хотим предоставить нашим пользователям возможность полнотекстового поиска по всем объявлениям сайта. Для этого добавляем соответствующие аннотации: @Entity @Indexed public class Ad { @Id @GeneratedValue private Integer id;

@Field private String text;

private AdStatus status;

//getters and setters } Теперь самое время упомянуть, что у объявления может быть один из следующих статусов: DRAFT, ACTIVE, ARCHIVE. После недолгого раздумья мы приходим к решению, что пользователям в результатах поиска необходимо отображать только объявления в статусе ACTIVE. Рассмотрим два варианта решения данной проблемы. Первый — в лоб. Добавляем аннотацию @Field над полем status. И каждый раз при поиске добавляем predicate, который и будет указывать, каким должен быть этот статус. Минусы данного решения: ощутимое падение производительности при большом количестве объявлений в статусе ARCHIVE и DRAFT, излишняя индексация сущностей, по которым уже не будет проводиться поиск.Тут же в голову приходит другое решение — не индексировать/удалять существующие индексы для объявлений во всех статусах кроме ACTIVE. В этом нам и поможет такой механизм, как interceptors. Сначала поставим задачу. Мы хотим, чтобы при изменении сущности индексация производилась в зависимости от нового статуса объявления. Теперь приступаем к реализации. Создаем класс AdIndexInterceptor, который реализует интерфейс EntityIndexingInterceptor:

public class AdIndexInterceptor implements EntityIndexingInterceptor { @Override public IndexingOverride onAdd (Ad entity) { if (entity.getStatus () == AdStatus.ACTIVE) { return IndexingOverride.APPLY_DEFAULT; } return IndexingOverride.SKIP; }

@Override public IndexingOverride onUpdate (Ad entity) { if (entity.getStatus () == AdStatus.ACTIVE) { return IndexingOverride.UPDATE; } return IndexingOverride.REMOVE; }

@Override public IndexingOverride onDelete (Ad entity) { return IndexingOverride.APPLY_DEFAULT; }

@Override public IndexingOverride onCollectionUpdate (Ad entity) { return onUpdate (entity); } } Как видно выше, в классе должно быть реализовано 4 метода, которые будут вызываться при добавлении записи, редактировании записи, удалении и обновлении коллекции записей соответственно. Каждый из этих методов должен вернуть одно из значений IndexingOverride, который в свою очередь является enum. Всего имеется четыре значения данного enum. Распишу, что происходит при возврате каждого из них: APPLY_DEFAULT — процесс индексации продолжается так, как бы он проходил при отсутствии interceptor«a. SKIP — индексация не происходит. UPDATE — обновляется существующий индекс. REMOVE — удаляется существующий индекс, новый не создается. Теперь вернемся к классу сущности. Для того, чтобы Люцена знала, что перед индексацией необходимо вызвать соответствующие методы interceptor«a, добавляем в аннотацию @Indexed над сущностью атрибут interceptor: @Entity @Indexed (interceptor = AdIndexingInterceptor.class) public class Ad { @Id @GeneratedValue private Integer id;

@Field private String text;

private AdStatus status;

//getters and setters } Осталось только корректно задокументировать использование данного interceptor«a, чтобы поведение Люцены было ожидаемым и для ваших коллег по команде.P.S. В официальной документации разработчики указывают, что данная фича является экспериментальной и ее функционирование может измениться в зависимости от обратной связи с пользователями.

Ссылка на официальную документацию.

© Habrahabr.ru