14

I have the following query and method

private static final String FIND = "SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId";

@Override
public Domain find(Long domainId) {
    Query query = getCurrentSession().createQuery(FIND);
    query.setLong("domainId", domainId);
    return (Domain) query.uniqueResult();
}

With Domain as

@Entity
@Table
public class Domain {
    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "domain_id")
    private Long domainId;

    @Column(nullable = false, unique = true)
    @NotNull
    private String name;

    @Column(nullable = false)
    @NotNull
    @Enumerated(EnumType.STRING)
    private DomainType type;

    @OneToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    }, fetch = FetchType.EAGER)
    @JoinTable(joinColumns = {
            @JoinColumn(name = "domain_id")
    }, inverseJoinColumns = {
            @JoinColumn(name = "code")
    })
    @NotEmpty
    @Valid // needed to recur because we specify network codes when creating the domain
    private Set<NetworkCode> networkCodes = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(joinColumns = {
            @JoinColumn(name = "parent", referencedColumnName = "domain_id")
    }, inverseJoinColumns = {
            @JoinColumn(name = "child", referencedColumnName = "domain_id")
    })
    private Set<Domain> operators = new HashSet<>();
    // more
}

I would expect this single query to fetch the Set<NetworkCode> and Set<Domain> relations, but it doesn't. Say that the Domain I query has two operators, Hibernate would perform 1 + 2 * 2 = 5 queries

Hibernate: select distinct domain0_.domain_id as domain1_1_0_, domain2_.domain_id as domain1_1_1_, networkcod4_.code as code2_2_, domain0_.name as name1_0_, domain0_.type as type1_0_, domain2_.name as name1_1_, domain2_.type as type1_1_, operators1_.parent as parent1_0__, operators1_.child as child4_0__, networkcod3_.domain_id as domain1_1_1__, networkcod3_.code as code5_1__ from domain domain0_ left outer join domain_operators operators1_ on domain0_.domain_id=operators1_.parent left outer join domain domain2_ on operators1_.child=domain2_.domain_id inner join domain_network_codes networkcod3_ on domain0_.domain_id=networkcod3_.domain_id inner join network_code networkcod4_ on networkcod3_.code=networkcod4_.code where domain0_.domain_id=?
Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=?
Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=?
Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=?
Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=?

I'm guessing this is because I'm joining the operators Domain elements but they have to join themselves.

Is there an HQL query I can execute that would do both?

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724

7 Answers7

14

If you know that you have only two levels in your tree, have you thought of joining deeper one level. Something like below?

SELECT DISTINCT domain FROM Domain domain 
  LEFT OUTER JOIN FETCH domain.operators operators1 
  LEFT OUTER JOIN FETCH domain.networkCodes 
  LEFT OUTER JOIN FETCH operators1.operators operators2 
  LEFT OUTER JOIN FETCH operators1.networkCodes
WHERE domain.domainId = :domainId
V G
  • 18,822
  • 6
  • 51
  • 89
13

The Hibernate Relations Works with different Fetch Strategies..!!

Hibernate provides 4 strategies for retrieving data:

SELECT

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id") 
@Fetch(FetchMode.SELECT)

In this Method there are Multiple SQLs fired. This first one is fired for retrieving all the records in the Parent table. The remaining are fired for retrieving records for each Parent Record. This is basically the N+1 problem. The first query retrieves N records from database, in this case N Parent records. For each Parent a new query retrieves Child. Therefore for N Parent, N queries retrieve information from Child table.

JOIN

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@Fetch(FetchMode.JOIN) 

This is similar to the SELECT fetch strategy except that fact that all database retrieval take place upfront in JOIN fetch unlike in SELECT where it happens on a need basis. This can become an important performance consideration.

SUBSELECT

 @OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
 @Column(name="id")
 @Fetch(FetchMode.SUBSELECT)

Two SQLs are fired. One to retrieve all Parent and the second uses a SUBSELECT query in the WHERE clause to retrieve all child that has matching parent ids.

BATCH

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@@BatchSize(size=2)

The batch size maps to the number of Parent whose child are retrieved. So we can specify the number of records to be fetched at a time.But Multiple queries will be executed.!!

one-to-many & many-to-many allows - join, Select and SubSelect

many-to-one & one-to-one allows - Join and Select


Hibernate also distinguishes between (when is the associations are fetched)

1.Immediate fetching -

an association, collection or attribute is fetched immediately, when the Parent is loaded. (lazy=“false”)

2.Lazy collection fetching -

