如何使用 Hibernate Search 以单向 ManyToMany 和 ManyToOne 关系加入数据

How to join data in unidirectional ManyToMany and ManyToOne relation with Hibernate Search

我是 Hibernate Search 的新手,我正在使用 Hibernate Search 6.1.4.Final 以及 Spring Boot 2.6.7、Spring Data JPA 2.6.7和 PostgreSQL 14。我有以下架构:

CREATE TABLE manufacturer
(
    id         UUID,
    PRIMARY KEY (id)
);

CREATE TABLE flavor
(
    id         UUID,
    PRIMARY KEY (id)
);

CREATE TABLE tobacco
(
    id              UUID,
    name            VARCHAR   NOT NULL,
    manufacturer_id UUID      NOT NULL,
    PRIMARY KEY (id)
);

ALTER TABLE tobacco
    ADD CONSTRAINT fk_manufacturer FOREIGN KEY (manufacturer_id) REFERENCES manufacturer (id);

CREATE TABLE tobacco_flavor
(
    tobacco_id UUID REFERENCES tobacco (id) ON UPDATE CASCADE ON DELETE CASCADE,
    flavor_id  UUID REFERENCES flavor (id) ON UPDATE CASCADE,
    CONSTRAINT tobacco_flavor_pk PRIMARY KEY (tobacco_id, flavor_id)
);

我在 Tobacco 和 Manufacturer 之间有一个单向的 ManyToOne 关系,在 Tobacco 和 Flavor 之间有另一个单向的 ManyToMany 关系。索引实体是:

@Data
@Entity
@Indexed
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@EqualsAndHashCode(of = "id")
@EntityListeners(AuditingEntityListener.class)
public class Tobacco {
    @Id
    @GeneratedValue
    private UUID id;
    @NotBlank
    @FullTextField(analyzer = "name")
    @FullTextField(name = "name_prefix", analyzer = "name_prefix", searchAnalyzer = "name")
    private String name;
    @ManyToOne(fetch = FetchType.EAGER) // this is EAGER because hibernate cannot serialize the proxy wrapper when using LAZY
    @JoinColumn(name = "manufacturer_id", nullable = false)
    private Manufacturer manufacturer;
    @ManyToMany(cascade = {
        CascadeType.MERGE
    })
    @JoinTable(name = "tobacco_flavor",
        joinColumns = @JoinColumn(name = "tobacco_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "flavor_id", referencedColumnName = "id")
    )
    private Set<Flavor> flavors;
}

我正在使用以下 lucene 配置器:

@Component("luceneTobaccoAnalysisConfigurer")
public class LuceneTobaccoAnalysisConfigurer implements LuceneAnalysisConfigurer {
    @Override
    public void configure(LuceneAnalysisConfigurationContext context) {
        context.analyzer("name").custom()
            .tokenizer("standard")
            .tokenFilter("lowercase")
            .tokenFilter("asciiFolding");

        context.analyzer("name_prefix").custom()
            .tokenizer("standard")
            .tokenFilter("lowercase")
            .tokenFilter("asciiFolding")
            .tokenFilter("edgeNGram")
            .param("minGramSize", "2")
            .param("maxGramSize", "7");
    }
}

我正在执行以下查询:

public List<Tobacco> find(String query) {
    return Search.session(entityManager)
        .search(Tobacco.class)
        .where(f -> f.match()
            .fields("barcode", "name").boost(2.0f)
            .fields("name_prefix")
            .matching(query)
            .fuzzy()
        )
        .fetchHits(10);
}

当我执行查询时,Hibernate 使用连接执行 5 个 SELECT 个查询,而不是 1 个。我想始终按烟草名称查询并检索与其关联的所有实体(口味和制造商),是否有任何方法可以指示 Hibernate Search 通过连接有效地执行查询?

要控制 Hibernate Search 加载的内容,您可以在构建搜索查询时通过调用 .loading( o -> o.graph( someGraph, GraphSemantic.FETCH ) ) 来利用 JPA entity graphs in Hibernate Search

是这样的吗?

public List<Tobacco> find(String query) {
    EntityGraph<Tobacco> graph = entityManager.createEntityGraph( Tobacco.class ); 
    graph.addAttributeNodes( "manufacturer" );
    graph.addAttributeNodes( "flavors" );
    return Search.session(entityManager)
        .search(Tobacco.class)
        .where(f -> f.match()
            .fields("barcode", "name").boost(2.0f)
            .fields("name_prefix")
            .matching(query)
            .fuzzy()
        )
        .loading( o -> o.graph( graph, GraphSemantic.FETCH ) ) 
        .fetchHits(10);
}

但是,根据您的模型和实体图,这可能会导致执行多个查询(由于 Hibernate ORM 中的实现限制)。在这种情况下,您可以探索这些将影响任何加载的替代解决方案,而不仅仅是在 Hibernate Search 中加载: