java 忽略关系中的 FetchType.EAGER

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17847289/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 15:12:56  来源:igfitidea点击:

Ignore a FetchType.EAGER in a relationship

javahibernatejpahqljpql

提问by Dherik

I have a problem with EAGERs relationships in a big application. Some entities in this application have EAGERassociations with other entities. This become "poison" in some functionalities.

我在大型应用程序中的 EAGERs 关系有问题。此应用程序中的某些实体EAGER与其他实体有关联。这在某些功能中变成了“毒药”。

Now my team needs to optimize this functionalities, but we cannot change the fetch type to LAZY, because we would need to refactor the whole application.

现在我的团队需要优化此功能,但我们不能将 fetch 类型更改为 LAZY,因为我们需要重构整个应用程序。

So, my question: Is there a way to do a specific query ignoring the EAGERs associations in my returned entity?

所以,我的问题是:有没有办法执行特定查询而忽略我返回的实体中的 EAGERs 关联?

Example: when a I have this entity Person, I would like to not bring the address list when I do a query to find a Person.

示例:当我有这个实体Person时,我想在查询查找Person时不带地址列表。

@Entity
public class Person {

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;

}

Query query = EntityManager.createQuery("FROM Person person");
//list of person without the address list! But how???
List<Person> resultList = query.getResultList();

Thanks!

谢谢!

Updated

更新

The only way I found is not returning the entity, returning only some fields of the entity. But I would like to find a solution that I can return the entity (in my example, the Personentity).

我发现的唯一方法是不返回实体,只返回实体的某些字段。但是我想找到一个可以返回实体(在我的示例中为Person实体)的解决方案。

I'm thinking if is possible to map the same table twice in Hibernate. In this way, I can mapping the same table without the EAGER associations. This will help me in a few cases...

我在想是否可以在 Hibernate 中两次映射同一个表。通过这种方式,我可以在没有 EAGER 关联的情况下映射同一个表。这将在某些情况下帮助我......

采纳答案by Dherik

After all this years, override the EAGER mapping is not yet possibleon Hibernate. From the latest Hibernate documentation(5.3.10.Final):

毕竟这么多年,在 Hibernate 上覆盖 EAGER 映射是不可能的。来自最新的 Hibernate 文档(5.3.10.Final):

Although the JPA standard specifies that you can override an EAGER fetching association at runtime using the javax.persistence.fetchgraph hint, currently, Hibernate does not implement this feature, so EAGER associations cannot be fetched lazily. For more info, check out the HHH-8776Jira issue.

When executing a JPQL query, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly, which can lead dto N+1 query issues.

For this reason, it's better to use LAZY associations, and only fetch them eagerly on a per-query basis.

尽管 JPA 标准规定您可以在运行时使用 javax.persistence.fetchgraph 提示覆盖 EAGER 获取关联,但目前,Hibernate 未实现此功能,因此无法延迟获取 EAGER 关联。有关更多信息,请查看 HHH-8776Jira 问题。

在执行 JPQL 查询时,如果省略了 EAGER 关联,Hibernate 将针对需要急切获取的每个关联发出辅助选择,这可能导致 dto N+1 查询问题。

出于这个原因,最好使用 LAZY 关联,并且只在每个查询的基础上急切地获取它们。

And:

和:

The EAGER fetching strategy cannot be overwritten on a per query basis, so the association is always going to be retrieved even if you don't need it. More, if you forget to JOIN FETCH an EAGER association in a JPQL query, Hibernate will initialize it with a secondary statement, which in turn can lead to N+1 query issues.

EAGER 获取策略不能在每个查询的基础上被覆盖,因此即使您不需要关联也总是会被检索。此外,如果您忘记在 JPQL 查询中 JOIN FETCH EAGER 关联,Hibernate 将使用辅助语句对其进行初始化,这反过来会导致 N+1 查询问题。

回答by Milan

If you are using JPA 2.1 (Hibernate 4.3+) you can achieve what you want with @NamedEntityGraph.

