7.3 Persistence Basics - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.1.7
Table of Contents
7.3 Persistence Basics
A key thing to remember about Grails is that under the surface Grails is using Hibernate for persistence. If you are coming from a background of using ActiveRecord or iBatis/MyBatis, Hibernate's "session" model may feel a little strange.Grails automatically binds a Hibernate session to the currently executing request. This lets you use the save and delete methods as well as other GORM methods transparently.Transactional Write-Behind
A useful feature of Hibernate over direct JDBC calls and even other frameworks is that when you call save or delete it does not necessarily perform any SQL operations at that point. Hibernate batches up SQL statements and executes them as late as possible, often at the end of the request when flushing and closing the session. This is typically done for you automatically by Grails, which manages your Hibernate session.Hibernate caches database updates where possible, only actually pushing the changes when it knows that a flush is required, or when a flush is triggered programmatically. One common case where Hibernate will flush cached updates is when performing queries since the cached information might be included in the query results. But as long as you're doing non-conflicting saves, updates, and deletes, they'll be batched until the session is flushed. This can be a significant performance boost for applications that do a lot of database writes.Note that flushing is not the same as committing a transaction. If your actions are performed in the context of a transaction, flushing will execute SQL updates but the database will save the changes in its transaction queue and only finalize the updates when the transaction commits.7.3.1 Saving and Updating
An example of using the save method can be seen below:def p = Person.get(1) p.save()
def p = Person.get(1)
p.save(flush: true)
def p = Person.get(1) try { p.save(flush: true) } catch (org.springframework.dao.DataIntegrityViolationException e) { // deal with exception }
save()
will simply return null
in this case, but if you would prefer it to throw an exception you can use the failOnError
argument:def p = Person.get(1) try { p.save(failOnError: true) } catch (ValidationException e) { // deal with exception }
application.groovy
, as described in the section on configuration. Just remember that when you are saving domain instances that have been bound with data provided by the user, the likelihood of validation exceptions is quite high and you won't want those exceptions propagating to the end user.You can find out more about the subtleties of saving data in this article - a must read!
7.3.2 Deleting Objects
An example of the delete method can be seen below:def p = Person.get(1) p.delete()
flush
argument:def p = Person.get(1)
p.delete(flush: true)
flush
argument lets you catch any errors that occur during a delete. A common error that may occur is if you violate a database constraint, although this is normally down to a programming or schema error. The following example shows how to catch a DataIntegrityViolationException
that is thrown when you violate the database constraints:def p = Person.get(1)try { p.delete(flush: true) } catch (org.springframework.dao.DataIntegrityViolationException e) { flash.message = "Could not delete person ${p.name}" redirect(action: "show", id: p.id) }
deleteAll
method as deleting data is discouraged and can often be avoided through boolean flags/logic.If you really need to batch delete data you can use the executeUpdate method to do batch DML statements:Customer.executeUpdate("delete Customer c where c.name = :oldName", [oldName: "Fred"])
7.3.3 Understanding Cascading Updates and Deletes
It is critical that you understand how cascading updates and deletes work when using GORM. The key part to remember is thebelongsTo
setting which controls which class "owns" a relationship.Whether it is a one-to-one, one-to-many or many-to-many, defining belongsTo
will result in updates cascading from the owning class to its dependant (the other side of the relationship), and for many-/one-to-one and one-to-many relationships deletes will also cascade.If you do not define belongsTo
then no cascades will happen and you will have to manually save each object (except in the case of the one-to-many, in which case saves will cascade automatically if a new instance is in a hasMany
collection).Here is an example:class Airport { String name static hasMany = [flights: Flight] }
class Flight { String number static belongsTo = [airport: Airport] }
Airport
and add some Flight
s to it I can save the Airport
and have the updates cascaded down to each flight, hence saving the whole object graph:new Airport(name: "Gatwick") .addToFlights(new Flight(number: "BA3430")) .addToFlights(new Flight(number: "EZ0938")) .save()
Airport
all Flight
s associated with it will also be deleted:def airport = Airport.findByName("Gatwick")
airport.delete()
belongsTo
then the above cascading deletion code would not work. To understand this better take a look at the summaries below that describe the default behaviour of GORM with regards to specific associations. Also read part 2 of the GORM Gotchas series of articles to get a deeper understanding of relationships and cascading.Bidirectional one-to-many with belongsTo
class A { static hasMany = [bees: B] }
class B { static belongsTo = [a: A] }
belongsTo
then the cascade strategy is set to "ALL" for the one side and "NONE" for the many side.Unidirectional one-to-many
class A { static hasMany = [bees: B] }
class B { }
Bidirectional one-to-many, no belongsTo
class A { static hasMany = [bees: B] }
class B { A a }
belongsTo
then the cascade strategy is set to "SAVE-UPDATE" for the one side and "NONE" for the many side.Unidirectional one-to-one with belongsTo
class A { }
class B { static belongsTo = [a: A] }
belongsTo
then the cascade strategy is set to "ALL" for the owning side of the relationship (A->B) and "NONE" from the side that defines the belongsTo
(B->A)Note that if you need further control over cascading behaviour, you can use the ORM DSL.
7.3.4 Eager and Lazy Fetching
Associations in GORM are by default lazy. This is best explained by example:class Airport { String name static hasMany = [flights: Flight] }
class Flight { String number Location destination static belongsTo = [airport: Airport] }
class Location { String city String country }
def airport = Airport.findByName("Gatwick") for (flight in airport.flights) { println flight.destination.city }
Airport
instance, another to get its flights, and then 1 extra query for each iteration over the flights
association to get the current flight's destination. In other words you get N+1 queries (if you exclude the original one to get the airport).Configuring Eager Fetching
An alternative approach that avoids the N+1 queries is to use eager fetching, which can be specified as follows:class Airport { String name static hasMany = [flights: Flight] static mapping = { flights lazy: false } }
flights
association will be loaded at the same time as its Airport
instance, although a second query will be executed to fetch the collection. You can also use fetch: 'join'
instead of lazy: false
, in which case GORM will only execute a single query to get the airports and their flights. This works well for single-ended associations, but you need to be careful with one-to-manys. Queries will work as you'd expect right up to the moment you add a limit to the number of results you want. At that point, you will likely end up with fewer results than you were expecting. The reason for this is quite technical but ultimately the problem arises from GORM using a left outer join.So, the recommendation is currently to use fetch: 'join'
for single-ended associations and lazy: false
for one-to-manys.Be careful how and where you use eager loading because you could load your entire database into memory with too many eager associations. You can find more information on the mapping options in the section on the ORM DSL.Using Batch Fetching
Although eager fetching is appropriate for some cases, it is not always desirable. If you made everything eager you could quite possibly load your entire database into memory resulting in performance and memory problems. An alternative to eager fetching is to use batch fetching. You can configure Hibernate to lazily fetch results in "batches". For example:class Airport { String name static hasMany = [flights: Flight] static mapping = { flights batchSize: 10 } }
batchSize
argument, when you iterate over the flights
association, Hibernate will fetch results in batches of 10. For example if you had an Airport
that had 30 flights, if you didn't configure batch fetching you would get 1 query to fetch the Airport
and then 30
queries to fetch each flight. With batch fetching you get 1 query to fetch the Airport
and 3 queries to fetch each Flight
in batches of 10. In other words, batch fetching is an optimization of the lazy fetching strategy. Batch fetching can also be configured at the class level as follows:class Flight {
…
static mapping = {
batchSize 10
}
}
7.3.5 Pessimistic and Optimistic Locking
Optimistic Locking
By default GORM classes are configured for optimistic locking. Optimistic locking is a feature of Hibernate which involves storing a version value in a specialversion
column in the database that is incremented after each update.The version
column gets read into a version
property that contains the current versioned state of persistent instance which you can access:def airport = Airport.get(10)println airport.version
def airport = Airport.get(10)try { airport.name = "Heathrow" airport.save(flush: true) } catch (org.springframework.dao.OptimisticLockingFailureException e) { // deal with exception }
The version
will only be updated after flushing the session.
Pessimistic Locking
Pessimistic locking is equivalent to doing a SQL "SELECT * FOR UPDATE" statement and locking a row in the database. This has the implication that other read operations will be blocking until the lock is released.In Grails pessimistic locking is performed on an existing instance with the lock method:def airport = Airport.get(10) airport.lock() // lock for update airport.name = "Heathrow" airport.save()
get()
and the call to lock()
.To get around this problem you can use the static lock method that takes an id just like get:def airport = Airport.lock(10) // lock for update airport.name = "Heathrow" airport.save()
def airport = Airport.findByName("Heathrow", [lock: true])
def airport = Airport.createCriteria().get {
eq('name', 'Heathrow')
lock true
}
7.3.6 Modification Checking
Once you have loaded and possibly modified a persistent domain class instance, it isn't straightforward to retrieve the original values. If you try to reload the instance using get Hibernate will return the current modified instance from its Session cache. Reloading using another query would trigger a flush which could cause problems if your data isn't ready to be flushed yet. So GORM provides some methods to retrieve the original values that Hibernate caches when it loads the instance (which it uses for dirty checking).isDirty
You can use the isDirty method to check if any field has been modified:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params if (airport.isDirty()) { // do something based on changed state }
isDirty()
does not currently check collection associations, but it does check all other persistent properties and associations.
You can also check if individual fields have been modified:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params if (airport.isDirty('name')) { // do something based on changed name }
getDirtyPropertyNames
You can use the getDirtyPropertyNames method to retrieve the names of modified fields; this may be empty but will not be null:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params def modifiedFieldNames = airport.getDirtyPropertyNames() for (fieldName in modifiedFieldNames) { // do something based on changed value }
getPersistentValue
You can use the getPersistentValue method to retrieve the value of a modified field:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params def modifiedFieldNames = airport.getDirtyPropertyNames() for (fieldName in modifiedFieldNames) { def currentValue = airport."$fieldName" def originalValue = airport.getPersistentValue(fieldName) if (currentValue != originalValue) { // do something based on changed value } }