I have a create criteria using multiple params with pagination.
The issue that I´m having that if is possible to re-use the code inside the and for others create-criteria totally differents, witouth having to duplicate the code.
I reffer that if I have the cases createcriteria and want to do the same "filters" on the cases2 not have to duplicate the code, unify both createCriteria is not an option
Per example, the problem that I´m facing is that one needs to be with pagination, and other don´t need to be, and both needs to access distinct fields, so the unique option is to create two identical create criteria if I´m not missing something.
That is a example, that cases is at least 90% indetically to cases2, and what would be a good mode to reuse the code?
def cases = PpCase.createCriteria().list{
and{
if(limit){
maxResults(limit)
}
firstResult(offset)
order("mostRecentPaymentDate", "desc")
order("totalAmount", "desc")
if(params.admin_id){
eq("adminId",params.admin_id)
}
if(params.status){
eq("status",params.status.toUpperCase() as PpCase.Status)
}
if(params.date_from){
ge('dateCreated', Date.parse("yyyy-MM-dd",params.date_from))
}
if(params.date_to){
le('dateCreated', Date.parse("yyyy-MM-dd",params.date_to))
}
if(params.date_closed_from){
ge('closedDate', Date.parse("yyyy-MM-dd",params.date_closed_from))
}
if(params.date_closed_to){
le('closedDate', Date.parse("yyyy-MM-dd",params.date_closed_to))
}
}
}
def cases2 = PpCase.createCriteria().list{
and{
firstResult(offset)
order("mostRecentPaymentDate", "desc")
order("totalAmount", "desc")
if(params.admin_id){
eq("adminId",params.admin_id)
}
if(params.status){
eq("status",params.status.toUpperCase() as PpCase.Status)
}
if(params.date_from){
ge('dateCreated', Date.parse("yyyy-MM-dd",params.date_from))
}
if(params.date_to){
le('dateCreated', Date.parse("yyyy-MM-dd",params.date_to))
}
if(params.date_closed_from){
ge('closedDate', Date.parse("yyyy-MM-dd",params.date_closed_from))
}
if(params.date_closed_to){
le('closedDate', Date.parse("yyyy-MM-dd",params.date_closed_to))
}
}
}
More real example:
In cases I need some fields, but with a limit , and in cases2 I need distinct fields, and witouth a limit,I´m duplicating the 90% of the code.
def cases = PpCase.createCriteria().list{
projections {
sum("field1")
countDistinct("id")
}
and{
if(limit){
maxResults(limit)
}
//HERE SHOULD BE THE CODE THAT I NEED TO REUTILIZE
}
def cases2 = PpCase.createCriteria().list{
projections {
sum("field2")
countDistinct("id")
}
and{
//HERE SHOULD BE THE CODE THAT I NEED TO REUTILIZE
}
I'd use "where" queries, which use DetachedCriteria under the hood, or DetachedCriteria directly. where queries don't run until you call list/get/count/exists/deleteAll/updateAll, so they're great candidates for composing queries in parts. Named queries can similarly be composed, but they need to be complete, runnable queries, but a partial where query is fine as long as you add the missing parts via composition before running them. They're also more flexible since you can use them to delete and update.
I'm not sure what you mean when you say "unify both createCriteria is not an option", especially considering in the code above you are executing the exact same query twice and ending up with two lists containing the same rows.
One way to reuse your criteria is to just define the criteria as a seperate closure, and pass that to createCriteria.
Closure fetchPayments = {
and{
if(limit){
maxResults(limit)
}
firstResult(offset)
order("mostRecentPaymentDate", "desc")
order("totalAmount", "desc")
if(params.admin_id){
eq("adminId",params.admin_id)
}
if(params.status){
eq("status",params.status.toUpperCase() as PpCase.Status)
}
if(params.date_from){
ge('dateCreated', Date.parse("yyyy-MM-dd",params.date_from))
}
if(params.date_to){
le('dateCreated', Date.parse("yyyy-MM-dd",params.date_to))
}
if(params.date_closed_from){
ge('closedDate', Date.parse("yyyy-MM-dd",params.date_closed_from))
}
if(params.date_closed_to){
le('closedDate', Date.parse("yyyy-MM-dd",params.date_closed_to))
}
}
}
def cases = PpCase.createCriteria().list(fetchPayments)
def cases2 = PpCase.createCriteria().list(fetchPayments)
You can do this by using named query :
have look to documentation .namedQueries
Related
I have a domain class
class Url {
UUID id
String url
static hasMany = [
indications:UrlIndication
]
...
}
And
class UrlIndication {
UUID id
String name
static belongsTo = Url
...
}
I want to choose urls so that it has all the necessary UrlIndication elements in a given list indicationsId.
For that I use an association and criteria like this one:
indications {
and {
indicationsId.each{
indication->
eq ('id',UUID.fromString(indication as String))
}
}
}
However, all I got is an empty result. Can you suggest any modifications/ other methods so that I can do this? Thanks in advance
Your query returned an empty list because it's the equivalent of the expression (pseudo-code): if 1 = 1 and 1 = 2 and 1 = 3
Such an expression would always be false. in or inList would not work for the reason #innovatism described.
In theory, Criteria's eqAll() or HQL's = ALL would work. But, I don't know for sure because I could not get either one to work.
What will work is to use inList to return a subset of Urls: those which contain at least one of the UrlIndication IDs. Then use Groovy's containsAll() to finish the job.
def ids = indicationsId.collect { UUID.fromString(it as String) }
Url.createCriteria()
.buildCriteria {
indications {
inList 'id', ids
}
}
.setResultTransformer(org.hibernate.Criteria.DISTINCT_ROOT_ENTITY)
.list()
.findAll {
it.indications.id.containsAll(ids)
}
Since the query has the potential to return duplicate Url instances, the ResultTransformer is set to return a unique list.
Finally, findAll() is used along with containsAll() to filter the list further.
Using eqAll (maybe)
Something like the following might work. Something funky is going on with Grails' HibernateCriteriaBuilder that causes the eqAll method to look up properties in the root entity; completely ignoring the sub criteria. So the following uses Hibernate directly. It didn't work for me, but it's as close as I could get. And it gave me a head-ache!
Url.createCriteria().buildCriteria {}
.createCriteria('indications', 'i')
.add(org.hibernate.criterion.Property.forName('i.id').eqAll(org.hibernate.criterion.DetachedCriteria.forClass(UrlIndication)
.add(org.hibernate.criterion.Restrictions.in('id', ids))
.setProjection(org.hibernate.criterion.Property.forName('id'))
))
.setResultTransformer(org.hibernate.Criteria.DISTINCT_ROOT_ENTITY)
.list()
The problem I had is I could not get Restrictions.in to work. Restrictions.eq works fine.
the in clause should do:
indications {
'in' 'id', indicationsId.collect{ UUID.fromString indication.toString() }
}
How do I write a createCriteria in grails which pull only few columns from the table instead of all columns?
I have a table called Ads. I want to retrieve only columns "Title" , "Price" and "Photo".
def c = Classified.createCriteria()
def records = c.list {
eq('publish_date', '2014-06-06')
}
maxResults(8)
}
Above query retrieves all the records. How to restrict to only few columns?
You can use projections to achieve this - at the simplest
projections {
property('title')
property('price')
property('photo')
}
would cause c.list to return a list of three-element lists, where records[n][0] is the title, records[n][1] is the price etc. If you want to be able to access the properties by name rather than by number then you need to assign aliases and use a result transformer
import org.hibernate.transform.AliasToEntityMapResultTransformer
def c = Classified.createCriteria()
def records = c.list {
eq('publish_date', '2014-06-06')
maxResults(8)
projections {
// first param is the property name, second is the alias definition -
// typically you'd leave them the same but here I make them different
// for demonstration purposes
property('title', 'ttl')
property('price', 'cost')
property('photo', 'picture')
}
resultTransformer(AliasToEntityMapResultTransformer.INSTANCE)
}
Now records will be a list of maps rather than a list of lists, and you can access the projected properties by alias name - records[n].ttl, records[n].cost, etc.
Try this:
def records = Classified.withCriteria {
eq('publish_date', '2014-06-06')
projections {
property('title')
property('price')
property('photo')
}
maxResults(8)
}
What is their difference and why and where we need to use them,i think it seems like they have no difference at all to me ?
withCriteria { ... } is essentially shorthand for createCriteria().list { ... }. If you need to use any of the other criteria methods (get, count, ...) or pass pagination parameters to list then you have to use the long-hand form.
SomeDomain.createCriteria().list(max:10, offset:50) {
// ...
}
It's worth adding what I just came across in the grails documentation for createCriteria().
Because that query includes pagination parameters (max and offset), this will return a PagedResultList which has a getTotalCount() method to return the total number of matching records for pagination. Two queries are still run, but they are run for you and the results and total count are combined in the PagedResultList.
Source
This means you can use getTotalCount() without having to initiate the call (it's made for you). This is very helpful. The example documentation shows:
def c = Account.createCriteria()
def results = c.list (max: 10, offset: 10) {
like("holderFirstName", "Fred%")
and {
between("balance", 500, 1000)
eq("branch", "London")
}
order("holderLastName", "desc")
}
println "Rendering ${results.size()} Accounts of ${results.totalCount}"
This capability is not available when using withCriteria().
Example of createCriteria():
def criteria = OfferCredit.createCriteria {
offer {
eq('status', LeverageUtils.ACTIVE_STATUS)
ge('expirationDate', new Date())
}
user {
eq('userId', userId)
}
eq('status', LeverageUtils.ACTIVE_STATUS)
order('creationDate', 'asc')
}
criteria.list()
Example of withCriteria():
List<Supermarket> results = Supermarket.withCriteria {
like("sp_street", params.street)
productSupermarket {
product {
idEq(params.product)
}
// or just eq('product', someProduct)
}
maxResults(10)
}
withCriteria executes and returns the list. It provides a closure using which you can customize the criteria before it gets executed.
createCriteria just creates a criteria object which you can modify and then explicitly call the list method to execute.
If criteria is simple or if it is defined in a single place it is better to use withCriteria.
If you need to pass the criteria around (create it in one function and pass it to others) createCriteria would be better. I think withCriteria support is limited.
withCriteria ->
Purpose -> Allows inline execution of Criteria queries.
If no matching records are found, an empty List is returned.
If a projection is specified:
returns a single value if it only contains one field
a List in case there are multiple fields in the projection
Imagine I have the following query:
def result = Test.createCriteria().list(params) {
// image here a lot of assocs, criterias, ...
}
In many cases you need the row count of the query above, e.g. list actions of many controllers.
def resultTotal = Test.createCriteria().list(params) {
// Imagine here a lot of assocs, criterias, ...
// Change to the criteria above is only the projection block
projections { rowCount() }
}
How can I avoid this?
You can:
Extract the Criteria creation to a conditional factory/builder method;
Use named queries;
Use named query parameters (and Groovy code!) to alter the query "on the fly", like:
.
static namedQueries = {
byLocation { Location location ->
if (location) {
... // some more criteria logic
}
}
}
If you aren't paginating the results of the query, you can simply do the following after the first query is invoked:
def resultTotal = result?.size()
For the same set of query being used at many places, I create them as a closure and change its delegate to the criteria in question.
For example :
def query = {
projections{
rowCount()
}
eq('type', myType)
}
def criteria = Customer.createCriteria()
query.delegate = criteria
myType = CustomerType.guest
List records = criteria.list(params) {
query()
}
I use totalCount like this:
def list() {
def myInstanceList = MyInstance.createCriteria().list(params){
eq('name', params.name)
}
[myInstanceList: myInstanceList, myInstanceListTotal: myInstanceList.totalCount]
}
I have two very similar methods in Grails, something like "calculate statistics by os" and "calculate statistics by browser" - effectively both prepare some things, then run a similar query on the DB, then do things with the results. The only part where the methods differ is the query they run in the middle of my method -
def summary = c.list {
eq('browser', Browser.get(1)) // OR eq('os', OS.get(1))
between('date', dates.start, dates.end)
}
It occurred to me that the ideal way to refactor it would be to pass in the first line of the closure as a method parameter. Like
doStats (Closure query) {
...
def summary = c.list {
query
between('date', dates.start, dates.end)
}
}
I tried this but "query" gets ignored. I tried query() instead but then the query clause is executed where defined, so this doesn't work either. I suppose I could just pass the whole closure as a parameter but that seems wrong - the query might also get more complicated in future.
Anyone have any better ideas?
I found leftShift operator useful for composing closure from two separate ones. What you can do is:
Closure a = { /*...*/ }
Closure b = { /*...*/ }
Closure c = a << b
Take a look at this example:
def criteria = {
projection Projections.distinct(Projections.property('id'))
and {
eq 'owner.id', userDetails.id
if (filter.groupId) {
eq 'group.id', filter.groupId
}
}
}
List<Long> ids = Contact.createCriteria().list(criteria << {
maxResults filter.max
firstResult filter.offset
})
Integer totalCount = Contact.createCriteria().count(criteria)
What you can see here is that I'm creating a criteria for listing ant counting GORM objects. Criterias for both cases are almost the same, but for listing purposes I also need to include limit and offset from command object.
You're using the criteria DSL which might be different than plain groovy closures.
To do what you're asking, you can use the method described here -
http://mrhaki.blogspot.com/2010/06/grails-goodness-refactoring-criteria.html
and put your query in to private method.
The more elegant solution for this is to use named queries in grails -
http://grails.org/doc/latest/ref/Domain%20Classes/namedQueries.html
Look at the
recentPublicationsWithBookInTitle {
// calls to other named queries…
recentPublications()
publicationsWithBookInTitle()
}
example -
Not sure about with the Grails Criteria builder, but with other builders, you can do something like:
doStats (Closure query) {
def summary = c.list {
query( it )
between('date', dates.start, dates.end)
}
}
And call this via:
def f = { criteria ->
criteria.eq( 'browser', Browser.get( 1 ) )
}
doStats( f )
If not, you're probably best looking at named queries like tomas says