【发布时间】:2019-12-09 16:02:11
【问题描述】:
我正在开发一个搜索 api,我需要在其中返回仅包含请求中询问的字段/属性的资源。字段也可以是子元素。例如 - book.author.name 其中 book 是父资源,author 是其下的子资源,可能具有多对一关系. 我在早期版本的休眠(5.x.x)中了解到嵌入式实体不支持投影。 所以想知道6.0有没有这个功能
【问题讨论】:
标签: hibernate-search
我正在开发一个搜索 api,我需要在其中返回仅包含请求中询问的字段/属性的资源。字段也可以是子元素。例如 - book.author.name 其中 book 是父资源,author 是其下的子资源,可能具有多对一关系. 我在早期版本的休眠(5.x.x)中了解到嵌入式实体不支持投影。 所以想知道6.0有没有这个功能
【问题讨论】:
标签: hibernate-search
当只有一个作者时,是的,您可以对作者进行投影(但您已经可以在搜索 5 中进行投影,虽然方式不太方便):
@Entity
@Indexed
class Book {
@Id
private Long id;
@GenericField
private String title;
@ManyToOne
@IndexedEmbedded
private Author author;
// ...getters and setters...
}
@Entity
class Author {
@Id
private Long id;
@GenericField(projectable = Projectable.YES)
private String firstName;
@GenericField(projectable = Projectable.YES)
private String lastName;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
// ...getters and setters...
}
class MyProjectedAuthor {
public final String firstName;
public final String lastName;
public MyProjectedAuthor(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
SearchSession searchSession = Search.session(entityManager);
List<MyProjectedAuthor> projectedAuthors = searchSession.search(Book.class)
.asProjection(f -> f.composite(
MyProjectedAuthor::new,
f.field("author.firstName", String.class),
f.field("author.lastName", String.class),
))
.predicate(f -> f.matchAll())
.fetchHits(20);
目前尚不支持多值投影(例如,如果每本书有多个作者),但我们将在 6.0.0 版本之前进行处理:https://hibernate.atlassian.net/browse/HSEARCH-3391
如果您谈论的是从数据库中加载作者而不是投影,那么还没有这样的内置功能。我们会在联系HSEARCH-3071 时进行调查,但我不知道需要多长时间。
作为一种解决方法,对于单值关联,您可以手动实现加载:
@Entity
@Indexed
class Book {
@Id
private Long id;
@GenericField
private String title;
@ManyToOne
@IndexedEmbedded
private Author author;
// ...getters and setters...
}
@Entity
class Author {
@Id
@GenericField(projectable = Projectable.YES)
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
// ...getters and setters...
}
SearchSession searchSession = Search.session(entityManager);
List<Author> authors = searchSession.search(Book.class)
.asProjection(f -> f.composite(
authorId -> entityManager.getReference(Author.class, authorId),
f.field("author.id", Long.class)
))
.predicate(f -> f.matchAll())
.fetchHits(20);
// Or, for more efficient loading:
SearchSession searchSession = Search.session(entityManager);
List<Long> authorIds = searchSession.search(Book.class)
.asProjection(f -> f.field("author.id", Long.class))
.predicate(f -> f.matchAll())
.fetchHits(20);
List<Author> authors = entityManager.unwrap(Session.class).byMultipleIds(Author.class)
.withBatchSize(20)
.multiLoad(authorIds);
编辑:根据您的评论,您的问题与许多领域有关,而不仅仅是作者。本质上,您关心的是加载尽可能少的关联。
在 Hibernate ORM 中解决此问题的最常见方法是将所有关联的获取模式设置为惰性(无论如何,默认情况下您应该这样做)。
然后在搜索时,甚至不要考虑加载:只需让 Hibernate Search 检索您需要的实体。此时将不会加载关联。
然后,当您将实体序列化为 JSON 时,只会加载您实际使用的关联。如果您正确设置了默认的批量提取大小(使用hibernate.default_batch_fetch_size、here,则性能应该与您使用更复杂的解决方案所获得的性能相当,并且只需一小部分开发时间。
如果您真的想急切地获取一些关联,最简单的解决方案可能是利用 JPA 的实体图:它们告诉 Hibernate ORM 在加载 Book 实体时准确加载哪些关联。
Hibernate Search 6 yet 没有内置功能,但您可以手动完成:
@Entity
@Indexed
class Book {
@Id
private Long id;
@GenericField
private String title;
@ManyToOne // No need for an @IndexedEmbedded with this solution, at least not for loading
private Author author;
// ...getters and setters...
}
@Entity
class Author {
@Id // No need for an indexed ID with this solution, at least not for loading
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
// ...getters and setters...
}
SearchSession searchSession = Search.session(entityManager);
List<Long> bookIds = searchSession.search(Book.class)
.asProjection(f -> f.composite(ref -> (Long)ref.getId(), f.entityReference()))
.predicate(f -> f.matchAll())
.fetchHits(20);
// Note: there are many ways to build a graph, some less verbose than this one.
// See https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#fetching-strategies-dynamic-fetching-entity-graph
javax.persistence.EntityGraph<Book> graph = entityManager.createEntityGraph( Book.class );
graph.addAttributeNode( "author" );
// Ask for the author association to be loaded eagerly
graph.addSubgraph( "author" ).addAttributeNode( "name" );
List<Book> booksWithOnlySomeAssociationsFetched = entityManager.unwrap(Session.class).byMultipleIds(Book.class)
.with(graph, org.hibernate.graph.GraphSemantic.FETCH)
.withBatchSize(20)
.multiLoad(bookIds);
请注意,即使使用此解决方案,您也应该在映射中将获取模式设置为惰性(@OnyToMany,...),以便尽可能多的关联,因为 Hibernate ORM doesn't allow making an eager association lazy though a fetch graph。
【讨论】: