Grails Many to Many Association Querying - grails

I have a many to many relationship.
class Post {
String title
static hasMany = [tags:Tag]
}
class Tag {
static hasMany = [posts:Post]
}
I would like to get a list of posts for a tag that have some other criteria (like a sort order, partial title match, etc). Do I have to use the grails criteria to achieve this? Or is there some way to do something like this:
Post.findAllByTitleLikeAndTagsContains("partial title", aTag)

I don't think dynamic finders will allow you to get into one to many or many to many associations - you have to do a criteria or go the HQL query route. You can only query by one to one association, not by one to many. (see section 5.4.1 Dynamic Finders)

You can use withCriteria,for example:
Post.withCriteria{
tags {
eq 'id',aTag.id
}
}

Related

Can't avoid n+1 selects with many to many relationship in Grails

I'm creating a toy Q&A site with Grails in order to learn the platform.
I have two domains, Posts and Tags, with a many to many relationship between them. I want to print a list of posts with their tags.
I can't use lazy fetching, because I would run into the N+1 selects problem
I can't use eager fetching either because it uses a left join and I wouldn't be able to page the results properly.
Therefore I decided to manually fetch the tags with the following code:
static def getList(params) {
ArrayList questions = Question.list(params)
def questionMap = [:]
questions.each {
questionMap.put(it.id, it)
}
if(questions.size()>0) {
Tag.executeQuery('SELECT q.id, t FROM Tag t JOIN t.questions q \
WHERE q.id in ( :list ) ', [ list:questions.collect{ it.id } ] ).each { questionMap.get(it[0]).tags.add(it[1]) }
}
return questions
}
however when I print the tags in my view:
<g:each in="${questions}" var="question">
${question.title}
<g:each in="${question.tags}" var="tag">
${tag?.text}
</g:each>
</g:each>
A query is executed for each question anyway!
What is the recommended approach here?
The problem with your code is that you don't do anything with the result of your Tag query. Also, it's a better approach to have a join class for many-to-many relationships. If you see the Spring Security Core plugin, for example, you have the User, the Role and a join class called UserRole. Here is the example class.
So my suggestion to you is:
class Tag {
...
}
class Question{
...
}
class QuestionTag implements Serializable {
Tag tag
Question question
static mapping = {
id composite: ['tag','question']
...
}
//need to override equals and hashCode
}
To store the result of tags, you can add a transient field for your class:
class Question {
def tags
static transients = ['tags']
//remove the hasMany.
}
You can now execute your HQL, look for the instance of question in your questions list and set the tags attribute. And since you are using one HQL that don't return a single class, the result is not mapped as a Tag object, so the access is a little different.
HQL queries can return domain class instances, or Arrays of specified
data when the query selects individual fields or calculated values
You are saying
"I can't use eager fetching either because it uses a left join and I
wouldn't be able to page the results properly."
You can do paging for an association by using session.createFilter.
This example (copyright Burt Beckwith) is from Burt Beckwith's book Programming Grails from "Chapter 5, Hibernate , session.createFilter"
// example from Burt Beckwith's book "Programming Grails", (c) Burt Beckwith
class Branch {
String name
List visits
static hasMany = [visits: Visit]
List<Visit> getVisitsByPage(int pageSize, int pageNumber) {
Branch.withSession { session ->
session.createFilter(visits, '')
.setMaxResults(pageSize)
.setFirstResult(pageSize * pageNumber)
.list()
}
}
}
I recommend buying this book

Grails: find by one-to-many association with String

I have a following domain class:
class User {
static hasMany = [roles:String]
}
I would like to find every user which has role ROLE_ADMIN. Is there any possibility to do that with dynamic finders? user.findAllByRoles('ROLE_ADMIN') seems to give me an error.
UPDATE: it is quite easy to query association where Class A has a list of class B instances and both A and B are domain classes. But here class A is a domain class and class B is a simple Java string.
The code for querying association containing list of another domain objects would look like this:
`User.findAll { roles { role_name=='ROLE_ADMIN' } }`
What i am looking for is a way to specify the value of a String, for example:
`User.findAll { roles {THIS_VALUE=='ROLE_ADMIN' }}`
UPDATE 2: as far as i have found it is not possible to use criteria with collections of primitive types. It is possible to use HQL though:
User.findAll("from User a where :roles in elements(roles)",[roles:'ROLE_ADMIN'])
However it is not as usefull as a findAll or where query. I cannot chain findAll methods so defining other methods that for example: get ROLE_ADMIN users with username like 'xxx' requires rewriting whole HQL query. Maybe it is possible to express above HQL condition in form of a where expression?
Maybe you can do something like:
if you have already a user list (userList)
def list = userList.findAll { user -> user.roles =~ 'ROLE_ADMIN' }
Hope this help!
I have the same problem How to find records by value in their association property via DetachedCriteria
I made some investigation and, as I found, it's impossible.
The GORM DSL itself doesn't have any method to check that value contains in association.
It conains oly that criterias that are in SQL: AND, OR, IN.
But! You can join association as table in criteria Querying by Association Redux

Querying associations with enum collections in Grails

