在 Hibernate Search 中输入单个 return

Single return type in Hibernate Search

假设我有一个包含许多不同实体的应用,它们之间没有关系。

我想创建一个查询所有这些的搜索,但是 returns 一个统一的类型,即:

class SearchResult {
  String stype;
  String title;
  String teaser;
}

所以我的想法是索引实体并将它们的值放入一个索引(具有相同的索引字段):

@Indexed(index = "idx_search")
class Book {
  @Field(name = "stype", analyze = ...)
  final String stype = "BOOK";
  @Field(name = "title", analyze = ...)
  String bookTitle;
  @Field(name = "teaser", analyze = ...)
  String bookBlurb ;
}

@Indexed(index = "idx_search")
class Person{
  @Field(name = "stype", analyze = ...)
  final String stype = "PERSON";
  @Field(name = "title", analyze = ...)
  String fullname;
  @Field(name = "teaser", analyze = ...)
  String profileIntroText;
}

@Indexed(index = "idx_search")
class Location{
  @Field(name = "stype", analyze = ...)
  final String stype = "LOCATION";      
  @Field(name = "title", analyze = ...)
  String streetPcAndCity;
  @Field(name = "teaser", analyze = ...)
  String wikiIntoText;
}

如您所见,索引名称和字段名称在所有实体上都是相同的。

现在我想查询他们得到这样的结果:

SearchResult[stype: PERSON, title: Spongebob, teaser: A funny sponge]
SearchResult[stype: BOOK, title: Clean Architecture , teaser: A Craftsmans Guide to Software...]
SearchResult[stype: PERSON, title: Patric, teaser: A funny seastar]
SearchResult[stype: LOCATION, title: Hannover, teaser: A city in Germany]

所以 SearchResult 不是一个实体,而只是将结果合并为一个类型。索引有效,但我必须在搜索时将实体类型传递到查询和 QueryBuilder 中:

final QueryBuilder queryBuilder = fullTextEntityManager
            .getSearchFactory()
            .buildQueryBuilder()
            .forEntity(SearchResult.class)
            .get();
...

Hibernate 然后 returns 这个错误信息:

HSEARCH000331: Can't build query for type 'SearchResult' which is neither configured nor has any configured sub-types.

你认为有办法让它发挥作用吗?

请注意,您不需要将每种类型分配给相同的索引; Hibernate Search 完全能够在单个查询中搜索多个索引。并且性能可能是相同的(无论如何,Lucene 索引通常在引擎盖下分成多个段)。

话虽这么说,假设 SearchResult:

中有一个构造函数,您可以这样做
class SearchResult {
  String stype;
  String title;
  String teaser;

  public SearchResult(String stype, String title, String teaser) {
    this.stype = stype;
    this.title = title;
    this.teaser = teaser;
  }
}

将您的字段标记为已存储:

@Indexed
class Book {
  @Field(name = "stype", store = Store.YES, analyze = ...)
  final String stype = "BOOK";
  @Field(name = "title", store = Store.YES, analyze = ...)
  String bookTitle;
  @Field(name = "teaser", store = Store.YES, analyze = ...)
  String bookBlurb ;
}

@Indexed
class Person{
  @Field(name = "stype", store = Store.YES, analyze = ...)
  final String stype = "PERSON";
  @Field(name = "title", store = Store.YES, analyze = ...)
  String fullname;
  @Field(name = "teaser", store = Store.YES, analyze = ...)
  String profileIntroText;
}

@Indexed
class Location{
  @Field(name = "stype", store = Store.YES, analyze = ...)
  final String stype = "LOCATION";      
  @Field(name = "title", store = Store.YES, analyze = ...)
  String streetPcAndCity;
  @Field(name = "teaser", store = Store.YES, analyze = ...)
  String wikiIntoText;
}

然后这样查询:

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager( entityManager);
final QueryBuilder queryBuilder = fullTextEntityManager
            .getSearchFactory()
            .buildQueryBuilder()
            .forEntity(Book.class)
            .get();

Query luceneQuery = ...;

FullTextQuery query = fullTextEntityManager.createFullTextQuery(query, Book.class, Person.class, Location.class);
query.setProjection("stype", "title", "teaser");
query.setMaxResults(20);

List<Object[]> arrayResults = query.list();