如果您使用的是 JPA 2.1(Hibernate 4.3+),您可以使用 @NamedEntityGraph 实现您想要的。

Basically, you would annotate your entity like this:

基本上,你会像这样注释你的实体:

@Entity
@NamedEntityGraph(name = "Persons.noAddress")
public class Person {

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;

}

And then use the hints to fetch Person without address, like this:

然后使用提示来获取没有地址的 Person,如下所示:

EntityGraph graph = this.em.getEntityGraph("Persons.noAddress");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

return this.em.findAll(Person.class, hints);

More on the subject can be found here.

可以在此处找到有关该主题的更多信息

When you use the fetch graph only fields that you have put inside @NamedEntityGraph will be fetched eagerly.

当您使用提取图时,只会急切地提取您放在 @NamedEntityGraph 中的字段。

All your existing queries that are executed without the hint will remain the same.

在没有提示的情况下执行的所有现有查询将保持不变。

回答by Madhusudana Reddy Sunnapu

By default, Hibernate's HQL, Criteria and NativeSQL gives us the flexibility to EAGERly load a collection if it is mapped as LAZY in the domain model.

默认情况下,如果集合在域模型中映射为 LAZY,Hibernate 的 HQL、Criteria 和 NativeSQL 使我们可以灵活地急切加载集合。

Regarding the other way round, ie., mapping the collection as as EAGER in the domain model and try to do LAZY load using HQL, Criteria or NativeSQL, I couldn't find a straight forward or simpler way in which we can meet this with HQL/Criteria/NativeSQL.

关于另一种方式,即将集合映射为域模型中的 EAGER 并尝试使用 HQL、Criteria 或 NativeSQL 进行延迟加载,我找不到一种直接或更简单的方法来解决这个问题HQL/标准/NativeSQL。

Although we have FetchMode.LAZYthat we can set on Criteria,it is deprecated and it is equivalent to FetchMode.SELECT. And effectively FetchMode.LAZY actually results in firing an extra SELECT query and still eagerly loads the collection.

虽然我们FetchMode.LAZY可以在 Criteria 上设置它,但它已被弃用,它等效于FetchMode.SELECT. 并且有效地 FetchMode.LAZY 实际上导致触发额外的 SELECT 查询并且仍然急切地加载集合。

But, if we want to LAZY load a collection that is mapped as EAGER, you can try this solution: Make the HQL/Criteria/NativeSQL to return the scalar values and use a ResultTransformer(Transformers.aliasToBean(..))to return the entity object (or DTO) with fields populated from scalar values.

但是,如果我们想延迟加载映射为 EAGER 的集合,您可以尝试以下解决方案:使 HQL/Criteria/NativeSQL 返回标量值并使用 aResultTransformer(Transformers.aliasToBean(..))返回实体对象(或 DTO),其中的字段填充自标量值。

In my scenario, I have a Forestentity that has a collection of Treeentities with oneToManymapping of FetchType.EAGERand FetchMode.JOIN. To load only the Forestentity without loading any trees, I have used the following HQL query with scalar valuesand Transformers.aliasToBean(...). This works with Criteria and Native SQL as well as long as scalars and aliasToBean Transformer is used.

在我的场景中,我有一个Forest实体,其中包含一个Tree实体的集合,其中oneToMany映射为FetchType.EAGERFetchMode.JOIN。为了仅加载Forest实体而不加载任何树,我使用了以下带有标量值Transformers.aliasToBean(...) 的HQL 查询。这适用于 Criteria 和 Native SQL,并且只要使用了标量和 aliasToBean 转换器。

Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();

Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();

I have tested for above simple query and it might be working checking if this works for complex cases as well and fits all your use cases.

我已经对上述简单查询进行了测试,它可能正在检查这是否适用于复杂情况并适合您的所有用例。

Would be keen to know if there is a better or simpler way of doing it especially without scalars and Transformers.

很想知道是否有更好或更简单的方法来做到这一点,尤其是在没有标量和变压器的情况下。

回答by Attila T

