Hibernate Search 6.0 Migration

自作のアプリのマイグレーションする過程で問題になった個所を纏めておく。

Hibernate Search 6の変更点

APIが大幅に変わっているため、コード修正なしに移行は不可。APIをElasticsearchに最適化したそうなので、Luceneを使用してきたがこれを機にElasticsearchに変更することを検討しても良いかも知れない。

インデックスの再作成
// Hibernate5
FullTextEntityManager txtentityManager = Search.getFullTextEntityManager(entityManager);
MassIndexer massIndexer = txtentityManager.createIndexer();

// Hibernate6
SearchSession searchSession = Search.session(entityManager);
MassIndexer massIndexer = searchSession.massIndexer();

// 共通
massIndexer.start()
ファセットの作成

全く同じように移行は出来なかった。5の場合は戻り値がList<Facet>で、6の場合はMap<String, Long>になる。Stringの部分はフィールドの属性毎に変更する必要がある。

// Hibernate5
FullTextEntityManager txtentityManager = Search.getFullTextEntityManager(entityManager);
SearchFactory searchFactory = txtentityManager.getSearchFactory();
QueryBuilder builder = searchFactory.buildQueryBuilder().forEntity(searchedEntity).get();
FacetingRequest categoryFacetingRequest = builder.facet()
    .name(field + searchedEntity.getSimpleName()).onField(field).discrete()
    .orderedBy(FacetSortOrder.COUNT_DESC).includeZeroCounts(false).maxFacetCount(maxCount)
    .createFacetingRequest();

Query luceneQuery = builder.all().createQuery();
FullTextQuery fullTextQuery = txtentityManager.createFullTextQuery(luceneQuery);
FacetManager facetManager = fullTextQuery.getFacetManager();
facetManager.enableFaceting(categoryFacetingRequest);

return facetManager.getFacets(field + searchedEntity.getSimpleName());

// Hibernate6
SearchSession searchSession = Search.session(entityManager);
AggregationKey<Map<String, Long>> countByKey = AggregationKey.of(field);

SearchResult<?> result = searchSession.search(User.class)
    .where(f -> f.matchAll())
    .aggregation(countByKey, f -> f.terms()
        .field(field, String.class)
        .orderByCountDescending()
        .minDocumentCount(1)
        .maxTermCount(maxCount))
    .fetch(20);

result.hits();

return result.aggregation(countByKey);
アナライザーの変更

@Analyzer(impl = JapaneseAnalyzer.class)はなくなり、@FullTextField(analyzer = “japanese”)の様にでフィールド毎に指定する必要がある。しかも、カスタムアナライザーを別途自分で定義する必要がある。@NormalizerDefも使用出来なくなっているため、アノテーションで細かく処理を指定出来なくなっている。

public class CustomLuceneAnalysisConfigurer implements LuceneAnalysisConfigurer {

    @Override
    public void configure(LuceneAnalysisConfigurationContext context) {
        context.analyzer("japanese").instance(new JapaneseAnalyzer());
    }
}
hibernate.search.backend.analysis.configurer = <パッケージ>.CustomLuceneAnalysisConfigurer

LuceneAnalysisConfigurerを定義して、以下の様に指定する。

@FullTextField(analyzer = "japanese")
private String text;
FullTextQueryの廃止

今までは全文検索用クエリ(FullTextQuery)を作成するためにluceneを使用する場面があったが、それらはバックエンドに移動したから、今後は使用しないようにということか。luceneのQueryを使用しなくても検索ロジックを記述することが出来るようになった。

Search.session(entityManager).search(User.class)
        .where(f -> {
            if (userSearchCriteria.getUsername() == null && userSearchCriteria.getEmail() == null) {
                return f.matchAll();
            } else {
                return f.bool(b -> {
                    if (userSearchCriteria.getUsername() != null) {
                        b.should(f.match().field(UserSearchCriteria.USERNAME_FIELD)
                                .matching(userSearchCriteria.getUsername()));
                    }
                    if (userSearchCriteria.getEmail() != null) {
                        b.should(f.match().field(UserSearchCriteria.EMAIL_FIELD)
                                .matching(userSearchCriteria.getEmail()));
                    }
                });
            }
        })
        .sort(f -> f.field(UserSearchCriteria.USERNAME_FIELD + "Sort"))
        .fetchHits(Long.valueOf(pageRequest.getOffset()).intValue(), pageRequest.getPageSize());
起動設定の変更
// Hibernate5
<prop key="hibernate.search.lucene_version">LUCENE_CURRENT</prop>
<prop key="hibernate.search.default.directory_provider">filesystem</prop>
<prop key="hibernate.search.default.locking_strategy">simple</prop>
<prop key="hibernate.search.default.exclusive_index_use">true</prop>
<prop key="hibernate.search.default.indexBase">${hibernate.search.indexBase}</prop>

// Hibernate6
<prop key="hibernate.search.backend.lucene_version">LATEST</prop>
<prop key="hibernate.search.backend.directory.type">local-filesystem</prop>
<prop key="hibernate.search.backend.directory.root">${hibernate.search.indexBase}</prop>
<prop key="hibernate.search.backend.analysis.configurer">common.dao.impl.CustomLuceneAnalysisConfigurer</prop>

Beanアノテーションの変更
// Hibernate5
@Field
@Field(name = "usernameSort", normalizer = @Normalizer(definition = "userSort"))
@SortableField(forField = "usernameSort")
private String username;

@Field(name = "firstNameFacet", analyze = Analyze.NO)
@Facet(forField = "firstNameFacet")
private String firstName;

// Hibernate6
@FullTextField(analyzer = "japanese")
@KeywordField(name = "usernameSort", sortable = Sortable.YES)
private String username;

@KeywordField(name = "firstNameFacet", aggregable = Aggregable.YES)
private String firstName;
Spring Boot

マニュアル通り下記を追加すると、エラーが発生してしまう。まだ、対応していないのかもしれない。
Caused by: org.hibernate.search.util.common.AssertionFailure: Unexpected duplicate key: enabled — this may indicate a bug or a missing test in Hibernate Search. Please report it: https://hibernate.org/community/

implementation 'org.apache.lucene:lucene-analyzers-kuromoji:8.7.0'
implementation 'org.hibernate.search:hibernate-search-mapper-orm:6.0.0.Final'
implementation 'org.hibernate.search:hibernate-search-backend-lucene:6.0.0.Final'

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください