使用HQL主动加载嵌套关联

huangapple go评论67阅读模式
英文:

Eagerly load nested association using HQL

问题

我有以下的模型:

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 的前两个查询是如何工作的。我的意思是,由于它们在不同的时间加载,我会期望只有最后一个查询会有加载后的实例。是因为 Hibernate 在缓存结果,因此不需要再次查询数据库吗?

感谢您能提供的任何帮助!

英文:

I have the following model:

public class BaseModel {
  List&lt;DataA&gt; lazyCollectionA;
  List&lt;DataB&gt; lazyCollectionB;
}

public class DataA {
  OtherEntity otherEntity;
}

public class OtherEntity {
  List&lt;DataC&gt; lazyCollectionC;
}

When I visit a particular page I need to use all this data. This is creating a performance select n+1 problem.

I already partly solved the issue by eagerly fetching the collections using:

List&lt;BaseModel&gt; result = entityManager.createQuery(
    &quot;select m from BaseModel m &quot; +
    &quot;left join fetch m.lazyCollectionA &quot; +
    &quot;where m.id in (:ids) &quot;, BaseModel.class)
.setParameter(&quot;ids&quot;, ids)
.getResultList();

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

Note that I had to perform 2 queries instead of only 1 because otherwise I would get a MultipleBagFetchException.

However, I'm having problems eagerly loading lazyCollectionA.otherEntity.lazyCollectionC. I tried several variations of the query to try to eagerly fetch the results, but when otherEntity.lazyCollectionC is accessed, the select n+1 problem keeps surfacing.

I think this should work, but unfortunately it is not:

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

Do you have any ideas why this is not working?

Also, I don't fully understand how my first 2 queries to load lazyCollectionA and lazyCollectionB are working. I mean, since they are loaded at different times, I would expect that only the last query would have the loaded instances. Is it because hibernate is caching the results and therefore it does not need to query the database again?

Thanks for any help you can provide!

答案1

得分: 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;
}

你有任何想法为什么这不起作用吗?

因为在这种情况下会产生MultipleBagFetchException。你需要再做一次请求。

英文:

I assume that all connections between your models are @OneToMany. In this case you could try simething like this:

@Autowired
private EntityManager em;

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

Full code

> Do you have any ideas why this is not working?
>

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

Because you get a MultipleBagFetchException in this case. You need to do one more request.

huangapple
  • 本文由 发表于 2020年10月11日 21:44:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/64304660.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定