休眠搜索-6.0版本是否支持对嵌入式实体的投影?

时间:2019-12-04 21:05:41

标签: hibernate-search

我正在开发一个搜索API,在该API中,我仅需要返回请求中的字段/属性,以返回资源。字段也可以是子元素。例如- book.author.name ,其中 book 是父资源, author 是其下的子资源,可能具有多对一关系。 我在较早版本的休眠(5.x.x)投影中了解到嵌入式实体不支持投影。 因此想知道是否在6.0中添加了此功能

1 个答案:

答案 0 :(得分:0)

只有一位作者时,可以,您可以对作者进行投影(但是您可能已经在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_sizehere,则在开发时间的一小部分,性能应该可以与使用更复杂的解决方案所达到的性能相媲美。

如果您真的想急切地获取某些关联,最简单的解决方案可能是利用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