Blogs

SpringSource Blog

GORM Gotchas (Part 2)

Peter Ledbrook

In part 1 of this series, I introduced you to some of the subtleties associated with persisting domain instances with GORM. This time, I'm going to tackle relationships with particular focus on hasMany and belongsTo.

GORM provides only a few basic elements for defining relationships between domain classes, but they are sufficient to describe most needs. When I give training courses on Grails, it always surprises me how few slides cover relationships. As you can imagine, this apparent simplicity does hide some subtle behaviour that can trip up the unwary. Let's start with the most basic of relationships: the many-to-one.

Many-to-one

Let's say I have the following two domain classes:

class Location {
    String city
}

class Author {
    String name
    Location location
}

When you see an Author domain class, you just know that a Book one can't be far behind. It's true, there will be a Book class too, but for now let's just concentrate on the two domain classes above and the many-to-one location relationship.

It looks simple, right? And it is. Just set the location property to a Location instance and you have linked an author to a location. But see what happens when we run the following code in the Grails console:

def a = new Author(name: "Niall Ferguson", location: new Location(city: "Boston"))
a.save()

An exception is thrown. If you look at the ultimate "caused by" exception, you'll see the message "not-null property references a null or transient value: Author.location". What's going on?

The bit about a "transient value" is the key here. A transient instance is one that isn't attached to a Hibernate session. As you can see from the code, we are setting the Author.location property to a new Location instance, not one retrieved from the database. Hence the instance is transient. The obvious fix is to make the Location instance persistent by saving it:

def l = new Location(city: "Boston")
l.save()

def a = new Author(name: "Niall Ferguson", location: l)
a.save()

So if our many-to-one properties must have persistent instances as values, why do so many GORM examples look like our original code, where we created a new Location instance? It's because domain classes usually use the belongsTo property in situations like this.

Cascading with belongsTo

Whenever you deal with relationships in Hibernate, you need to have a good grasp of what is meant by cascading. That holds true for GORM as well. Cascading determines what type of actions, when applied to a domain instance, also apply to the relations of that instance. For example, given the model above, is the author's location saved when we save the author? Is the location deleted when we delete the author? What about if we delete the location? Is the associated author also deleted?

Saves and deletes are the most common actions connected with cascading, and they're the only ones you really need to understand. So if you go back to the previous section, you'll understand that the Location instance isn't saved with the author because cascading is not in operation for that Author -> Location relationship. If we now change Location to this:

class Location {
    String city

    static belongsTo = Author
}

we will find that the exception disappears and the Location instance is saved along with the author. The belongsTo line ensures that saves are cascaded from Author to Location. As the documentation says, it also cascades deletes as well, so if you delete an author, its associated location will be deleted too. However, saving or deleting a location does not save or delete the author.

Which belongsTo?

One thing that often confuses people is that belongsTo supports two different syntaxes. The one used above simply defines cascading between two classes, while the alternative also adds a corresponding back reference, automatically turning the relationship into a bidirectional one:

class Location {
    String city

    static belongsTo = [ author: Author ]
}

In this case, an author property is added to Location at the same time as the cascading is defined. The advantage of this syntax is that you can define multiple cascading relationships.

One thing you may notice if you use the latter syntax is that when you save a new Author with a location, Grails automatically sets the Location's author property to the Author instance. In other words, the back reference is initialised without you having to do it explicitly.

Before I move on to collections, I'd like to say one last thing about the many-to-one relationship. Sometimes people think that adding a back reference as we have done above turns the relationship into a one-to-one. In fact, it's not technically a one-to-one unless you add a uniqueness constraint on one side of the relationship or the other. For example:

class Author {
    String name
    Location location

    static constraints = {
        location(unique: true)
    }
}

Of course, it doesn't make sense to turn the Author - Location relationship into a one-to-one in this particular case, but hopefully you can see how a one-to-one is defined.

The many-to-one relationship is pretty straightforward once you understand how belongsTo works. Relationships involving collections, on the other hand, can throw up a few unpleasant surprises if you're not used to Hibernate.

Collections (one-to-many/many-to-many)

Collections are the natural way to model one-to-many relationships in an object-oriented language and GORM makes using them pretty easy considering what's happening behind the scenes. Nonetheless, this is definitely one area where the impedance mismatch between object-oriented languages and relational databases raises its ugly head. For a start, you have to remember that your in-memory data can be different to that in the database.

Domain instance collections vs DB records

