Note: for the following “solution” to be viable you don’t have to care about formatting. If you need formatting in your exported file then you will need a special library in which case I can’t help :)
There were already several Grails posts available with tips on how to get data from Grails to Excel. While the solutions were great, they weren’t very well suited to large amounts of data. Most solutions used something like JExcelApi. Even though the library is great, it doesn’t perform very well when writing large amounts of data (which is mentioned on the JExcelApi site). Creating new java objects is slow. With JExcelApi you have to create a new object for each cell (you can’t change the coordinates of a cell after creating the object so you can’t reuse an object). Since I had to export at least 30.000 rows, maybe more, each with 10 columns of data…..well….that’s a lot of objects. That was only one of the problems though. I was using Hibernate criteria for getting the data that was supposed to go into the Excel file. Usually you do something like below (this is not the actual statement, just an example):
personCriteria = Person.createCriteria()
personList = personCriteria.list {
if (params.name) {
ilike("name", "%${params.name}%")
}
if (params.dateOfBirth) {
eq("dateOfBirth", sdf.parse(params.dateOfBirth))
}
order(params.sort ?: "name", params.order ?: "asc")
}
This would return a list of people. In my case I had to get 30.000 objects from the database and query 10 properties of each object. In short……after I had my first CSV export up and running I could have put a message on the site saying “this is going to take a while, you better get a cup of coffee”. Even on a very fast machine this took ages. This was unacceptable. I have since made 2 changes to the code which sped up the export tremendously.
1) Use Hibernate projections
In the previous criteria it is basically doing something like “SELECT * FROM …..” and putting the results into objects (oversimplified but you probably get what I mean :)). Hibernate projections give you the possibility to do things like “SELECT count(*) FROM….” or “SELECT name, dateOfBirth FROM ….”. The found rows are returned in a list, not in Person objects. So what I did is specify exactly which properties I wanted to be returned:
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy")
personCriteria = Person.createCriteria()
personList = personCriteria.list {
projections {
property("name")
property("dateOfBirth")
property("phonenumber")
property(".....")
property(".....")
property(".....")
property(".....")
}
if (params.name) {
ilike("name", "%${params.name}%")
}
if (params.dateOfBirth) {
eq("dateOfBirth", sdf.parse(params.dateOfBirth))
}
order(params.sort ?: "name", params.order ?: "asc")
}
2) Use a CSV file instead of Excel or HTML
I tried using the JExcelApi to get a real Excel file and a HTML builder since Excel can read HTML tables as well. Since I had serious performance issues with both of them I decided to try a simple CSV format which turned out to work great. Creating a CSV file from the list returned by the optimized Hibernate criteria is incredibly easy in Grails. Here is the code including the part how it returns the CSV file to the browser:
response.setHeader("Content-disposition", "attachment; filename=person-list.csv")
response.contentType = "application/vnd.ms-excel"
def outs = response.outputStream
def cols = [:]
outs << "Name;Date of birth;Phonenumber;....;.....;.....;.....\n"
personList.each() {
outs << it.join(";")
outs << "\n"
}
outs.flush()
outs.close()
That’s it :). The code will probably a little more complex since you have to check for empty values (otherwise your file will contain lots of “null” text) and you’ll need some formatting in case of dates and so on but this is the basic idea. How does it perform? Well…..it went from taking 1 to 2 minutes down to about 1 second…..for 30.000 rows. I’d say that’s fast enough for most people.