a collection is fetched when the application invokes an operation upon that collection. (This is the default for collections.(lazy=“true”)

3."Extra-lazy" collection fetching -

individual elements of the collection are accessed from the database as needed. Hibernate tries not to fetch the whole collection into memory unless absolutely needed (suitable for very large collections) (lazy=“extra”)

4.Proxy fetching -

a single-valued association is fetched when a method other than the identifier getter is invoked upon the associated object. (lazy=“proxy”)

5."No-proxy" fetching -

a single-valued association is fetched when the instance variable is accessed. Compared to proxy fetching, this approach is less lazy.(lazy=“no-proxy”)

6.Lazy attribute fetching -

an attribute or single valued association is fetched when the instance variable is accessed. (lazy=“true”)

one-to-many & many-to-many allows Immediate, Layzy, Extra Lazy

many-to-one & one-to-one allows Immediate Proxy, No Proxy

Dileep
  • 5,362
  • 3
  • 22
  • 38
  • This is a good reference, but I cannot use `@Fetch` with HQL. – Sotirios Delimanolis Oct 15 '13 at 14:48
  • you can add fetch strategy as Extra-lazy in the JPA class, so that the inner queries will not be executed unless you access the inner tables relation. By this you will only be getting the values from the main table, rest will be ignored and that too with SINGLE query. – Dileep Oct 16 '13 at 06:37
4

You marked your associations EAGER. So, whatever you do in your query, Hibernate will load all the associated domains and network codes of the loaded domains. And it will load the domains and network codes of the additional domains, etc. etc. until all collection loads return empty collections or entities that have already been loaded.

To avoid that, make your collections lazy (as they are by default). Then loading a domain with its operators and its network codes will load just that.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Those collections need to be `EAGER` so that they are initialized within my `Transaction` boundaries. I use them in my view layer. I'm wondering if it's possible to load all operators/networkcodes in one swoop, instead of one query for each `Domain`. – Sotirios Delimanolis Oct 08 '13 at 20:40
  • 1
    Not without using proprietary SQL (see http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm for example), or without adjusting your model to introduce, for example, the root of the tree in every child node, and load all the domains that have the same root node. Can't you simply make everything lazy and use OpenSessionInView? Making tomany association eager is almost always a bad idea, especially if recursive: this makes it eager for **every** use-case, including those that just need to load one specific entity. – JB Nizet Oct 08 '13 at 20:46
  • The problem isn't the lazy loading. All that data is going to be retrieved regardless. I just don't want to send tons of SQL requests. I'll take a careful look at the document you linked. Give it a few days. Thank you. – Sotirios Delimanolis Oct 08 '13 at 20:48
  • 1
    I understand your concern. But making the association eager will cause the same tons of request to be executed, except they will be for every single use-case, and not just for the use-cases needing the associations to be actually loaded. That makes it far worse. – JB Nizet Oct 08 '13 at 20:50
  • For now, each use case that uses that query requires the full object graph. I'll look into refactoring. – Sotirios Delimanolis Oct 08 '13 at 20:53
  • 1
    You didn't catch me: not just the use cases that use that query. All the use cases which, in one way or another, load a domain from the database. All the operators of the loaded domain, and the operators of the operators, etc. will always be loaded when loading a domain via *any* query, `session.get()` or navigation through the graph of entities. – JB Nizet Oct 08 '13 at 20:57
  • Ahh, right. It's weird that hasn't actually come up as a problem in the current state of the application. I think I need the whole graph in every case. Looking at my queries, they all have `JOIN FETCH` anyway. – Sotirios Delimanolis Oct 08 '13 at 21:01
2

Your EAGER mapping will only be considered automatically by Hibernate if you use the Criteria API for the query.

If you use HQL, you will need to manually add the FETCH keyword to your JOINs to force Hibernate to include the relations in the first query and avoid subsequent queries.

This is Hibernate-specific and may work differently on other ORMs.

See this question/answer for a slightly different angle.

Community
  • 1
  • 1
sola
  • 1,498
  • 14
  • 23
1

It's not documented that good, but did you try setting the FetchMode? You can do so by either using the Criteria API: domainCriteria.setFetchMode("operators", JOIN) or use @Fetch(JOIN) at the relation definition.

The annotation (and only the annotation as it seems) also allows to set a fetch mode SUBSELECT, which should at least restrain Hibernate to execute 3 queries max. Not knowing your dataset, I assume this should be the way to go for you, as a big fat join over those tables does not seem too healthy. Best to figure it out for yourself, I guess...

skirsch
  • 1,640
  • 12
  • 24
  • My HQL queries already do `FETCH JOIN`. I'll try the `SUBSELECT`. – Sotirios Delimanolis Oct 09 '13 at 12:25
  • I'm not sure if the HQL notation you use is actually the same as the "fetch mode" as understood by hibernate. Afaik, the HQL `JOIN FETCH` is just means to say "oh and by the way, initialize that relations fully, and don't only fetch the IDs" - Hibernate does not interpret this as to what kind of strategy it should apply for fetching, as the preceeding `JOIN` is simply the necessary syntax to apply the `FETCH`. But, as this is nowhere mentioned explicitly, I may have been mixing up my experience with HQL & annotation usage. – skirsch Oct 10 '13 at 12:08
  • Oh and as you bring up your HQL: as you've annotated the realtions with `EAGER` anyway, does your query yield any different result than a simple "SELECT DISTINCT domain FROM Domain domain WHERE domain.domainId = :domainId"? And why the `DISTINCT`? as `domainId` is the PK, how can there be ever more than one result? – skirsch Oct 10 '13 at 12:11
  • Sorry for taking so long to respond. [It turns out you cannot use `@Fetch` with HQL.](http://stackoverflow.com/questions/2931936/hibernate-noob-fetch-join-problem/16614100#16614100) As for the `DISTINCT`, I was having some issues with `JOIN` that required it. It might be redundant in some of these queries. I'll clean that up but it does no harm now afaik. – Sotirios Delimanolis Oct 15 '13 at 13:51
0

Since you have already specified FetchType.EAGER for both networkCodes and operators, whenever you will query domain, hibernate will load both networkCodes and operators. That's the whole idea of EAGER fetching mode

So you could change your query simple to following:

private static final String FIND
    = "SELECT DISTINCT domain"
    + " FROM Domain domain"
    + " WHERE domain.domainId = :domainId";

API details here

Cheers !!

Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
Sachin Thapa
  • 3,559
  • 4
  • 24
  • 42
  • 1
    EAGER fetch don't means that hibernate will execute one single sql query: it means that all objects that must fetch eagerly will be fetch at the same time as the root object (i.e. NOT necessarily in the same query) – ben75 Oct 15 '13 at 10:25
  • @ben75 - Yes you are right, not necessarily same query but they will be loaded at same time. – Sachin Thapa Oct 15 '13 at 13:41
  • Note that my query is not a simple `JOIN`, it's a `LEFT OUTER JOIN`. I cannot get rid of that as the returned entities will not be correct. – Sotirios Delimanolis Oct 15 '13 at 14:24
0

My first observation is that you do not need to write an HQL query containing joins if your mappings say that they must be eagerly loaded.

You can however tell the Hibernate to use fetching strategy as sub select if you don't want to use joins.

Hibernate generates the SQL query for loading the objects during startup based on the specified mappings and caches it. However in your case, you have one to many nested relation with self and arbitrary depth, so looks like it won't be possible for hibernate to decide before hand the sql to correctly eager fetch. So it would need to send multiple joins queries depending upon the depth of the parent Domain you are querying at runtime.

To me it looks like you are thinking that HQL and the resulting SQL/('s) in your case can have one to one correpondence which is not true. With HQL you query for objects and the orm decides how to load that object and its relations (eager/lazy) based on the mappings or you can specify them at runtime too ( for e.g, a lazy association in mapping can be overridden by Query api but not vice versa). You can tell the orm what to load ( my marking eager or lazy ) and how to load eagerly ( either using join / sub select).

UPDATE

When I run the following query on your domain model

SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId";

I can see that the networkCode and operator collections are of instance PersistentSet ( this is Hibernate wrapper) and both have initialized property set to be true. Also in the underlying session context I can see the domains with the domain and the operators listed. So what is making you think that they are not eagerly loaded ?

This is how my domain looks like

@Entity
@Table
public class Domain {
    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "domain_id")
    private Long domainId;

    @Column(nullable = false, unique = true)   
    private String name;

    @Column(nullable = false)    
    @Enumerated(EnumType.STRING)
    private DomainType type;

    @OneToMany(mappedBy = "domain",cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    }, fetch = FetchType.EAGER)   
    private Set<NetworkCode> networkCodes = new HashSet<NetworkCode>();

    @ManyToMany(mappedBy="parent",fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    private Set<Domain> operators = new HashSet<Domain>();
    // more

    @ManyToOne  
    private Domain parent;

    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


public DomainType getType() {
        return type;
    }

    public void setType(DomainType type) {
        this.type = type;
    }


    public Set<Domain> getOperators() {
        return operators;
    }


    public Long getDomainId() {
        return domainId;
    }


    public void setDomainId(Long domainId) {
        this.domainId = domainId;
    }


    public void setOperators(Set<Domain> operators) {
        this.operators = operators;
    }

    public void addDomain(Domain domain){
        getOperators().add(domain);
        domain.setParent(this);
    }


    public Domain getParent() {
        return parent;
    }


    public void setParent(Domain parent) {
        this.parent = parent;
    }

    public void addNetworkCode(NetworkCode netWorkCode){
        getNetworkCodes().add(netWorkCode);
        netWorkCode.setDomain(this);
    }

enter image description here

Shailendra
  • 8,874
  • 2
  • 28
  • 37
  • The `JOIN` in the HQL isn't only for `EAGER` loading, it's for the `LEFT OUTER`. Not every domain has `operators` or `networkCodes`. The `LEFT OUTER JOIN` is necessary for the correct mapping. – Sotirios Delimanolis Oct 15 '13 at 14:15
  • The issue isn't them not being eagerly loaded. It's that there are IMO too many SQL queries generated. I would think Hibernate could do it with less. – Sotirios Delimanolis Oct 15 '13 at 17:02
  • I get that now. I think in that case, Andrei's answer is your best bet ! – Shailendra Oct 15 '13 at 18:29