List<SearchResult> hits = new ArrayList<>();
for (Object[] array : arrayResults) {
    hits.add(new SearchResult((String) array[0], (String) array[1], (String) array[2]);
}

另请注意,如果您upgraded to Hibernate Search 6.

,这可能会明显减少尴尬

您需要对映射进行一些更改:

@Indexed
class Book {
  @KeywordField(name = "stype", projectable = Projectable.YES)
  @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
  final String stype = "BOOK";
  @FullTextField(name = "title", projectable = Projectable.YES, analyzer = ...)
  String bookTitle;
  @FullTextField(name = "teaser", projectable = Projectable.YES, analyzer = ...)
  String bookBlurb ;
}

@Indexed
class Person{
  @KeywordField(name = "stype", projectable = Projectable.YES)
  @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
  final String stype = "PERSON";
  @FullTextField(name = "title", projectable = Projectable.YES, analyzer = ...)
  String fullname;
  @FullTextField(name = "teaser", projectable = Projectable.YES, analyzer = ...)
  String profileIntroText;
}

@Indexed
class Location{
  @KeywordField(name = "stype", projectable = Projectable.YES)
  @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
  final String stype = "LOCATION";      
  @FullTextField(name = "title", projectable = Projectable.YES, analyzer = ...)
  String streetPcAndCity;
  @FullTextField(name = "teaser", projectable = Projectable.YES, analyzer = ...)
  String wikiIntoText;
}

但后来我认为搜索时的改进是值得的:

List<SearchResult> hits = Search.session(entityManager)
        .search(Book.class, Person.class, Location.class)
        .select(f -> f.composite(
                SearchResult::new,
                f.field("stype", String.class),
                f.field("title", String.class),
                f.field("teaser", String.class)))
        .where(f -> ...)
        .fetchHits( 20 );

仍在 Hibernate Search 6 中(尽管我相信您至少需要 6.1),您甚至可以使用接口:

interface Searchable {
  @KeywordField(projectable = Projectable.YES, analyzer = ...)
  String getStype();
  @FullTextField(projectable = Projectable.YES, analyzer = ...)
  String getTitle();
  @FullTextField(projectable = Projectable.YES, analyzer = ...)
  String getTeaser();
}

@Indexed
class Book implements Searchable {
  String bookTitle;
  String bookBlurb;

  @Override
  @javax.persistence.Transient
  @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
  String getStype() {
      return "BOOK";
  }
  @Override
  @javax.persistence.Transient
  @IndexingDependency(derivedFrom = @ObjectPath(
          @PropertyValue(propertyName = "bookTitle")
  ))
  String getTitle() {
      return bookTitle;
  }
  @Override
  @javax.persistence.Transient
  @IndexingDependency(derivedFrom = @ObjectPath(
          @PropertyValue(propertyName = "bookTitle")
  ))
  String getTeaser() {
      return bookBlurb;
  }
}

@Indexed
class Person implements Searchable {
  String fullname;
  String profileIntroText;

  @Override
  @javax.persistence.Transient
  @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
  String getStype() {
      return "PERSON";
  }
  @Override
  @javax.persistence.Transient
  @IndexingDependency(derivedFrom = @ObjectPath(
          @PropertyValue(propertyName = "bookTitle")
  ))
  String getTitle() {
      return bookTitle;
  }
  @Override
  @javax.persistence.Transient
  @IndexingDependency(derivedFrom = @ObjectPath(
          @PropertyValue(propertyName = "bookTitle")
  ))
  String getTeaser() {
      return bookBlurb;
  }
}

@Indexed
class Location implements Searchable {
  String streetPcAndCity;
  String wikiIntoText;

  @Override
  @javax.persistence.Transient
  @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
  String getStype() {
      return "LOCATION";
  }
  @Override
  @javax.persistence.Transient
  @IndexingDependency(derivedFrom = @ObjectPath(
          @PropertyValue(propertyName = "streetPcAndCity")
  ))
  String getTitle() {
      return streetPcAndCity;
  }
  @Override
  @javax.persistence.Transient
  @IndexingDependency(derivedFrom = @ObjectPath(
          @PropertyValue(propertyName = "wikiIntoText")
  ))
  String getTeaser() {
      return wikiIntoText;
  }
}

那么你可以这样搜索:

List<SearchResult> hits = Search.session(entityManager)
        .search(Searchable.class)
        .select(f -> f.composite(
                SearchResult::new,
                f.field("stype", String.class),
                f.field("title", String.class),
                f.field("teaser", String.class)))
        .where(f -> ...)
        .fetchHits( 20 );

或者,您可以直接加载实体:

List<Searchable> hits = Search.session(entityManager)
        .search(Searchable.class)
        .where(f -> ...)
        .fetchHits( 20 );

for (Searchable hit : hits) {
    String stype = hit.getStype();
    String title = hit.getTitle();
    String teaser = hit.getTeaser();
    if ( hit instanceof Book ) {
        ...
    }
    else if ( hit instanceof Location ) {
        ...
    }
    else { 
        ...
    } 
}