Never actually tried this but might worth a shot... Assuming that the session factory is available at the DAO layer via injection or any other means you can implement something similar in a (probably new) DAO method:

从未真正尝试过,但可能值得一试......假设会话工厂通过注入或任何其他方式在 DAO 层可用,您可以在(可能是新的)DAO 方法中实现类似的东西:

List<Person> result = (List<Person>) sessionFactory.getCurrentSession()
        .createCriteria(Person.class)
        .setFetchMode("address", FetchMode.LAZY)
        .list();
return result;

回答by Dragan Bozanovic

  1. Yes, you can map two entity classes to the same table and that is a valid workaround. However, beware the situations in which instances of both types are simultaneously present in the same persistence context, because updates of an entity instance of one type are not reflected to the same instance of the other type. Also, second-level caching of such entities gets more complicated.
  2. Fetch profilesare also interesting, but are very limited at the time being and you can override the default fetch plan/strategy only with the join-style fetch profiles (you can make a lazy association eager, but not vice versa). However, you could use this trickto invert that behaviour: Make the association lazy by default and enable the profile by default for all sessions/transactions. Then disablethe profile in transactions in which you want lazy loading.
  1. 是的,您可以将两个实体类映射到同一个表,这是一种有效的解决方法。但是,请注意两种类型的实例同时存在于同一持久性上下文中的情况,因为一种类型的实体实例的更新不会反映到另一种类型的同一实例中。此外,此类实体的二级缓存变得更加复杂。
  2. 获取配置文件也很有趣,但目前非常有限,您只能使用连接样式的获取配置文件覆盖默认的获取计划/策略(您可以使延迟关联急切,但反之则不行)。但是,您可以使用此技巧来反转该行为:默认情况下使关联延迟并默认为所有会话/事务启用配置文件。然后在要延迟加载的事务中禁用配置文件。

回答by Nathan Kummer

You didn't say why you couldn't change from eager to lazy. I, however, got the impression that it was for performance reasons and so I want to question this assumption. If that is the case, please consider the following. I realize this answer does not strictly answer your question and it violate the condition of no lazy loads, but here is an alternative that reflects my dev team's approach to what I think is the same underlying issue.

你没有说为什么你不能从渴望变成懒惰。然而,我的印象是出于性能原因,所以我想质疑这个假设。如果是这种情况,请考虑以下事项。我意识到这个答案并没有严格回答你的问题,它违反了无延迟加载的条件,但这里有一个替代方案,反映了我的开发团队对我认为是相同的潜在问题的方法。

Set the fetch type to lazy, but then set a @BatchSize annotation. Since hibernate generally uses a separate DB query to load a collection, this maintains that behavior but with a tuned BatchSize, you avoid 1 query per element (while in a loop, for example) - provided your session is still open.

将获取类型设置为惰性,然后设置 @BatchSize 注释。由于 hibernate 通常使用单独的 DB 查询来加载集合,因此这会保持该行为,但通过调整 BatchSize,您可以避免每个元素 1 个查询(例如,在循环中) - 前提是您的会话仍处于打开状态。

The behavior for the backref of a OneToOne relationship gets a little funny (the referenced side of the relationship - the side without the foreign key). But for the other side of the OneToOne, for OneToMany and ManyToOne, this gives the end result that I think you probably want: you only query the tables if you actually need them but you avoid a lazy load per record, and you don't have to explicitly configure each use case. This means that your performance will remain comparable in the event that you execute the lazy load, but this load does not happen if you do not actually need it.

OneToOne 关系的 backref 的行为变得有点有趣(关系的引用侧 - 没有外键的一侧)。但是对于 OneToOne 的另一面,对于 OneToMany 和 ManyToOne,这给出了我认为您可能想要的最终结果:您只在确实需要它们时才查询表,但是您避免了每条记录的延迟加载,而您没有必须明确配置每个用例。这意味着如果您执行延迟加载,您的性能将保持可比性,但如果您实际上不需要它,则不会发生此加载。