使用 HQL 急切加载嵌套关联

Posted

技术标签:

【中文标题】使用 HQL 急切加载嵌套关联【英文标题】:Eagerly load nested association using HQL 【发布时间】:2020-10-11 13:44:00 【问题描述】:

我有以下型号:

public class BaseModel 
  List<DataA> lazyCollectionA;
  List<DataB> lazyCollectionB;


public class DataA 
  OtherEntity otherEntity;


public class OtherEntity 
  List<DataC> lazyCollectionC;

当我访问特定页面时,我需要使用所有这些数据。这造成了性能选择 n+1 问题。

我已经通过急切地使用以下方式获取集合来部分解决了这个问题:

List<BaseModel> result = entityManager.createQuery(
    "select m from BaseModel m " +
    "left join fetch m.lazyCollectionA " +
    "where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();

result = entityManager.createQuery(
    "select m from BaseModel m " +
    "left join fetch m.lazyCollectionB " +
    "where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();

请注意,我必须执行 2 个查询而不是仅 1 个,否则我会得到一个 MultipleBagFetchException

但是,我在急切地加载 lazyCollectionA.otherEntity.lazyCollectionC 时遇到了问题。我尝试了几种查询变体来尝试急切地获取结果,但是当访问 otherEntity.lazyCollectionC 时,选择 n+1 问题不断出现。

我认为这应该可行,但不幸的是它不是:

entityManager.createQuery(
    "select a from BaseModel m " +
    "left join m.lazyCollectionA a " +
    "left join fetch a.otherEntity o " +
    "left join fetch o.lazyCollectionC " +
    "where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();

您知道为什么这不起作用吗?

另外,我不完全了解加载 lazyCollectionAlazyCollectionB 的前 2 个查询是如何工作的。我的意思是,由于它们是在不同时间加载的,我希望只有最后一个查询才会有加载的实例。是不是因为hibernate正在缓存结果,所以不需要再次查询数据库?

感谢您提供的任何帮助!

【问题讨论】:

【参考方案1】:

我假设您的模型之间的所有连接都是@OneToMany。在这种情况下,你可以尝试这样的事情:

@Autowired
private EntityManager em;

@Transactional
public List<BaseModel> getAllByThreeQueries() 
    List<Long> ids = Arrays.asList(1L);
    List<BaseModel> first = em.createQuery(
            "select distinct m from BaseModel m " +
                    "left join fetch m.lazyCollectionB " +
                    "where m.id in (:ids) ", BaseModel.class)
            .setParameter("ids", ids)
            .getResultList();
    List<BaseModel> second = em.createQuery(
            "select distinct m from BaseModel m " +
                    "left join fetch m.lazyCollectionA a " +
                    "left join fetch a.otherEntity o " +
                    "where m in (:models) ", BaseModel.class)
            .setParameter("models", first)
            .getResultList();
    em.createQuery("select distinct a from BaseModel m " +
            "left join m.lazyCollectionA a " +
            "left join fetch a.otherEntity o " +
            "left join fetch o.lazyCollectionC " +
            "where m in (:models) ", DataA.class)
            .setParameter("models", second)
            .getResultList();
    return second;

Full code

您知道为什么这不起作用吗?

entityManager.createQuery(
    "select a from BaseModel m " +
    "left join m.lazyCollectionA a " +
    "left join fetch a.otherEntity o " +
    "left join fetch o.lazyCollectionC " +
    "where m.id in (:ids) ", BaseModel.class)
.setParameter("ids", ids)
.getResultList();

因为在这种情况下你会得到一个 MultipleBagFetchException。 You need to do one more request.

【讨论】:

感谢您的贡献!不幸的是,它对我不起作用。用于获取lazyCollectionC 的select n+1 查询仍在执行。我注意到这些查询使用WHERE dataC.otherEntity_id = ?。我试图复制这个 where 子句,但仍然出现 n+1 个查询。 我也尝试访问您发布的完整代码链接,但它给出了 404 错误:/ 对不起,我忘了打开 repo。现在我已经做到了。您可以查看它。 您能否提供有关您的环境的更多信息?例如 - 完整的请求方法、模型之间的关系、类或方法或字段注释?

以上是关于使用 HQL 急切加载嵌套关联的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize嵌套的渴望加载找到所有嵌套关联不匹配的地方

laravel eloquent - 在嵌套急切加载的关系上不使用

带有约束的 Laravel 嵌套急切加载

带有嵌套关系的 Laravel 急切加载

Entity Framework Core 2.0.1 急切加载所有嵌套的相关实体

laravel 嵌套急切加载获取和第一个函数