Grails : how to best construct a hibernate criteria builder to search 'hasMany' relationships with domain instance

£可爱£侵袭症+ 提交于 2019-11-29 13:02:54

What you're looking for is the equivalent of Groovy's Object.every(Closure).

assert [1, 2, 3].every { it < 4 } == true assert [1, 2, 3].every { it < 3 } == false

The every() method returns a Boolean indicating whether the Closure evaluates to true for every item in the collection.

Unfortunately, none of the query methods (where, criteria, and HQL) provide an equivalent of every(). But... you can cheat using HQL.

Note: Where nor Criteria queries will do because they don't support the equivalent of the HQL HAVING clause.

Scenario #1 - The Hack

def ids = [4, 5, 6] // List of Option ids.

Product.executeQuery '''
select prd from Product as prd 
    join prd.productOptions as prdopts 
    join prdopts.option as opt 
where opt.id in :ids 
group by prd
having count(prd) = :count''', [ids: ids.collect { it.toLong() }, count: ids.size().toLong()]

How it works

The query begins by selecting all of the Products which have any of the Options in the ids list. As long as a Product has at least one of the options it will be returned.

This produces the side-effect of listing a Product for every matching option it has. For instance, if a Product has three of the Options, then the Product is returned three times. The GROUP BY clause makes the query filter out those duplicate listings.

However, those duplicates are key to this hack: if the list of IDs is a unique list, and Products do not have the same Option more than once, then the Product has all of the required Options if the number of duplicates is equal to the number of IDs. And that's what the HAVING clause does by counting the number of Products.

Scenario 2 & 3

Scenarios 2 & 3 can be handled by the same query. I'm going to forgo consistency and chose a Criteria query because it serves this purpose best.

// Example params for scenario 2
def qparams = [
    or: [1, 2], // These are color Option IDs
    and: 5 // This is a size Option ID
]

// Example params for scenario 3
def qparams = [
    or: [10, 11] // These are brand Option IDs
]

Product.withCriteria {
    productOptions {
        option {
            if(qparams.and) eq('id', qparams.and.toLong())
            inList('id', qparams.or.collect({ it.toLong() }))               
        }
    }
}

The or parameter is always expected, but the if block only adds the and constraint if the and parameter is specified. Notice that the IDs are all just Option IDs, so you have some flexibility. For instance, you can search for any colors without a size constraint.

About the IDs...

You'll notice that in my examples I converted the IDS from Integers to Longs. If you IDs are coming from the database, then they're already Longs so you can take that code out.

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