Spring Boot 使用hibernate criteriabuilder进行连接的多查询

hivapdat  于 2023-05-06  发布在  Spring
关注(0)|答案(1)|浏览(195)

我想使用hibernate criteria API执行一个连接查询,并显式地Map一些连接字段。这是非常简单的,所以我的代码导致:

CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<Country> criteria = builder.createQuery(Country.class);
        Root<Country> root = criteria.from(Country.class);
        Join<Country, Translation> join = root.join("translatedName");
        criteria.multiselect(root, join.get("fr"));
        TypedQuery<Country> tq = session.createQuery(criteria);
        List<Country> countries = tq.getResultList();

国家实体

@Entity
@Table(name = "countries")
public class Country {

    @Id
    @Column
    private UUID id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "name_id")
    private Translation translatedName;

    @Transient
    private String name;

    public Country(Country c, String name) {
        this.id = c.id;
        this.name = name;
    }

//getters...
}

翻译实体

package database.models;

import service.model.Country;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "translations")
public class Translation {
    @Id
    private String id;
    private String fr;
    private String en;
// getters/setters
}

这正是我想要的。Multiselect语句将联接字段传递给构造函数,然后将其存储在正确的变量中。
然而,我遇到了臭名昭著的N+1选择问题:

Hibernate: select country0_.id as col_0_0_, translatio1_.id as col_1_0_, translatio1_.fr as col_2_0_ from countries country0_ inner join translations translatio1_ on country0_.name_id=translatio1_.id
Hibernate: select country0_.id as id1_0_0_, country0_.name_id as name_id2_0_0_ from countries country0_ where country0_.id=?
...

经过一些研究,我发现使用fetch语句而不是join可以解决这个问题。更新代码:

CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<Country> criteria = builder.createQuery(Country.class);
        Root<Country> root = criteria.from(Country.class);
        Join<Country, Translation> join = (Join)root.fetch("translatedName");
        criteria.multiselect(root, join.get("fr"));
        TypedQuery<Country> tq = session.createQuery(criteria);
        List<Country> countries = tq.getResultList();

查询失败,并出现以下(众所周知的)QueryException:

Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=service.model.Country.translatedName,tableName=translations,tableAlias=translatio1_,origin=countries country0_,columns={country0_.name_id ,className=database.models.Translation}}]

我已经明白,我用来Map我的自定义字段的DTO投影和连接获取是不兼容的。
为了记录在案,我甚至不需要获取translatedName关系。我只想提取正确的语言并将其Map到我的实体中。
先谢谢你了。有什么建议吗
这里的主要目的是在我所在国家的字段name中填充翻译,并将其直接返回给用户,而不是在嵌套字段中包含所有可用的翻译。例如,如果我收到一个lang=fr的查询,我想返回:

{
  "name": "fr_name"
}

而不是

{
  "name": {
    "fr": "fr_name",
    "en": "en_name"
  }
}

而且我不希望由于性能原因而必须遍历结果集来手动Map它

mznpcxlj

mznpcxlj1#

by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=service.model.Country.translatedName,tableName=translations,tableAlias=translatio1_,origin=countries country0_,columns={country0_.name_id ,className=database.models.Translation}}]

在Hibernate中,当您在查询中使用选择联接来承载实体,但不包括包含关系的查询实体时,可能会发生此错误。
例如,如果我们有一个Order和Address实体与@ManyToOne成员资格相关联,并且我们希望在执行查询以获取订单时使用fetch join来放置地址,那么我们可以编写以下代码:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Order> query = cb.createQuery(Order.class);

Root<Order> orderRoot = query.from(Order.class);
Join<Order, Address> addressJoin = orderRoot.join("address", JoinType.LEFT);
orderRoot.fetch("address", JoinType.LEFT);

List<Order> orders = entityManager.createQuery(query).getResultList();

但是,如果我们没有在选择列表中包含Order实体,我们将得到错误“query specified join fetching,but the owner of the fetched association was not present in the select list”。
要修复此错误,我们需要在查询的选择列表中包含Order实体。例如,我们可以这样修改代码:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);

Root<Order> orderRoot = query.from(Order.class);
Join<Order, Address> addressJoin = orderRoot.join("address", JoinType.LEFT);
orderRoot.fetch("address", JoinType.LEFT);

query.select(cb.array(orderRoot, addressJoin));

List<Object[]> resultList = entityManager.createQuery(query).getResultList();

在这段代码中,我们使用select方法和cb.array()方法在同一个请求中同时选择Order和Address,这将创建一个包含所选对象的对象数组。但我仍然不确定如何实施替换

相关问题