Search code examples
hibernategrailscriteriahibernate-criteria

Grails withCriteria fetch, or a fast way to sort in predetermined order


A database function returns id's of all events within a certain radius, ordered by their distance.

Afterwards, to preserve performance I'm eagerly loading necessary collections in withCriteria like this:

    def events = Event.withCriteria {
        'in'('id', ids)
        fetchMode("someRelation", FetchMode.JOIN)
        // a few more joins
        setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
    }

However this messes up the ordering. I've noticed that the result of this criteria query returns all events sorted by id. Which does somewhat make sense since in doesn't guarantee any sort of special ordering (nor does it make any sense that it should). However this poses a bit of a problem, since I want this list to be ordered.

So what I did was this:

    List<Event> temp = [];
    ids.each { id -> temp << events.find { it.id == id } }
    events = temp;

However when the list contains ~2400 elements this piece of code adds around 1 second to total execution time which is something I wish to lower as much as possible.

Is there any other way of doing this which could speed up the process?


Solution

  • I think that there are at least three way to solve your problem (I've done additional researches and considerations):

    SQL way

    According to this gist, ordering by field is possibile also with Postgres and not only in mysql, but it's a little bit tricky since it's not directly supported. As you can read in the gist discussion there are different approaches, but I think that the cleaner and simpler is the last: adding a custom order by clause!

    ORDER BY (ID=10, ID=2, ID=56, ID=40) DESC
    

    To create a custom SQL query in Grails that returns objects with a specific type, you can follow this tutorial

    Groovy way (your current)

    // only four id, for the sake of simplicity
    def ids = [10, 2, 56, 40];
    
    // get your events with your criteria
    def events = Event.withCriteria {
        'in'('id', ids)
        fetchMode("someRelation", FetchMode.JOIN)
        // a few more joins
        setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
    }
    
    // create an array that maps event.id -> sortedEventIndex
    def indexToIdArray = [];
    // event with id 2 is at index 1
    // event with id 10 is at index 0 
    // ...
    // [null, null, 1 , null, null, null, null, null, null, null, 0, ...]
    ids.eachWithIndex{ num, idx -> indexToIdArray[$num] = $idx }
    
    def sortedEvents = [];
    events.each { ev -> sortedEvents[indexToIdArray[ev.id]] = ev }
    

    In this way you have sorted in O(n), consuming some additional memory. I don't know if this solution really performs better than your but you should give it a try.

    See also this interesting article on how to find performance issue in your groovy code.

    Client way

    Return the unsorted list of events to the client with sorting data, then sort them in the client. If a client of your application can sort the events list in more ways, I think that this solution could be useful (every sorting action is done only client side).

    the 4th, the 5th, ...

    Since this is a also a performance problem, there are other possible solutions (depends on your domain/environment):

    • Cache eagerly everything, even the ordered result
    • Use a fixed list of points for sorting (if I'm near p1, use p1 for sorting)
    • Your ideas

    I hope this helps.