I'm trying a query in Grails 1.2.1, find all products by tenant type.
My solution works but is very inefficient, first I retrieve all products and then find all matching results for the given tenant.
I found a related bug in JIRA: Enum as collection
class Product {
Set<TenantType> tenants
static hasMany = [tenants: TenantType]
}
enum TenantType {
BICYCLE,
MOTORCYCLE
}
def tenant = TenantType.BICYCLE
Product.list().findAll { product -> tenant in product.tenants }
Is there a more efficient way of querying for this data?
A similar question was asked here and as pointed out in the answer, it looks like Hibernate doesn't support criteria queries over collections of value types like enums. One option is to use an hql query instead:
Product.executeQuery('from Product p inner join p.tenants tenants
where tenants = :tenant', [tenant: TenantType.BICYCLE])
Can be executed without join:
Product.executeQuery('from Product where :tenant in elements(tenants)', [tenant: TenantType.BICYCLE])
Use named query in Product class something like
findByTenantType { tenantType->
tenants{ eq 'value', tenantType}
}
And then access this named query like this -
def product = Product .findByTenantType(TenantType.BICYCLE).get()
see similar blog - http://www.grailsbrains.com/search-parent-through-child-in-aggregation-relationship/

grails searchable plugin query

My Grails app is using the searchable plugin, which builds on Compass and Lucene to provide search functionality. I have two searchable classes, say Author and Book. I have mapped these classes to the search index, so that only certain fields can be searched.
To perform a search across both classes I simply call
def results = searchableService.search(query)
One of the nice features of doing the search across both class simultaneously, is that the results object includes metadata about number of results included, number of results available, pagination details etc.
I recently added a boolean approved flag to the Book class and I never want unapproved books to appear in the search results. One option is to replace the call above with:
def bookResults = Book.search(query + " approved:1")
def authorResults = Author.search(query)
However, I now need to figure out how to combine the metadata for both results, which is likely to be tricky (particularly pagination).
Is there a way to search across Book and Author with a single query, but only return approved books?
Do you want to be able to find authors or do you want to find books with a given author?
If you want to find books with a given author, you can configure your domain classes in the following way:
class Author {
String name
...
static searchable = {
root false
}
}
this will result in excluding the Author from the searchableService.search(query)-result and you'll find field names like $/Book/Author/name in your index. (use luke to examine your index: http://code.google.com/p/luke/).
You can change the name of those fields by configuring a better prefix in your Book-class:
class Book {
String name
Author author
...
static searchable = {
author component: [prefix: 'author']
}
}
this will change the name of the field in the index to bookauthor.
If you now search with searchableService.search(query), you'll find all books where the name of the book or the name of the author contains the search term. You can even restrict the search to a given author by using the authorname:xyz syntax.
If you really would like to mix the search results, I only know the solution you already mentioned: mixing both results with your own code, but I guess it will be hard to mix the scoring of the hits in a good way.
Update to your response: Here's my pagination code...
.gsp:
<div class="pagination">
<g:paginate total="${resultsTotal}" params="[q: params.q]"/>
</div>
controller:
result = searchableService.search(params.q, params)
[
resultList: result.results,
resultsTotal: result.total
]
So if you just merge the results of your two searches and add the result.totals, this could work for you.
I've created a test app and came to the following solution. maybe it helps...
if the property approved only has the states 0 and 1, the following query will work:
def results = searchableService.search(
"(${query} AND approved:1) OR (${query} -approved:0 -approved:1)"
)
I guess this can be reformulated in a better way if you don't use the QueryParser but the BooleanQueryBuilder.
BTW: if you add a method like
String getType() { "Book" }
and
String getType() { "Author" }
To your domains, you can even configure your search to do it like this
def results = searchableService.search(
"(${query} AND approved:1) OR (${query} AND type:Author)"
)

Grails createCriteria many-to-many

Imagine i have the following (this is a search mechanism for my website)
class Supermarket {
String sp_name
String sp_street
}
class Products {
String p_name
String p_price
}
class products_supermarket{
Supermarket sp
Products pro
}
Now i want to create a criteria:
def c = Supermarket.createCriteria()
def results = c.list {
like("sp_street", params.street)
and {
************ ... params.product
}
maxResults(10)
}
Where i have the * i want to be able to find products whithin that supermaked searching on products_supermarket class. How to do that?
PS. If criteria works as an each() method, iterating over all supermarkets, i could use an if-else statment to search for products, and if found i could use: idEq(it), where it is the supermarket id. This way i would make it. My problem is, i dont know how to get current'sm supermarket id. Any help?
and is applied to criterias inside it, so there's no point applying it to a single statement. Top-level criterias are and-ed by defauilt.
You usually better go without connector class, just by using hasMany: Supermarket and hasMany: Product in domain classes. Connector table will be auto-generated by Hibernate.
If you stick with ProductsSupermarket connector class, do add belongsTo: Supermarket and belongsTo: Product to it class, and add 'hasMany: ProductsSupermarket' to other two, or you're losing Grails' GORM advantage.
There's a section "Querying Associations" in the doc.
Object's id is as simple as that: mySupermarket.id, or mySupermarket.ident() if key field is named differently. id field is auto-added to class and table by default.
So the query is:
List<Supermarket> results = Supermarket.withCriteria {
like("sp_street", params.street)
productSupermarket {
product {
idEq(params.product)
}
// or just eq('product', someProduct)
}
************ ... params.product
maxResults(10)
}

Resources