I have a dummy cafeteria project wherein I have three domain classes: User, Product and Transaction.
Here's how the classes are defined:
class User {
String name
int employeeId
long balance = 800
static constraints = {
balance(max: 800L)
}
}
class Product {
String type
int quantityInStock
float price
static constraints = {
}
}
class Transaction {
int quantityBought
static belongsTo = [user: User, product: Product]
static constraints = {
}
}
Now I want to find out the number of/list of users who have bought more than, say 2 products.
How do I do it using Grails createCriteria?
This is what I tried:
Transaction.createCriteria().list(){
projections {
groupProperty('user')
'product' {
count('id','numberOfPurchases')
}
}
gt('numberOfPurchases',2)
}
But it gives the following error:
Stacktrace:
org.hibernate.QueryException: could not resolve property: numberOfPurchases of: cafeteria.dummy.Transaction
at grails.orm.HibernateCriteriaBuilder.invokeMethod(HibernateCriteriaBuilder.java:1618)
at Script1.run(Script1.groovy:4)
at org.grails.plugins.console.ConsoleService.eval(ConsoleService.groovy:37)
at org.grails.plugins.console.ConsoleController.execute(ConsoleController.groovy:59)
at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:195)
at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
How can I access this alias numberOfPurchases outside the projections block so as to use it in gt?
Internally GORM uses Hibernate and currently Hibernate doesn't have support for having clause in Criteria API. The reason you are not able to get numberOfPurchases outside projections block is that the restrictions are added to where clause and you can't use an aggregation alias in where clause.
Although JPA has support for having clause.
But if you still want to achieve this using GORM Criteria you can. But you would have to modify your query a little bit and it would not involve use of having clause and will require a subquery.
Modified query will be:
SELECT DISTINCT this_.user_id FROM transaction this_ where 2 < (SELECT count(sub_.id) FROM transaction sub_ WHERE sub_.user_id=this_.user_id)
And GORM for it would be:
import org.hibernate.criterion.DetachedCriteria
import org.hibernate.criterion.Projections
import org.hibernate.criterion.Restrictions
import org.hibernate.criterion.Subqueries
DetachedCriteria subQuery = DetachedCriteria.forClass(Transaction, 'sub').with {
setProjection Projections.count('sub.id')
add Restrictions.eqProperty('sub.user', 'this.user')
}
List<User> users = Transaction.createCriteria().list() {
projections {
distinct("user")
}
add Subqueries.lt(2l, subQuery)
}
Related
I needed to create a custom join table to re-model a many-to-many mapping and following some great posts on here came up with the model below.
Now my question is, if I have either a Course or Journey object in a GSP, how do I access the extra column data belonging to the join table.
In this example, I want to access the field named extraColumn1 in the CourseJourneyDetail within my GSP if I have either a journey or course instance
I've tried the following :
${course.courseJourneyDetail.extraColumn1}
but it didn't work.
Here are (relevant parts of) my domain classes :
class Course {
static hasMany = [journies: CourseJourneyDetail]
String courseName
String organisersDescription
Set<Journey> getJournies() {
return CourseJourneyDetail.findAllByCourse(this)*.journey
}
}
class Journey {
static hasMany = [courses: CourseJourneyDetail]
java.util.Date dateCreated
java.util.Date lastUpdated
boolean enabled = true
User user
Set<Course> getCourses() {
return CourseJourneyDetail.findAllByJourney(this)*.course
}
}
class CourseJourneyDetail implements Serializable {
String extraColumn1
static belongsTo = [course: Course, journey: Journey]
boolean equals(other) {
if (!(other instanceof CourseJourneyDetail)) {
return false
}
other.journey?.id == journey?.id &&
other.course?.id == course?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (course) builder.append(course.id)
if (journey) builder.append(journey.id)
builder.toHashCode()
}
static constraints = {
}
static mapping = {
version false
id composite: ['course', 'journey']
}
}
Since you've established that each Course/Journey has a collection of CourseJourneyDetail's rather than a single instance, ${course.courseJourneyDetail.extraColumn1} won't work (as you've discovered).
If you break down your groovy expression into this: course.courseJourneyDetail, it doesn't really make sense based on the relationships you have created. The reason being, Course doesn't have a single CourseJourneyDetail but rather a collection.
If your desire is to have Course and Journey in a one-to-one relationship, but with a join table with additional columns, then your domain structure needs to change to reflect this: rather than using static hasHany in each class, you would switch to a single instance.
If your desire is to keep the many-to-many relationship, then you need to think about how to fetch the appropriate join object that represents the association. One example:
CourseJourneyDetail.findAllByCourseAndJourney(<courseInstance>, <journeyInstance>)
If you want the additional columns collected for all of the many-to-many associations, you can use a syntax that you are already using in your convience methods (getJournies and getCourses):
course.journies*.extraColumn1
This would output an array of Strings, so its usage makes less sense within a ${} expression; and more sense within a g:each. It entirely depends on how you plan on using this data.
I need to map Domain classes and subclasses of a legacy database.
The model thath I need to recreate with Grails is to this tables:
Tables structure
CARD_PAYMET and CHEQUE_PAYMENT is subclasses of PAYMENT and share a composite key of two field: OrderId and PaymentId.
I try some ex scenarios, but I can´t arrive to solution. None of then recreate the same model data, and I can´t change this model.
Can any one help me?
Thanks.
Your database looks like a good fit for table-per-subclass inheritance. First, since you're using a composite primary key, your domain classes need to implement the Serializable interface. Then it's a matter of mapping each table column to a property.
import groovy.transform.EqualsAndHashCode
#EqualsAndHashCode(includes=['orderId', 'paymentId'])
class Payment implements Serializable {
int orderId
int paymentId
float amount
static mapping = {
version false
tablePerHierarchy false
id composite: ['orderId', 'paymentId']
orderId column: 'OrderId'
paymentId column: 'PaymentId'
/* Assuming case-insensitive db, so I left out 'Amount'. */
}
}
class CardPayment extends Payment {
String cardType
static mapping = {
version false
cardType column: 'CardType'
}
}
class ChequePayment extends Payment {
int checkNumber
static mapping = {
version false
checkNumber column: 'CheckNumber'
}
}
Note: In this example I used Groovy's EqualsAndHashCode AST transformation to implement Serializable.
With the domain classes in place you'll be able to do GORM polymorphic queries:
def payments = Payment.list() // All Payments (Payment, CardPayment, and ChequePayment).
def cardPayments = CardPayment.list() // Only CardPayments.
...
def nicePayments = Payment.where { amount > 1000 }.list()
In the following example, I'd expect Product.searchAll to match both
additives and products, but it seems to ignore eq('name', taste).
class Additive {
String flavor
static belongsTo = [product:Product]
}
class Product {
String name
static hasMany = [additives:Additive]
static constraints = {
name nullable:true
}
static namedQueries = {
searchAll { taste ->
or {
eq('name', taste)
additives { eq('flavor', taste) }
}
}
searchAdditives { taste ->
additives { eq('flavor', taste) }
}
searchProducts { taste ->
eq('name', taste)
}
}
}
class SearchSpec extends grails.plugin.spock.IntegrationSpec {
def choc, muff
def 'searchAll should return products and additives that match - THIS FAILS'() {
setup:
createTestProducts()
expect:
Product.searchAll("chocolate").list() == [choc, muff]
}
def 'searchProducts should return only products that match - THIS PASSES'() {
setup:
createTestProducts()
expect:
Product.searchProducts("chocolate").list() == [choc]
}
def 'searchAdditives should return only additives that match - THIS PASSES'() {
setup:
createTestProducts()
expect:
Product.searchAdditives("chocolate").list() == [muff]
}
private def createTestProducts() {
// create chocolate
choc = new Product(name:'chocolate').save(failOnError:true, flush:true)
// create a chocoloate-flavored muffin
muff = new Product(name:'muffin').addToAdditives(flavor:'chocolate').save(failOnError:true, flush:true)
}
}
The SQL generated is as follows:
select this_.id as id1_1_, this_.version as version1_1_,
this_.name as name1_1_, additives_1_.id as id0_0_,
additives_1_.version as version0_0_, additives_1_.flavor as
flavor0_0_, additives_1_.product_id as product4_0_0_ from product
this_ inner join additive additives_1_ on
this_.id=additives_1_.product_id where (this_.name=? or
(additives_1_.flavor=?))
Is there something wrong with my syntax, or is this a problem with Grails, GORM or H2?
My guess, quickly looking at your query, is that Grails / GORM is performing an inner join. An inner join only matches if a relationship exists between the tables. In the example above, that query will never match choc, because choc does not have any associated additives.
So, it's not the or that's failing, it's the actual query. Fire up localhost:8080/{yourapp}/dbConsole and run that same query, but without the where statement. You should see that you only get products with one or more additives.
I believe (not tested) you can force a LEFT JOIN using syntax like this:
import org.hibernate.criterion.CriteriaSpecification
...
searchAll { taste ->
createAlias("additives", "adds", CriteriaSpecification.LEFT_JOIN)
or {
eq('name', taste)
eq('adds.flavor', taste)
}
}
This should force a left (or outer) join, allowing for products that do not have a matching additive. Note: It's possible to get duplicate results when using outer joins, but this depends on your particular usage scenario.
I'm writing some named queries for my domain classes in Grails and I've hit a blocker.
Given the following domain class:
class Contributor {
// evals is a collection of another domain class
def evals
static namedQueries = {
hasNoEvals {
// Something like this...
evals.size() == 0
}
}
}
Can anyone help with the syntax I need to select the Contributors who have no Evals?
Thanks.
Please look in createCriteria doc for "collection property" operations. In your case, it is isEmpty:
static namedQueries = {
hasNoEvals {
isEmpty('evals')
}
}
For generic size restriction, it is sizeEq, sizeLe and so on.
Let's say I have definied a User object using GORM. Each user can have zero or more Login:s. Each Login has a timestamp. When retrieving user.logins I want the logins to be sorted based on the value of login.date. What is the correct Grails way to achieve this?
Example: I want the following code to list all the user's logins in ascending order.
<g:each var="login" in="${user.logins}">
<tr>
<td>${login.date}</td>
</tr>
</g:each>
These are the referenced classes:
class User {
...
def hasMany = [logins: Login]
static fetchMode = [logins: "eager"]
}
class Login {
Date date
...
def belongsTo = [User]
}
I'm running Grails 1.0.4 which is the latest stable release.
They show how to do this on the GORM page in the reference guide (section 5). The bit you want is near the bottom of that document is the section you want. They have two simple examples:
class Airport {
…
static mapping = {
sort "name"
}
}
class Airport {
…
static mapping = {
sort name:"desc"
}
}
They also have an example of sorting on an association:
class Airport {
…
static hasMany = [flights:Flight]
static mapping = {
flights sort:'number'
}
}
Just make the Login Class implement the Comparable interface:
class Login implements Comparable {
// ...
Date date
public int compareTo(def other) {
return date <=> other?.date // <=> is the compareTo operator in groovy
}
}
and declare the relation to be a SortedSet:
class User {
...
def hasMany = [logins: Login]
SortedSet logins
static fetchMode = [logins: "eager"]
}
The handling of default sort order in Grails/GORM seems to have been radically simplified in Grails 1.1:
Grails 1.1 release notes (search for "Default Sort Order")