Reputation: 1132
I am working on a grails project and would like to leverage hibernate criteria builders to search for instances of a domain object. I would like to find instances where one of the 'hasMany' relationships contains domain object with certain ids. Here is an example of what I mean.
Domain Objects
class Product {
static hasMany = [ productOptions: ProductOption ]
}
class ProductOption{
Option option
static belongsTo = [ product: Product ]
}
class Option{
String name
}
This is a simplified example of my domain structure and doesn't include all relationships.
An Option
could be size, color, brand, etc.
Example of what I would like to achieve
Lets say I have 3 products.
Product 1 is red, small and by brandx
Product 2 is blue, small and by brandx
Product 3 is yellow, medium and by brandz
I have a few scenarios that I need to cover.
Scenario 1
Scenario 2
Scenario 3
I hope this covers all scenarios.
This is an example of a current attempt.
def c = Product.createCriteria()
def products = c.list{
and {
productOptions {
'option' {
idEq(1)//1 is the id of the blue option
}
}
productOptions {
'option' {
idEq(5)//5 is the id of the small size option
}
}
productOptions {
'option' {
idEq(10)//10 is the id of the brandx brand option
}
}
}
}
The and
portion of this example doesn't include all options and fails. How do I best achieve this? Can I use Grails hibernate criteria builder to achieve this? Please let me know if additional information will help.
Thanks in advance for any guidance provided.
Upvotes: 0
Views: 653
Reputation: 9895
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.
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()]
The query begins by selecting all of the Product
s which have any of the Option
s 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 Option
s, 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 Product
s do not have the same Option
more than once, then the Product
has all of the required Option
s 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 Product
s.
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.
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.
Upvotes: 2