GORM Gotchas (Part 2) |
|
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:

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
- GORM Gotchas (Part 3)
- GORM Gotchas (Part 1)
- GORM for MongoDB: New Milestone, Richer Experience
- Yet Another Flavour of GORM: MongoDB
- Reuse your Hibernate/JPA domain model with Grails





martin says:
Added on July 2nd, 2010 at 8:42 amthats 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'
martin says:
Added on July 2nd, 2010 at 8:57 amok, 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.
Marc Palmer says:
Added on July 2nd, 2010 at 11:08 amThe 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.
Bruce Baron says:
Added on July 3rd, 2010 at 10:26 amNice work. Gavin King has never been able to describe these types of (G)ORM gotchas as clearly.
Sakuraba says:
Added on July 4th, 2010 at 5:30 amThe 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.
Peter Ledbrook (blog author) says:
Added on July 5th, 2010 at 4:11 am@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.
Graeme Rocher says:
Added on July 5th, 2010 at 5:27 am@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
Eduardo says:
Added on July 5th, 2010 at 9:00 amThank 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?
Werner says:
Added on July 7th, 2010 at 1:55 amThank 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.
Mike says:
Added on July 10th, 2010 at 5:18 pmI 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!
Klaus Baumecker says:
Added on July 27th, 2010 at 7:33 amHi 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.
Elmar Wachtmeester says:
Added on July 28th, 2010 at 4:44 amHi 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
Peter Ledbrook (blog author) says:
Added on July 28th, 2010 at 10:21 amThanks 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?
Klaus Baumecker says:
Added on July 29th, 2010 at 2:01 amHi 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.
Andre Gerdts says:
Added on July 29th, 2010 at 2:45 amThanks for the article. I ran into most of the challenges you described.
Kind regards
Andre
Paul Willems says:
Added on August 10th, 2010 at 3:04 amThis is GOLD! Thank you so much.
Eamonn O'Connell says:
Added on January 27th, 2011 at 6:13 amPeter, 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?
michael bennett says:
Added on May 11th, 2011 at 3:28 amHi 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?
Peter Ledbrook (blog author) says:
Added on May 11th, 2011 at 4:54 am@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
Michael Bennett says:
Added on May 11th, 2011 at 5:43 am@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
sudhir says:
Added on March 23rd, 2012 at 11:42 amPeter – may you have a look at this issue http://jira.grails.org/browse/GRAILS-8046 and comment whats going wrong here.
sudhir says:
Added on March 23rd, 2012 at 11:45 amThe interesting problem with http://jira.grails.org/browse/GRAILS-8046 is 'Duplicate records in join table'
sudhir says:
Added on March 23rd, 2012 at 1:23 pmPeter – yes, I have been able to workaround by flushing the session after doing clear(). But have you been able to reproduce the issue !
kulveer says:
Added on May 26th, 2012 at 2:05 pmHi All
I have one to many relation and i want to delete one of the object from the collection and i try to find the object using the
oneClassObj.manyClasObjs.find{
manyClassObj-> manyClassObj.id=params.id
}
It doesnt find the object in the collection and returns me null . What could be the reason ? NOTE- I am invoking this delete using AJAX call.
Any hint will be appreciated to solve this.
Thanks
Mike says:
Added on May 28th, 2012 at 6:56 am@kulveer – I see two potential problems with your example:
1) in your example, you are using the single = assignment, instead of the comparison operator. (maybe this is just a typo in the example.)
2) You are trying to compare manyClassObj.id (which is probably a Long) with params.id (which is probably a String).
In the future, you may want to consider joining the Grails User mailing list (http://grails.org/Mailing lists) for support. I have received a lot of help from smart folks on the mailing list.
deadman says:
Added on May 28th, 2012 at 10:37 amIn the MANY-TO-ONE example, you mentioned that Location belongsTo Author and Author has one Location. I am not clear how can this relation become MANY-TO-ONE.
In the example, which is Many, Which is One?
My understanding:
Author has one location
Location belongsTo one Author
So,I understand this relation as one to one.
You mentioned that Location as unique in Author. Does that mean Location(NEWYORK) is unique to the Author or the database table?
Peter Ledbrook (blog author) says:
Added on May 28th, 2012 at 11:41 am@deadman Author is the MANY side, Location is the ONE side.
. static belongsTo = Author
defines the behaviour of cascading updates, but it does _not_ provide a reference from the Location back to the author. Hence the relationship is still many-to-one.
Murphy says:
Added on July 14th, 2012 at 11:33 pmdef a = new Author(name: "Niall Ferguson", location: new Location(city: "Boston"))
a.save()
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).
Margots says:
Added on August 8th, 2012 at 7:35 amHi Peter,
Thank you for the great article
The session you mentioning, how would one debug for objects(children) remaining causing the: 'deleted object would be re-saved by cascade' so we can see exactly which object among many is the cause.(i have to delete object that is the main part of app with lot of children associated)
Thank you for your help
p.s. IntelliJ i am able evaluate the Domain object containing the children to delete but how do i see the session so i can clear any children i missing before delete the main object?
William Wang says:
Added on August 8th, 2012 at 10:21 amThanks for great explanation. It's very helpful. I wish I have seen this earlier. But I'm seeing a different behavior for the discussion on "Domain instance collections vs DB records". Without explicit flush after adding book into author, the two books are saved in the database. Is this because the query implied by "println Book.list()*.title" triggered an auto flush?
Looking at the SQL statements, it actually triggered another SQL update to author after adding books, although it's updating nothing since all the attributes values are same. Is there a way to prevent this extra update?
Manoj says:
Added on September 26th, 2012 at 3:57 amHi Peter,
This blog was very helpful and mant hanks for that.
But Deadman has a point. When you create two authors as
Author author1 = new Author(name: "Michael Jackson", location: new Location(city: "Los Angeles"))
Author author2 = new Author(name: "Stephen King", location: new Location(city: "Los Angeles"))
you are actually creating two instances of "Author" object and two instances of "Location" object. Which is fine. But the question is how is this information stored in the database.
When I ran the tests, it seems that database has two rows in Author table and two rows in Location table. This is not a many-to-one relationship. I would expect there to be one row in Location table (since both are Los Angeles) and two rows in Author. The LOCATION_ID in both the Author rows would point to ID of location table.
If my DB table Row for Location table is huge, then storing multiple copies of the same data for every row in Author defeats the purpose of Many-to-One.
Please can you clarify and elaborate on this.
Peter Ledbrook (blog author) says:
Added on September 27th, 2012 at 1:36 am@William That may be a difference in the version of Hibernate being used. This blog post was written for Grails 1.3.x, but Grails 2 uses a significantly newer version of Hibernate. It may also be down the database being used.
The query should trigger an auto-flush, so I suspect this is something that was fixed in Hibernate. As for the extra update, I doubt it's possible to remove that. The author is treated as dirty because one of its collections has changed, so the update derives from that.
@Manoj You would not use that code. You would use:
. . def location = new Location(city: "Los Angeles").save()
. . Author author1 = new Author(name: "Michael Jackson", location: location)
. . Author author2 = new Author(name: "Stephen King", location: location)
I think I made a mistake in adding a belongsTo on Location, since a Location could not really 'belong to' an author. What I tried to demonstrate (with a very contrived example) is that belongsTo activates the cascading updates, so that
. . def a = new Author(name: "Niall Ferguson", location: new Location(city: "Boston"))
. . a.save()
would work. Hope that helps.
Randy Jones says:
Added on December 5th, 2012 at 1:10 pmI'm having an issue with the many to one behaving as if it is bidirectional when it should be unidirectional. Example:
class Book {
Author author
}
class Author {
Book lastReadBook
Book favoriteBook
}
Creating new books is setting the "lastReadBook" or the "favoriteBook" of the books author which is not desired or intended.