Ok i'm wondering what is the best way to implement my scenario.
My objects are as follows:
ProjectCategory has many Projects. Projects have a ProjectStatus. ProjectStatus has a property called name and name can be either "Closed" or "Open" etc...
I'm trying to display on a page all categories and the number of opened projects for that category next to the category name.
How would I go about doing that. The problem I'm seeing is that (using grails gorm) is that by default you cannot do something like
category.findAll{ it.status.name == "Opened" }.size()
because the objects are not loaded that deep. Now If I forced them to load, now for all categories I'm potentially loading a bunch of projects just to get the status. Wouldn't the system take a huge hit in performance the higher amount of projects you have?
The thought of creating a counter in the category and updating it every time a project status changes makes me cringe.
I must just be sleep deprived because I can't see what the proper way of doing this would be. If the way I mentioned first with the .findAll is the way to go, do I really have to worry about performance? How would I go about implementing that?
Thanks in advance for all your help.
I would use HQL. Assuming Projects belong to a ProjectCategory, you could add something like this to your ProjectCategory class:
class ProjectCategory {
// Fields/Methods
def getOpenedProjectsCount() {
ProjectCategory.executeQuery("SELECT count(*) FROM Projects p WHERE p.projectCategory = :projectCategory AND p.projectStatus.name = 'Opened'", [projectCategory: this])
}
}
Then when you have a ProjectCategory instance you can use the openedProjectsCount property:
def projectCategory = ProjectCategory.get(123)
projectCategory.openedProjectsCount
Related
I just wasted half a day trying to figure this out, reading about some workarounds, and thinking "it can't be that bad - there must be a straightforward to do edit a collection in Grails, whethere using scaffolded views or my own."
Let's say I have this domain object:
class TreeGroup {
String name
List<Tree> trees
static hasMany = ['trees': MyTree]
}
Just to explain the choice of data structure - I need my records to be unique, but in the order I set. That's why I chose List, AFAIK one cannot rely on order in a Set. So there are 2 pieces to this question - 1) how to remove from any Collection, for example a Set, 2) is List the best replacement for Set in this context (preserving order).
I want to be able to create a group record with no trees in it and make 4 updates:
edit/save
edit the group record to reference 2 trees A and B
add another tree C
remove A
remove B and C
And obviously, I want the desired state after every step. Currently though, I can only add records, and if I even edit/save to list, the list elements are added to it again.
I am using the multiple select tag for this. It looks like this:
<g:select name="trees" from="${allTrees}" optionKey="id"
multiple="true" class="many-to-many"
value="${trees ? trees*.id : treeGroupInstance?.trees*.id}" />
and that's fine, in the sense that it generates an HTTP header with these variables on update:
_method:PUT
version:19
name:d5
trees:1
_action_update:Update
But the data binder only adds new elements, it never lets you edit a list.
What is the cleanest way to do it ? Is it me, not reading something obvious, or is this a design flaw of grails data binding (and of so, when/how will it be fixed) ?
Is there a way perhaps via a hidden HTTP parameter to clear the list before (re)adding elements ?
Thanks
I ended up doing this:
private repopulate(def domainObject, String propertyName, Class domainKlaz) {
if (params[propertyName] != null) {
domainObject[propertyName].clear()
domainObject[propertyName].addAll(
params[propertyName].collect { domainKlaz.get(it) }
)
}
}
and I am calling it in update controller method before save(), for every collection. OMG how ugly.
I'm trying to accelerate the performance of my app and wonder if there is a difference between accessing domain property value with instance.name and instance.getName()
If it is, which one is the best in terms of performance ?
Example
class User {
String name
}
User user = User.get(100);
//is it better this way
user.name
//or this way
user.getName()
Thank you
It doesn't matter for the usage you've provided, because user.name uses user.getName() behind scenes. So it's the same. If you want to access property directly you have to use # like this user.#name. See more here
But I don't think this is the way you can speed up your app.
It is very likely you will find a lot easier ways for improving performance of your code. Here are some ideas where to start if you like to improve performance.
A) Number of queries. Try to avoid the the N+1 problem. For example if one user hasMany [events: Event], code like user.events.each { access event.anyPropertyExceptId } will dispatch new queries for each event.
B) Efficiency of queries. Grails per default creates indexes for all gorm associations / other nested domains. However anything you use to search, filter etc. you need to do "manually" for example.
static mapping = {
anyDomainProperty index: 'customIndexName'
}
C) Only query for the data you are interested in, replace for example:
User.all.each { user ->
println user.events.size()
}
with
Event.withCriteria {
projections {
property('user')
countDistinct('id')
groupProperty('user')
}
}
D) If you really need to speed up your groovy code and your problem is rather a single request than general cpu usage, take a look at http://gpars.codehaus.org and http://grails.org/doc/2.3.8/guide/async.html and try to parallize work.
I doubt any performance issues in your application are related to how you are accessing your properties of your domain classes. In fact, if you profile/measure your application I'm sure you will see that is the case.
I'm creating an application using Grails 2.2.4 and Java 7 (these are constraints I cannot change) and I run into an odd behavior when trying to delete multiple entries in a Many-To-Many hasMany Set.
I have a class named Sport that contains the following:
Class Sport{
String name
static hasMany=[category:Category]
static belongsTo = [Category]
}
And another one named Category:
Class Category{
String name
static hasMany=[sports:Sport]
}
Now when in my CategoryController I try to delete multiple Sport instances from sports, my code compiles and runs without errors, but for reason only one of the selected instances is actually deleted.
If I get a Sport list and a Category id from a form and try to run the following code on every objet in the list:
def categoryInstance = Category.get(idCategory)
def sportInstance = Sport.get(idSport)
if(sportInstance!=null){
categoryInstance.removeFromSports(sportInstance)
}
categoryInstance.save()
Only the last instance is deleted.
If I run
def categoryInstance = Category.get(idCategory)
def sportInstance = Sport.get(idSport)
if(sportInstance!=null){
categoryInstance.removeFromSports(sportInstance)
categoryInstance.save()
}
Only the first one is deleted.
Note that this code is run from within a for loop over the params.sport.toList() list.
My guess is that this is either due to the fact that my sports Set is somehow "changed" after the first deletion and therefore Hibernate can't find the next instance, or that my save method commits the first change then "forgets" the next.
Any advice on how I can delete more than one instance at a time?
A workaround is to wrap the code for deleting a single association in a block of withNewSession + withNewTransaction
Sport.withNewSession {
Sport.withNewTransaction {
//delete 1 association here
}
}
It's not very elegant but works. Beware of a potential performance impact, as this will likely have many database roundtrips.
I don't know why this problem occurs, and googling didn't help either.
Another solution = workaround is to explicitly map the relationship as SportCategory, which you can then delete like any other object with SportCategory.delete().
I have a grails application where I have contacts which belongs to another domain contactGroup. It all seems to be working fine except for removeFromContacts method. I am using following code. The code works correctly but removes only one contact from the group at a time. I even did some debugging and the foreach loop runs as many times as the contacts provided. There is no error message. Any idea what could be going wrong -
ContactGroup group = ContactGroup.findByIdAndOwner(params.groupId, user)
def contactIds = request.JSON.data.contact
contactIds.each {
Contact contact = Contact.findByContactIdAndOwner(it.contactId, user)
if(contact) {
group.removeFromContacts(contact)
}
}
I've read a few things about the findAll methods loading proxies if the associations are lazy-loaded rather than the "real" instance.
Try this:
group.removeFromContacts(Contact.get(contact.id))
The 'get' should bypass the proxies and use the "real" instance. There is a JIRA that talks about this (Grails-5804). An overall fix according to the JIRA (from Burt Beckwith) is to implement the equals and hashCode method in your Contact class.
Thanks for all your support. I realized that I have not defined the relationship at the domain level correctly and that was messing up with the whole thing. When I corrected that it was working correctly.
saurabh
I have two object, a room type and a reservation. Simplified they are:
class Room {
String description
int quantity
}
class Reservation {
String who
Room room
}
I want to query for all rooms along with the number of rooms available for each type. In SQL this does what I want:
select id, quantity, occupied, quantity-coalesce(occupied, 0) as available
from room left join(select room_id, count(room_id) as occupied from reservation)
on id = room_id;
I'm not getting anywhere trying to work out how to do this with HQL.
I'd appreciate any pointers since it seems like I'm missing something fairly fundamental in either HQL or GORM.
The problem here is your trying to represent fields that are not your domain classes like available and occupied. Trying to get HQL\GORM to do this can be a bit a little frustrating, but not impossible. I think you have a couple options here...
1.) Build your domain classes so that there easier to use. Maybe your Room needs to know about it's Reservations via a mapping table or, perhaps write what you want the code to look like and then adjust the design.
For example. Maybe you want your code to look like this...
RoomReservation.queryAllByRoomAndDateBetween(room, arrivalDate, departureDate);
Then you would implement it like this...
class RoomReservation{
...
def queryAllByRoomAndDateBetween(def room, Date arrivalDate, Date departureDate){
return RoomReservation.withCriteria {
eq('room', room)
and {
between('departureDate', arrivalDate, departureDate)
}
}
}
2.) My second thought is... It's okay to use the database for what it's good for. Sometimes using sql in you code is simply the most effective way to do something. Just do it in moderation and keep it centralized and unit tested. I don't suggest you use this approach because you query isn't that complex, but it is an option. I use stored procedures for things like 'dashboard view's' that query millions of objects for summary data.
class Room{
...
def queryReservations(){
def sql = new Sql(dataSoruce);
return sql.call("{call GetReservations(?)}", [this.id]) //<-- stored procedure.
}
}
I'm not sure how you can describe a left join with a subquery in HQL. INn any case you can easily execute raw SQL in grails too, if HQL is not expressive enough:
in your service, inject the dataSource and create a groovy.sql.Sql instance
def dataSource
[...]
def sql= new Sql(dataSource)
sql.eachRow("...."){row->
[...]
}
I know it's very annoying when people try to patronize you into their way of thinking when you ask a question, instead of answering your question or just shut up, but in my opinion, this query is sufficiently complex that I would create a concept for this number in my data structure, perhaps an Availability table associated to the Room, which would keep count not only of the quantity but also of the occupied value.
This is instead of computing it every time you need it.
Just my $.02 just ignore it if it annoys you.