When you have a collection on a domain instance, you're dealing with in-memory objects. This means that you can deal with it just like any other collection of objects. You can iterate over it and you can modify it. Then at some point you will want to persist any changes to the database, which you can do by saving the object that has the collection. I'll come back to that shortly, but first I'd like to demonstrate some of the subtleties associated with this disconnect between your collection of objects and the actual data. To do that, I'm going to introduce the Book class:

class Book {
    String title

    static constraints = {
        title(blank: false)
    }
}

class Author {
    String name
    Location location

    static hasMany = [ books: Book ]
}

This creates a uni-directional (Book does not have a back reference to Author) one-to-many relationship, where an author has zero or more books. Now let's say I execute this code in the Grails console (a wonderful tool for experimenting with GORM):

def a = new Author(name: "Niall Ferguson", location: new Location(city: "Boston"))
a.save(flush: true)

a.addToBooks(title: "Colossus")
a.addToBooks(title: "Empire")

println a.books*.title
println Book.list()*.title

The output will look like this:

[Empire, Colossus]
[]

So you can print the collection of books, but they aren't in the database yet. You can even insert a.save() after the second a.addToBooks() to no apparent effect. Remember from the previous article that I said calling save() does not guarantee immediate persistence of the data? This is a concrete example of that. If you want to see the new books in your query, you'll have to add an explicit flush:

...
a.addToBooks(title: "Colossus")
a.addToBooks(title: "Empire")
a.save(flush: true)   // <---- This line added

println a.books*.title
println Book.list()*.title

The two println statement will then output the same books, although not necessarily in the same order. Another symptom of this discrepancy between the in-memory collection and the database data is demonstrated if you replace the println statements with:

println a.books*.id

Even after a save() (without an explicit flush), this will print nulls. It's only when you flush the session that the child domain instances have their IDs set. This is quite different to the many-to-one case we saw earlier, where you didn't need an explicit flush for the Location instance to be persisted to the database! It's important to realise that difference exists, otherwise you'll be in for a hard time.

As a little aside in case you're following the examples in the Grails console yourself, be aware that anything you save when you run a script in the console will still be there when you execute the next script. The data is only cleared when you restart the console. Also, the session is always flushed on completion of the script.

OK, back to collections. The examples above exhibit some interesting behaviour that I want to talk about next. Why were the Book instances persisted to the database even though I didn't define a belongsTo on Book?

Cascading

As with other relationships, mastering collections means mastering their cascading behaviour. The first thing to note is that saves are always cascaded from the parent to its children, even if there is no belongsTo specified. If that's the case, is there any point to using belongsTo? Yes.

Consider what happens if we execute this code in the console after we have added the author and his books:

def a = Author.get(1)
a.delete(flush: true)

println Author.list()*.name
println Book.list()*.title

The output looks like this:

[]
[Empire, Colossus]

In other words, the author has been deleted, but the books haven't. That's where belongsTo comes in: it ensures that deletes are cascaded as well as saves. Simply by adding the line static belongsTo = Author to Book, the above code will print empty lists for Author and Book. Simple, right? In this case, yes, but the real fun is only just beginning.

Aside: see how we're forcing a flush of the session in the example above? If we don't, Author.list() may display the author that's just been deleted, simply because the change may not have been persisted by that point.

Deleting children

Deleting something like an Author instance and having GORM delete the children automatically is straightforward. But what if you just want to delete one or more of the author's books, not the author himself? You might try this:

def a = Author.get(1)
a.books*.delete()

thinking that this will delete all the books. But in fact this code will generate an exception:

org.springframework.dao.InvalidDataAccessApiUsageException: deleted object would be re-saved by cascade (remove deleted object from associations): [Book#1]; ...
	at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:657)
	at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
	at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:411)
	at org.springframework.orm.hibernate3.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:374)
	at org.springframework.orm.hibernate3.HibernateTemplate.flush(HibernateTemplate.java:881)
	at ConsoleScript7.run(ConsoleScript7:3)
Caused by: org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [Book#1]

Wow, a useful stacktrace message! Yes, the problem is that the books are still in the author's collection, so when the session is flushed, they will be recreated. Remember, not only are saves cascaded, but modified domain instances are automatically persisted (because of Hibernate's dirty-checking).

The solution, as the exception message explains, is to remove the books from the collection:

def a = Author.get(1)
a.books.clear()

