As many readers may know we are developing and maintaining some Grails applications for more than 10 years now. One of the main selling points of Grails is its domain model and object-relational-mapper (ORM) called GORM.
In general ORMs are useful for easy and convenient development at the cost of a bit of performance and flexibility. One of the best features of GORM is the availability of several flexible APIs for use-cases where dynamic finders are not enough. Let us look at a real-world example.
The performance problem
In one part of our application we have personal messages that are marked as read after viewing. For some users there can be quite a lot messages so we implemented a “mark all as read”-feature. The naive implementation looks like this:
def markAllAsRead() {
def user = securityService.loggedInUser
def messages = Messages.findAllByUserAndTimelineEntry.findAllByAuthorAndRead(user, false)
messages.each { message ->
message.read = true
message.save()
}
Messages.withSession { session -> session.flush()}
}
While this is both correct and simple it only works well for a limited amount of messages per user. Performance will degrade because all the domain objects are loaded into domain objects, then modified and save one-by-one to the session. Finally the session is persisted to the database. In our use case this could take several seconds which is much too long for a good user experience.
DetachedCriteria to the rescue
GORM offers a much better solution for such use-cases that does not sacrifice expressiveness. Instead it offers a succinct API called “Where Queries” that creates DetachedCriteria
and offers batch-updates.
def markAllAsRead() {
def user = securityService.loggedInUser
def messages = Messages.where {
read == false
addressee == user
}
messages.updateAll(read: true)
}
This implementation takes only a few milliseconds to execute with the same dataset as above which is de facto native SQL performance.
Conclusion
Before cursing GORM for bad performance one should have a deeper look at the alternative querying APIs like Where Queries, Criteria, DetachedCriteria, SQL Projections and Restrictions to enhance your ORM toolbox. Compared to dynamic finders and GORM-methods on domain objects they offer better composability and performance without resorting to HQL or plain SQL.