Question

There is some code in the project I'm working on where a dynamic finder behaves differently in one code branch than it does in another.

This line of code returns all my advertisers (there are 8 of them), regardless of which branch I'm in.

Advertiser.findAllByOwner(ownerInstance)

But when I start adding conditions, things get weird. In branch A, the following code returns all of my advertisers:

Advertiser.findAllByOwner(ownerInstance, [max: 25])

But in branch B, that code only returns 1 advertiser.

It doesn't seem possible that changes in application code could affect how a dynamic finder works. Am I wrong? Is there anything else that might cause this not to work?

Edit

I've been asked to post my class definitions. Instead of posting all of it, I'm going to post what I think is the important part:

static mapping = {
    owner fetch: 'join'
    category fetch: 'join'
    subcategory fetch: 'join'
}

static fetchMode = [
      grades: 'eager',
      advertiserKeywords: 'eager',
      advertiserConnections: 'eager'
]

This code was present in branch B but absent from branch A. When I pull it out, things now work as expected.

I decided to do some more digging with this code present to see what I could observe. I found something interesting when I used withCriteria instead of the dynamic finder:

Advertiser.withCriteria{owner{idEq(ownerInstance.id)}}

What I found was that this returned thousands of duplicates! So I tried using listDistinct:

Adviertiser.createCriteria().listDistinct{owner{idEq(ownerInstance.id)}}

Now this returns all 8 of my advertisers with no duplicates. But what if I try to limit the results?

Advertiser.createCriteria().listDistinct{
    owner{idEq(ownerInstance.id)}
    maxResults 25
}

Now this returns a single result, just like my dynamic finder does. When I cranked maxResults upto 100K, now I get all 8 of my results.

So what's happening? It seems that the joins or the eager fetching (or both) generated sql that returned thousands of duplicates. Grails dynamic finders must return distinct results by default, so when I wasn't limiting them, I didn't notice anything strange. But once I set a limit, since the records were ordered by ID, the first 25 records would all be duplicate records, meaning that only one distinct record will be returned.

As for the joins and eager fetching, I don't know what problem that code was trying to solve, so I can't say whether or not it's necessary; the question is, why does having this code in my class generate so many duplicates?

Was it helpful?

Solution

I found out that the eager fetching was added (many levels deep) in order to speed up the rendering of certain reports, because hundreds of queries were being made. Attempts were made to eager fetch on demand, but other developers had difficulty going more than one level deep using finders or Grails criteria.

So the general answer to the question above is: instead of eager by default, which can cause huge nightmares in other places, we need to find a way to do eager fetching on a single query that can go more than one level down the tree

The next question is, how? It's not very well supported in Grails, but it can be achieved by simply using Hibernate's Criteria class. Here's the gist of it:

        def advertiser = Advertiser.createCriteria()
            .add(Restrictions.eq('id', advertiserId))
            .createCriteria('advertiserConnections', CriteriaSpecification.INNER_JOIN)
                .setFetchMode('serpEntries', FetchMode.JOIN)
                .uniqueResult()

Now the advertiser's advertiserConnections, will be eager fetched, and the advertiserConnections' serpEntries will also be eager fetched. You can go as far down the tree as you need to. Then you can leave your classes lazy by default - which they definitely should be for hasMany scenarios.

OTHER TIPS

Since your query are retrieving duplicates, there's a chance that this limit of 25 records return the same data, consequently your distinct will reduce to one record.

Try to define the equals() and hashCode() to your classes, specially if you have some with composite primary key, or is used as hasMany.

I also suggest you to try to eliminate the possibilities. Remove the fetch and the eager one by one to see how it affects your result data (without limit).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top