Except this isn't a solution, because the books are still in the database. They are simply no longer associated with the author. OK, so we need to explicitly delete them as well:

def a = Author.get(1)
a.books.each { book ->
    a.removeFromBooks(book)
    book.delete()
}

Oops, now we get a ConcurrentModificationException because we are removing books from the author's collection while we're iterating over it. Standard Java gotcha that one. We can side step that by creating a copy of the collection:

def a = Author.get(1)
def l = []
l += a.books

l.each { book ->
    a.removeFromBooks(book)
    book.delete()
}

That works, but boy does it require some effort.

You also have to be careful if you have a bidirectional relationship, for example if your belongsTo uses this sytnax: static belongsTo = [ author: Author ]. If we remove the books from the collection without deleting them like so:

def a = Author.get(1)
def l = []
l += a.books

l.each { book ->
    a.removeFromBooks(book)
}

we'll get a "not-null property references a null or transient value: Book.author" error. As I'll explain later, that's because the books have had their author property set to null. Since the property is not nullable, this triggers a validation error. It's enough to drive anyone crazy!

Fear not, for there is a solution. If we add this mapping to Author:

static mapping = {
    books cascade: "all-delete-orphan"
}

then any book that is removed from its author will automatically be deleted by GORM. The last code sample, where we remove all the books from the collection, will now work. In fact, if the relationship is unidirectional, you can reduce the code substantially:

def a = Author.get(1)
a.books.clear()

This will remove all the books and delete them in one fell swoop!

The moral of this story is a simple one: if you use belongsTo with a collection, explicitly set the cascade type to "all-delete-orphan" in the parent's mapping block. In fact, there's a strong case for making this the default behaviour for belongsTo and one-to-many relationships in GORM.

This raises an interesting question: why doesn't the clear() method work on a bidirectional relationship? I'm not 100% sure, but I believe it's because the books retain a back reference to the author. To understand why that would affect the behaviour of clear(), you must first realise that GORM maps unidirectional and bidirectional one-to-many relationships to database tables differently. For unidirectional relationships, GORM creates a join table by default, so when you clear the collection of books, the records are simply removed from that join table. Bidirectional relationships are mapped using a straight foreign key on the child table, i.e. the book table in our example. A diagram should make that clearer:

one-to-many-mappings

When you clear the collection of books, that foreign key is still there because GORM doesn't clear the value of the author property. Hence it's as if the collection was never emptied.

That's almost it for collections. I'd just like to tie up this section with a quick look at the addTo*() and removeFrom*() methods.

addTo*() vs <<

In my examples, I have used the addTo*() and removeFrom*() dynamic methods provided by GORM. Why is that? After all, if these are standard Java collections, can't we just use code like this:

def a = Author.get(1)
a.books << new Book(title: "Colossus")

Sure we can, but there are some subtle benefits to the GORM methods. Consider this code:

def a = new Author(name: "Niall Ferguson", location: new Location(city: "Boston"))
a.books << new Book(title: "Colossus")
a.save()

Doesn't seem to be anything wrong with that, does there? And yet, if you run the code, you'll get a NullPointerException because the books collection is not yet initialised. That's quite different to the behaviour you see when you fetch the author from the database, for example using get(). In that case, we can happily append items to the books collection. We only run into this problem because we are creating the author via new. If you use the addTo*() method instead, you don't have to worry about this issue at all because it's null-safe.

Now consider the example where we fetch the author using get() before appending a new book to its collection. If the relationship is bidirectional, we'll hit a "property not-null or transient" exception, because the book's author property hasn't been set. If you use the standard collection methods, you have to initialise back references manually. With the addTo*() method, this is done for you.

The last feature of the addTo*() method is the implicit creation of the correct domain class. Notice in our examples how we just pass the initial property values for the books to the method, rather than explicitly instantiating Book? That's because the method can infer from the hasMany property what type the collection contains. Neat, huh?

The removeFrom*() method is less useful, but it does clear back references. Of course, this works best with the "all-delete-orphan" cascade option as I discussed earlier.

The last type of relationship to consider is the many-to-many.

Many-to-many

If you want, you can get GORM to manage many-to-many relationships for you. There are a few things you need to be aware of if you do, though:

# Deletes do not cascade, period.
# One side of the relationship must have a belongsTo, but it usually doesn't matter which side has it.
# The belongsTo only affects the direction of cascading saves – it does not cause cascading deletes
# A join table is always used, but you cannot store any extra information on it.

