'Any way in Grails 3 for hasOne to do a lazy fetch?

Grails 3.2.5. Is see from my sql dump that the hasOne relationship does an eager fetch. This used to be the case back in prior versions of Grails, and the behavior could not be overridden. Is this still the case? What is the recommended model for a 1:1 relationship where we want a lazy fetch on the dependent object?

A little background. My "Comment" object has a one-to-one relationship with a "CommentText" object, where the text object holds Oracle clob text - some of it large. I only wanted to get the text when explicitly required to do so. The fk was in the dependent database text object, hence the "hasOne". Fortunately I was able to move the fk to the owner side of the association via an embedded domain object and update the db schema.

Throughout, I was unable to get lazy loading of the hasOne dependent object. Tried fetch: 'lazy'; fetchMode: 'lazy, and other variations of things. I needed a full domain class association because of "find" actions that needed to traverse the association.

I would still prefer the hasOne approach, if loading were indeed lazy.



Solution 1:[1]

Old question, but I just encountered the same problem so I'll answer for later reference.

Basically, it is impossible to lazy-fetch a hasOne property in Grails 3 (tested with 3.3.11, assuming Hibernate). But there are some workarounds.

The immediate lazy-fetch N+1 problem

As soon as you put hasOne: [child: Child] on the parent class, GORM will force you to make the relationship into a bidirectional one-to-one, and it will put the foreign key on the child table.

When you then fetch entities of the parent, it will immediately fetch all of the child entities as well, but it will do a query for every child (N+1 problem).

So a statement like this

Parent.list(max: 10)

will issue one query to get the 10 parents, and then do a query where parent_id = ? for each of the 10 children.

Even if you put fetch: 'lazy' and batchSize: 10 on the mapping of the child in Parent.groovy, the behavior is the same.

Workaround 1: One-directional with FK on the parent table

This is the solution you mention in your post. If you don't need to access the parent from the child side, you can make the relationship one-directional, which will put the FK on the parent table.

Now when fetching the Parent entity it will fetch the child_id from the parent table automatically, but keep the child property as a Hibernate proxy.

The child entity behind the proxy will correctly only be fetched once you access it. The batchSize mapping seems to be ignored here though, so when you actually start accessing the .child entities it will again issue one query per Parent.

Workaround 2: One-to-many and just access the first element

If you really want to keep the FK on the child table and also have lazy loading, you can use this hackaround.

On the Parent.groovy you could specify it like this

static hasMany = [children: Child]

static transients = ['child']

Child getChild() {
    children ? children.first() : null
}

static mapping = {
    children batchSize: 100
}

Now when you fetch the Parent entities it will correctly ignore the child property, and if you e.g. loop through a list of Parent and access the .child on each, it will only issue one single query for batchSize Parents.

So code like this:

def parents = Parent.list(max: 10)
parents.each {
    log.info it.child.subProperty
}

will do the first query to get the 10 parents, and then one single query to lazily batch-fetch the children for up to batchSize parents. With Sql logging enabled it should look something like this:

select child0_.parent_id, child0_.id, ... from child child0_ where child0_.parent_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Workaround 3: The eager-fetch non-workaround

If your application code almost always uses the child property, then one option is to give up on lazy fetching and just specify child fetch: 'join' in Parent.groovy.

This will eliminate the N+1 lazy fetching problem, but as a downside Hibernate will now LEFT JOIN the child table and select all it's properties every time you request the Parent entity, even if you never touch the child property.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1