Grails: writing HQL query when domains are not directly matched - grails

I have a domain class named Logging which stores an id of another domain class: Organization
The structure of both domains is provided:
class Logging {
Date dateCreated
long user_id
long organization_id
String memberCode
static constraints = {
user_id(nullable: false)
organization_id(nullable: false)
memberCode(nullable: true)
}
}
class Organization {
Type type
String name
String memberCode
User manager
String collateralAutoEmails
boolean isBlocked = true
static constraints = {
name(blank: false, unique: true)
manager(nullable: true)
memberCode(nullable: true)
collateralAutoEmails(nullable: true)
}
static mapping = {
manager(lazy: false)
}
}
User enters several parameters: dateCreated, the memberCode and the name of the organization. I need to select all elements from the Logging domain matching these criterias.
The tricky part for me is writing the query for the name of the organisation parameter.
According to the search rules I should check whether organization.name field contains data entered by user as a substring(case insensetive) and select the corresponding element from the Logging domain.
The two domains are not mapped directly and I can't join those tables.I have tried different approaches but still haven't found the solution.

Here you go
Logging.executeQuery("Select l from Logging l, Organization o where l.organization_id = o.id and o.dateCreated = :dateCreated and o.memberCode = :memberCode and o.name = :name", [dateCreated: dateCreated, memberCode: memberCode, name: name])

Try something like this:
Organization.executeQuery("select o from Organization o, Logging l where o.name like = :orgName AND o.id=l.organization_id", [orgName : orgName ])
I didn't tried it, if it works then more search options can be added on the query, and also % can be added on the parameter, in order to enhance the search.

Related

Gorm criteria query to eager-fetch two one to many relationships, fetchMode doesn't work

I have a domain model - where Org has many sites, and has many domains (these are two 'bag' collection attributes).
I want to write a query that retrieves a single, and preferably the sites and domain collections in one hit.
I tried this first
org = Org.findById (id, [fetch:[sites:"eager", domains:"eager"]]
which fails with
cannot simultaneously fetch multiple bags: [com.softwood.domain.OrgRoleInstance.sites, com.softwood.domain.OrgRoleInstance.domains]
(It will work with only one of the two collections).
I tried a criteria query like this
org = resource.withCriteria (uniqueResult: true) {
fetchMode 'sites', FetchMode.SELECT
fetchMode 'domains', FetchMode.SELECT
idEq(id)
sites {
org {
eq 'id', "$id"
}
}
} // didn't work
which errors with this
No signature of method: com.softwood.controller.OrgRoleInstanceController.fetchMode() is applicable for argument types: (java.lang.String, org.hibernate.annotations.FetchMode) values: [sites, SELECT]
This indicates it doesn't like the fetchMode function, (using either SELECT or JOIN).
How do you write a criteria query to return a single matched object by id, but returns sites, and domains collections at the same time?
The domain class looks like this. I don't want to use the static mapping ={} closure - as i want to control the loading through writing explicit queries as required
class Org {
enum OrgRoleType {
Supplier,
Customer,
Service_Provider,
Manufacturer,
Maintainer,
Indeterminate
}
String name
OrgRoleType role
Collection<NetworkDomain> domains = []
Collection<Site> sites = []
Collection<MaintenanceAgreement> mags //optional only set if role is service provider
//Collection<Device> ci
static hasMany = [domains : NetworkDomain, sites: Site, mags:MaintenanceAgreement]
static mappedBy = [mags: "maintainer"] //disambiguate column in mags
static constraints = {
name nullable:false
role nullable:true
domains nullable:true
sites nullable:true
mags nullable:true //optional
}
}
I had seen this [Grails GORM Criteria Query Eager Fetching
I'd tried to do similarly but the fetchMode (String, enum) just won't run.
Update
I changed query to this
org = resource.withCriteria (uniqueResult: true) {
join 'sites'
join 'domains'
idEq(id)
sites {
org {
eq 'id', "$id"
}
}
domains {
customer {
eq 'id', "$id"
}
}
}
This errors with
Cannot invoke method call() on null object
with the trace point to point where it access sites{org{ in the criteria.
First, testing has to be done as integration tests and not unit tests, however the result is you still can't do query finder with two eager loads. if you want to load collections you have to use criteria or possibly a where clause to do so
Here are two integration tests - the first using finders fails with exception, the second achieves the goal using criteria query:
void "query by finderById with multiple eager fetch in map conditions " () {
given :
when: "we print value from a collection "
def org = OrgRoleInstance.findById (7L, [fetch:[sites:"eager", domains:"eager"]])
println org.domains[0].name
then:
org.hibernate.loader.MultipleBagFetchException ex = thrown()
}
void "query withCriteria to do multiple eager fetch in map conditions " () {
given :
def org = OrgRoleInstance.withCriteria (uniqueResult: true) {
fetchMode 'sites', FetchMode.SELECT
fetchMode 'domains', FetchMode.SELECT
idEq (7L)
sites {}
domains {}
}
when: "we print value from a collection "
println org.domains[0].name
then:
org.domains.size() == 1
org.sites.size() == 2
}

Grails: search using createCriteria

I need to do simple search (Two my example-simple domains and controller action are below). I want to return list of users with firstName, lastName or Car.carName like searchPattern
class User {
String firstName
String lastName
static hasMany = [car : Car]
}
class Car {
User user
String carName
}
def list(String search){
...
def searchPattern = "%" + search + "%"
def domains = User.createCriteria().list(max: max, offset: offset) {
or {
like("firstName", searchPattern)
like("lastName", searchPattern)
car {
like("carName", searchPattern)
}
}
}
It returns incorrect results - doesn't see user, which hasn't got car. Can you help me to change it for correct working? Thanks a lot
try this one:
car{
or{
isNull 'carName'
like 'carName', searchPattern
}
}
First you need to set up the domain class associations correctly. It seems you're going for a has-many association between User and Car. There are two variations: uni-directional and bi-directional. However, your implementation uses neither. Going with the assumption that you want a bi-directional association, you'll need to modify your Car class like this:
class Car {
static belongsTo = [user: User]
String carName
}
And for clarity, since a User has many Cars, it'd be worth pluralizing the collection name:
class User {
String firstName
String lastName
static hasMany = [cars : Car]
}
For more on associations, you can read my article on the subject.
Next, since you want Users even if they do not have Cars, you should know about a subtle default built into GORM: the SQL database tables are automatically INNER JOINed. It is this INNER JOIN that's causing Users without Cars to be ignored. To address this, you'll need to change the join to an OUTER JOIN. You can do something like this:
import static org.hibernate.sql.JoinType.*
def domains = User.createCriteria().list(max: max, offset: offset) {
createAlias('cars', 'c', LEFT_OUTER_JOIN)
or {
like("firstName", searchPattern)
like("lastName", searchPattern)
like("c.carName", searchPattern)
isNull("c.carName")
}
}
If I recall, aliases are used differently, hence the c.carName. You can read a bit more about using a LEFT OUTER JOIN here.
Thanks a lot to all for your help and for usefull links. This decided my problem:
import org.hibernate.criterion.CriteriaSpecification
.....
def domains = User.createCriteria().list(max: max, offset: offset) {
createAlias('cars', 'c', CriteriaSpecification.LEFT_JOIN)
or {
like("firstName", searchPattern)
like("lastName", searchPattern)
like("c.carName", searchPattern)
}

Criteria in relation between entities - Grails

I have an app with the following entities:
Topic:
class Topic {
UUID id
String description
String name
boolean visibility = true
// Relation
static hasMany = [tests:Test]
...
}
Test:
class Test {
UUID id
boolean active = true
String description
...
static hasMany = [evaluationsTest: Evaluation]
static belongsTo = [topic: Topic, catalog: Catalog]
}
When I show all visible topics to the user I request the query:
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
This query returns me for example: [['English'], ['Spanish']]. Then, I can show the full information about each topic to the user.
But I also want to indicate to the user the number of active test in each visible topic.
For example:
English topic has 2 active test.
Spanish topic has a total of 2 test. One is active and the other is not.
German topic has not any active test.
Then I need a query whose result is: def activeTotalEachTopic = [[2],[1],[0]] and I can pass the activeTotalEachTopic variable to the view (.gsp).
Solution:
From the first query where I can obtain all visible topics, I get the number of active test.
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
def numberActiveTest = []
activeTopics.each { topic ->
def result = Test.findAllByTopicAndActive(topic, true).size()
numberActiveTest.push(result)
}
And I pass to the view both variables.
render view: 'home', model: [activeTopics: activeTopics, numberActiveTest: numberActiveTest]
What you are missing is grouping so that you get the count per group, rather than a total count.
You also need to change the join type from the default inner join to an outer join in order for topics without an active test to return 0. However, a side-effect of this is that it changes how association properties are referenced due to the alias that's created by the join. Something like this:
import static org.hibernate.sql.JoinType.*
def activeTotalEachTopic = Topic.createCriteria().list() {
createAlias('tests', 't', LEFT_OUTER_JOIN)
eq 'visibility', true
or {
eq 't.active', true
isNull 't.id'
}
projections {
groupProperty 'name'
count()
}
order ("name", "asc")
}
Now, another issue to address is that the output would be something like this due to the grouping: [['English', 2],['Spanish', 1],['German', 0]]. So what you can do is collect the second item in each sub-list:
activeTotalEachTopic*.getAt(1)
// Which is the equivalent of...
activeTotalEachTopic.collect { it[1] }

Grails GORM find by value inside map of Domain

I am using Grails 2.2.4 and having one Domain contains value as map and I want to find domain object using key of map. Please help me to resolve this issue.
Student.groovy
package com.grails
import java.util.Map;
class Student {
String firstName
String lastName
Map address
static constraints = {
}
}
When My application are run I can see that Grails application create tables in database are as follow:
1) first table
student
id
version
first_name
last_name
indexes
2) second table
student_address
address
addres_idx
addres_elt
When I save Domain as:
def std = new Student()
std.firstName = 'Piyush'
std.lastName = 'Chaudhari'
std.address = [city:'Surat',state:'Gujarat',pincode:'38001']
std.save(flash:true)
values are insert in database as follow:
student table
ID VERSION FIRST_NAME LAST_NAME
1 0 Piyush Chaudhari
student_address table
ADDRESS ADDRESS_IDX ADDRESS_ELT
1 city Surat
1 state Gujarat
1 pincode 38001
Now, I want data or row using GORM like Student.findBy_____ or Student.findAllBy______
where 'city' = surat
Any one can help me to resolved this issue?
You can use:
Student.findBy<FieldName1>And<FieldName2> (<FieldNameParameter1>, <FieldNameParameter2>)
Or Either:`
Student.list().find { it.address.city == 'Surat' }
Student.list().findAll { it.address.city == 'Surat' }
`
I don't think that you can search things like this using maps.
Maybe you can do this:
def students = Student.list()
def result = students.each { student -> student.address.city == 'Surat' }
println("Resultado" + result)
But this is a very bad way to do this kind of things
Define an address class, and then add an address field to the student class (this will change how your tables are mapped in the database):
class Student {
String firstName
String lastName
Address address
static constraints = {
}
}
class Address {
String city
String state
String pincode
}
Address should be another entity in your domain, not a map of values. Remember that Grails GROM is an ORM, so you should design your domain using a OOP model in order to take advantage of the dynamic finders and criterias for doing queries.
With those changes in place, you can now use a simple criteria:
def students = Student.withCriteria{
'address'{
eq('city', 'surat')
}
}
More information about criterias in the grails docs:
http://grails.org/doc/latest/ref/Domain%20Classes/withCriteria.html
http://grails.org/doc/latest/guide/single.html#criteria
If you want to use Dynamic finders, you will have to get all the address with city = 'surat' and then use a findByAddressInList(...). But i think that in this case, criterias is a better approach

Grails GORM Domain class relationship

Grails 1.1.1
Goovy 1.5.7
In a relationship such this:
Author 1 -- n Book n -- 1 Publisher
Defined in Grails:
class Author {
String firstName
String lastName
static hasMany = [books: Book]
static constraints = {
books(nullable: true)
}
}
class Book {
String title
Author author
Publisher publisher
static constraints = {
author(nullable: true)
publisher(nullable: true)
}
}
class Publisher {
String name
static hasMany = [books: Book]
static constraints = {
books(nullable: true)
}
}
I want to load a Book with the values of Publisher and Author.
When i get a Book with the query:
def book2 = Book.findAllByAuthor(author)
I get the response with the autor assosiated but the publisher only have the id and name class in the other query:
def book3 = Book.findAllByPublisher(publisher)
I retrieve me the inverse result,i have the book with the publisher data but the author only have the id and the class name.
Where is the error in the defined model ? o there is an error in the way to do the queries ?
Edit:
I need the way to retrieve the values only with the query like this:
def book2 = Book.findAllByAuthor(author, [fetch:[publisher:'eager']])
In this one I can manage the value of publisher.
Question: If publisher had a hasmany or Domain related, getting the book I'm able to read the attributes?
Thanks.
Thanks.
Lazy fetching is used by default with gorm associations. If you want to enable eager fetching, you can modify the ORM DSL by adding the following mappings block to your Author domain class:
static mapping = {
books lazy:false
}
or you could change the fetch mode in the domain object by adding following code after your books relationship is defined.
static fetchMode = [books:"eager"]
Doing the same to your Publisher domain object should allow you to accomplish what you want. You do want to be careful of the consequence that you may load more data than you intend to.
Shouldn't the get() method return what you are looking for?
Example: def book2 = Book.get(author)
You'd better use Criteria and explicitly define which relations should be loaded eagerly. Just mention relation in the query.
Example:
def c = Teacher.createCriteria()
List<Teacher> results = c.list {
subjects {
attendees {}
}
}

Resources