Sorry to labour the point on cascading deletes, but it's important to understand that the behaviour is quite different from the many-to-one and one-to-many relationships. It's also important to understand the last point: lots of many-to-many relationships have associated information. For example, a user may have many roles and a role may have many users. But the user may have different roles in different projects, hence the project is associated with the relationship itself. In these cases, you're better off managing the many-to-many relationship yourself.

Summary

Well, that's probably the longest article I've written so far, but you've reached the end. Congratulations! Don't worry if you haven't managed to digest everything in one sitting, you can always refer back to it.

I think GORM does a great job of providing a nice abstraction for dealing with database relationships in an object-oriented way, but as you've seen, you can't really forget that you are ultimately dealing with a database. Armed with the information provided in this article, though, you should have no problems coping with the basics of GORM collections. Hopefully that will mean you can enjoy working with one-to-many relationships in your applications and reaping the benefits.

You may not believe it, but I haven't covered everything you need to know about collections yet. There are still some interesting issues to cover around lazy-loading, but I'll talk you through those in the next article.

Until next time!

Similar Posts

Share this Post
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Slashdot
  • Technorati
  • TwitThis
 

20 responses


  1. thats really helpful information. I think the reason people get confused about belongsTo is because the grails documentation barely mentions that the 2 syntaxes produce different effects. All i've found is a one line note declaring : 'Note in this case because we are not using the map syntax in the belongsTo declaration and explicitly naming the association. Grails will assume it is unidirectional'. Which is in the *old* grails documentation, it appears to have been removed from the latest docs.

    old : http://grails.org/doc/1.0.x/guide/5. Object Relational Mapping (GORM).html#5.2.1 Association in GORM

    latest : http://grails.org/doc/latest/guide/5. Object Relational Mapping (GORM).html#5.2.1 Association in GORM

    and the reference page for belongsTo also doesnt mention any of this :

    http://www.grails.org/doc/latest/ref/Domain Classes/belongsTo.html

    It only details the bi-directional form, which may cause some suprises for people when they find out that deletes cascade in both directions. 'I've deleted my nose and now my face has disappeared' :)


  2. ok, i got that wrong. its a bi-directional relationship so far as references go, but deletes don't cascade from the Nose to the Face.


  3. The problem with clear() on the bi-di association is that the back ref must be nullable for this to work.

    Grails Validation occurs before hibernate flushes the session (obviously) so these backrefs that default to non-nullable always fail validation once you have called removeFromXXXX(instance).

    Needless to say, its a bit sucky. We could do with some special logic in nullable constraint that detects persistent properties that are backrefs to an owner, and skill the nullable test for them.


  4. Nice work. Gavin King has never been able to describe these types of (G)ORM gotchas as clearly.


  5. The fact that there is a need for a two part blog post about gotchas with an API proves that there is an issue with our ORMs. Why dont Rails and Django suffer from these problems? Because they are not trapped in the JVM and its oh so well thought out enterprise level standards.


  6. @Sakuraba I can't speak for the other frameworks because I'm not familiar enough with them, but I'd be surprised if they don't have some of the same issues. Remember, ORM in general is a difficult problem to solve because of the impedance mismatch between objects and relational records. Whatever the solution, there will always be a trade-off between flexibility and simplicity.

    Hibernate seems to go for maximum flexibility with the additional benefits of a session-based approach (which allows for interesting optimisations). But that approach means that you have to have a fairly good grasp of how it works with the database.

    I think GORM does a pretty good job of simplifying the HIbernate model and using sensible defaults. I certainly found it very accessible when I first started using it. Since then, I've also learned to appreciate its ability to work with legacy databases. That really highlights one of the key benefits of GORM: you can start being productive with it pretty quickly, then as you learn more, you find that it can satisfy almost any need you have. Yes, you ultimately have to learn how it works to become really proficient with it, but the learning curve isn't all that steep. And if a few articles can help with that learning curve, all the better.


  7. @Sakuraba A query for "activerecord gotcha" returns 39,900 results on Google, so I guess they're not exempt either. We've just taken the time to write about them ;-)


  8. Thank you very much for this series of articles!
    Would it be possible for you to write about how to add database indexes to speed up queries on non-key fields?


  9. Thank you for this article! Reading this earlier would have saved me days of experimenting and wondering… I've been reading the grails 1.2 book including the GORM section, but regarding relationships, it stays on a level that you can compare to the "few slides" you mention in the beginning.

    I suggest you also link to this article from the documentation as the documentation isn't enough – GORM is the one thing in grails where you really need to know what happens under the hood in my opinion.

    I'm looking forward to the next GORM article. Maybe you can also talk about the influence of the hibernate session, something I stumbled over e.g. in grails issue https://cvs.codehaus.org/browse/GRAILS-6356 in relation to save and addTo.


  10. I just wanted to say thank you! For the past few years I have enjoyed working on a number of grails based projects, and GORM / Database issues has always been the most difficult part for me. After reading the past two articles, I have been able to simplify a lot of code, and feel like I have a much better understanding of database side of things in grails.

    Also, I would like to add that I think it would be if the information in this series be added to the Grails User Guide, or at least have the User Guide reference back to these blog posts. I imagine a lot of people who start using grails don't have a great understanding of Hibernate, and this would really help someone getting started.

    I can't wait for the next article!


  11. Hi Peter,

    nice article. GORM is from my experience the most challenging part of Grails. What I'm interested in the future is any education in the space of GORM/Grails together with existing hibernate mapping files. What are the pitfalls and things to obey.

    Right now we are faces with problems during our upgrade to version 1.3.3. It turns out that Grails is now a bit more pedantic with the domain classes.

    Anyway, many thanks for this series.
    klaus.


  12. Hi Peter,

    Thanks for these 2 great articles. Wish i would have read them before running into most of these gothas though. I will keep an eye open on new articles in this serie.

    Thanks,
    Elmar


  13. Thanks for all the feedback! I'll look into at least getting links to these articles into the user guide. Hopefully there will be time to roll some of the information directly into the user guide too.

    @Klaus I may do something on Hibernate mapping files, but it will be some time in the future. I also don't have a lot of personal experience, so I don't know what kind of issues people run into. When you say Grails is more pedantic now, what do you mean? Perhaps you have some examples?


  14. Hi Peter,

    in our migration from 1.1 to 1.3.3 we ran into multiple problems where Grails/Spring was complaining about the hibernate mapping. In particular calculated fields and inconsistencies with getters and member types raised exceptions. I believe it's mainly a newer Spring version that shows this behavior. With Grails 1.1 we had no errors. I also believe, that we made mistakes in our mappings, which now become exceptions.

    The main point I wanted to make is about using external hibernate mappings in general. I understand that this is not the main use case for Grails, but you cannot always start everything from scratch, including the domain model. What I would like to understand better is the way Grails uses hibernate in terms of mapping and even more important, what Grails expects in a mapping to function properly. In our specific situation the domain classes along with their mappings work fine in an environment, that uses hibernate directly. But with Grails it produces errors. And to trace down these errors is not easy.

    klaus.


  15. Thanks for the article. I ran into most of the challenges you described.

    Kind regards
    Andre


  16. This is GOLD! Thank you so much.


  17. Peter, a great set of articles. Even if it is only articulates what I have already found in the field it is very useful.

    To me it says: "Yes, this really is what you have to do to manage object relationships in GORM. You are actually on the right track. There is no utopian ideal that all the experts are using, but you can't seem to grasp – it really is this clumsy!"

    Marc Palmers point above is very notable too. If you want back links, you have to define nullable:true for the the child's reference to the parent. An example with back-links could be useful for this series. Also, an example of multiple belongsTo ownership could be beneficial.

    This is real "Grails in Action" stuff. Does anyone know if the book by that name covers these post-beginner problems?


  18. Hi Peter,

    I had a look at these blog posts earlier this year and I am sure there was more info on using an intermediate class to handle your many-to-many relationships. I have a new domain issue that has a class with multiple hasMany elements and two belongsTo that seem to save each element successfully but don't save the relationships in the join tables. Would an intermediate class work here or am I doing something wrong in the mapping sections?


  19. @Michael: I'm sorry, I don't understand your question. What's the current model look like? Also, it would probably be better to ask this question on the user mailing list: http://grails.org/Mailing lists


  20. @Peter,

    Thanks, I think i was looking for the many-to-many code burt had on his blog, I thought there was similar code to handle many-to-many relationships in this blog post.

    I have posted the question to the mailing list, I hope someone there has some ideas or perhaps you can help if you see the post.

    I have a domain object with multiple belongsTo but the join tables don't seem to populate, I have tried a few different things but to no avail.

    thanks

    Michael

2 trackbacks

Leave a Reply