Ignore a FetchType.EAGER in a relationship

寵の児 提交于 2020-01-19 04:48:31

问题


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

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.

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

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.

@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 Person entity).

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...


回答1:


If you are using JPA 2.1 (Hibernate 4.3+) you can achieve what you want with @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:

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.

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




回答2:


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:

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



回答3:


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.

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.

Although we have FetchMode.LAZY that 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.

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.

In my scenario, I have a Forest entity that has a collection of Tree entities with oneToMany mapping of FetchType.EAGER and FetchMode.JOIN. To load only the Forest entity without loading any trees, I have used the following HQL query with scalar values and Transformers.aliasToBean(...). This works with Criteria and Native SQL as well as long as scalars and aliasToBean Transformer is used.

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.




回答4:


After all this years, override the EAGER mapping is not yet possible on Hibernate. From the latest Hibernate documentation (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-8776 Jira 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.

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.




回答5:


  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 profiles are 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 trick to invert that behaviour: Make the association lazy by default and enable the profile by default for all sessions/transactions. Then disable the profile in transactions in which you want lazy loading.



回答6:


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.

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.



来源:https://stackoverflow.com/questions/17847289/ignore-a-fetchtype-eager-in-a-relationship

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!