Bryan Cox
Bryan Cox

Reputation: 298

Grails sort of list not producing consistent results - Possible hibernate/gorm caching issue

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

Answers (4)

Gr3go
Gr3go

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

James Kleeh
James Kleeh

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

Ian Roberts
Ian Roberts

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

Tomasz Kalkosiński
Tomasz Kalkosiński

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

Related Questions