Reputation: 298
I'm seeing some strange behavior that I believe deals with GORM caching, but I don't know how to fix the issue. I have a project object with a list of purchaseOrderEntries that each have a start date. If the user enters a two digit year in the Purchase Order state date, I convert it to a 4 digit year and then sort the dates. But they are not sorting correctly! Here is the important snippet:
def updatePurchaseOrderEntryDates(def project) {
def poEntryList = project.purchaseOrderEntries
poEntryList?.each { DateHelper.updateTwoDigitYear(it?.startDate) } // 2-digit yr
poEntryList?.sort()
log.info "list 1: " + poEntryList
poEntryList?.sort()
log.info "list 2: " + poEntryList
Which outputs the following:
list 1: 1965, 2020, null
list 2: 2020, 1965, null
How can calling the same sort method produce different results the second time? What am I missing? I feel like this is a hibernate lazy load or caching or something issue. No clue. Any ideas?
* UPDATE W/ MORE INFO * Thanks for all the ideas. Unfortunately, the solution just doesn't seem to be that easy. The sort is working correctly, it just just seem to work every time. This is why it seems to be an issue with something I don't know much about. The project object contains a List of purchase order entries - this is a list (not a set) and the PurchaseOrderEntry object implements Comparable. Here is the code:
class Project ... {
static hasMany = [purchaseOrderEntries:ProjectPurchaseOrderEntry, ...]
static mapping = {
purchaseOrderEntries cascade:'all-delete-orphan'
...
}
List<ProjectPurchaseOrderEntry> purchaseOrderEntries
...
}
class ProjectPurchaseOrderEntry ... implements Comparable {
Project project
Date startDate
Date endDate
...
int compareTo(obj) {
// Compare these dates in reverse older. i.e. put the newest/earliest date on the top and the oldest/null date at the end of the list
return obj.startDate <=> startDate // this comparison is done in reverse - comparator operator is null safe
}
...
}
In other words, the sort seems to work fine. The issue is here
poEntryList?.each { DateHelper.updateTwoDigitYear(it?.startDate) } // 2-digit yr
poEntryList?.sort()
Even though all the dates in the list are updated to a 4-digit year, it still sorts as though those dates have not been been updated. I suspect that the 2-digit year is a Julian Calendar date the 4-digit year is a Gregorian Calendar date and that my comparison operator is failing silently. BUT.... Why wouldn't the dates have been updated already since they were in the previous each loop? That is what I'm not getting. Is the each doing a SQL query to lazy fetch the project.purchaseOrderEntries and then using this again (instead of the newly sorted list)? Even if I hold the results of the sort in a new list such as:
poEntryList?.each { DateHelper.updateTwoDigitYear(it?.startDate) } // support two digit year entry
def sortedList = poEntryList?.sort({it?.startDate})?.reverse()
the newly "sortedList" is still not sorted correctly. The only way I see this as possible is if it isn't using the updated dates for its sort. Again, thanks for all your help. Any ideas are welcome. This one is driving me crazy.
Upvotes: 0
Views: 921
Reputation: 714
This issue has bitten me several times. Check the return type - it is likely a Set. You need to convert to an ArrayList and then sort. Try the following:
def sortedList = poEntryList?.toArray().sort { it.startDate }
Upvotes: 0
Reputation: 12228
The hasMany
relationships in Grails use the datatype Set
. A set has no order by design, so without telling it what to order by, it does nothing.
Update after you updated your post.
1) List<ProjectPurchaseOrderEntry> purchaseOrderEntries
can be simplified to List purchaseOrderEntries
.
2) A .each{ .. }
does NOT return the collection. It is simply a loop. You want .collect{..}
if you want the items in the array to be updated by the code in the closure.
3) A hasMany
of type List
is not intended to implement Comparable and thus does not need the overrides. It sorts by the order the elements were added to the list.
4) Once you do a .collect{ .. }
you no longer have a list of your domain objects, therefore whatever sorting you have set up will not be used. My opinion is to use the standard Set datatype then do your collect, then say .sort{it.startDate}.reverse()
Upvotes: 0
Reputation: 122364
The Groovy-JDK Collection.sort()
method only sorts in-place if the collection on which it is called is a List
. Since hasMany
relations are Set
by default
poEntryList.sort()
leaves poEntryList
unchanged and returns a new List
in sorted order (where "sorted" here means according to the compareTo
ordering of the objects in the collection). If you logged the List
returned by sort()
rather than the original collection you should see it in sorted order consistently (assuming of course that the objects have a meaningful compareTo
method).
Upvotes: 0
Reputation: 3723
Hibernate doesn't provide any sort order by default. So I assume that it works fine, and your sort() method does nothing. You should provide sort order, like this:
def sortedList = poEntryList?.sort { it.startDate }
Upvotes: 1