假设我有一个有很多不同实体的应用程序,它们之间没有关系。
我想创建一个查询所有这些内容的搜索,但是返回一个统一的类型,即:
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返回以下错误消息:
HSEARCH000331: Can't build query for type 'SearchResult' which is neither configured nor has any configured sub-types.
你认为这有办法让这件事成功吗?
发布于 2021-10-27 09:46:06
注意,您不需要将每个类型分配给相同的索引;Hibernate搜索完全能够在一个查询中通过多个索引进行搜索。而且性能可能是相同的(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;
}
}我将从Hibernate搜索6开始,因为如果您使用升级,这可能会大大减少尴尬。您将在下面的Hibernate搜索5中找到一个旧的答案。
对于Hibernate搜索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(Arrays.asList(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 {
...
}
}从Hibernate搜索6.2开始,通过用@ProjectionConstructor (和编译器标志)注释投影类,可以使投影更加简单:
class SearchResult {
String stype;
String title;
String teaser;
@ProjectionConstructor
public SearchResult(String stype, String title, String teaser) {
this.stype = stype;
this.title = title;
this.teaser = teaser;
}
}然后你就可以这样搜索:
List<SearchResult> hits = Search.session(entityManager)
.search(Searchable.class)
.select(SearchResult.class)
.where(f -> ...)
.fetchHits( 20 );Hibernate搜索5的旧答案:
将您的字段标记为存储:
@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]);
}https://stackoverflow.com/questions/69735853
复制相似问题