Last updated by
4 years ago
Page: Many-to-Many Mapping with Hibernate XML, Version:3
Tutorial: Many-to-Many mapping with Hibernate XML (Author/Book)
Grails has supported many-to-many relationships with straight GORM for a long time now, so this article is only really useful if you are doing Hibernate mappingHere is an example with a many-to-many relationship: an author can write several books and a book can be written by several authors. The relationship will be managed from the author's side: a book can be added or removed from the author's list of books.From the book GSP pages it looks like an author can be added/removed from the book side, but in reality, the author of the current book gets his/her book list updated. See the book controller for more details. Following are the Hibernate files and domain class files for Author and Book, the data source config file and some excerpts of the controllers.The zip file contains all the files to build the example. The database used is mySQL and the "confDevelopmentDataSource.groovy" file set accordingly. Change it for your database, don't forget to include the JBDC driver in your placing the driver jar file in the %PROJECT_HOME%lib directory. In this example: "%PROJECT_HOME%libmysql-connectormysql-connector-java-3.1.12-bin.jar". IMPORTANT: The version of Grails used is 0.3-SNAPSHOT of 17-Oct-06.%PROJECT_HOME%hibernatehibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><hibernate-configuration> <session-factory> <mapping resource="Book.hbm.xml"/> <mapping resource="Author.hbm.xml"/> </session-factory> </hibernate-configuration>
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping> <class name="Author" table="author"> <id name="id" column="id" unsaved-value="null"> <generator class="native"></generator> </id> <version name="version" column="version" type="java.lang.Long"/> <property name="name" column="name" /> <set name="books" table="author_book" lazy="true" inverse="false"> <key> <column name="author_id" not-null="true"/> </key> <many-to-many class="Book"> <column name="book_id" not-null="true"/> </many-to-many> </set> </class> </hibernate-mapping>
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping> <class name="Book" table="book"> <id name="id" column="id" unsaved-value="null"> <generator class="native"></generator> </id> <version name="version" column="version" type="java.lang.Long"/> <property name="title" column="title"/> <set name="authors" table="author_book" lazy="true" inverse="true"> <key> <column name="book_id" not-null="true"/> </key> <many-to-many class="Author"> <column name="author_id" not-null="true"/> </many-to-many> </set> </class> </hibernate-mapping>
class DevelopmentDataSource {
boolean pooling = true
String dbCreate = "create-drop"
String url = "jdbc:mysql://localhost/library"
String driverClassName = "com.mysql.jdbc.Driver"
String username = "root"
String password = ""
}class ApplicationBootStrap { Closure init =
{
def book
book = new Book(title: 'Moto')
book.save()
book = new Book(title: 'Auto')
book.save()
book = new Book(title: 'Cooking')
book.save()
book = new Book(title: 'Windsurfing')
book.save() def java = new Book(title: 'Java')
java.save() def author = new Author(name: 'Fred')
author.addBook(java)
author.save()
}
}class AuthorController {
…
def edit = {
def author = Author.get( params.id ) if (!author) {
flash.message = "Author not found with id ${params.id}"
redirect(action:list)
}
else {
def availableBooks = Book.list(max:20,order:'asc')
def authorBooks = author.books
availableBooks = availableBooks - authorBooks;
return [author : author , availableBooks : availableBooks]
}
} def addBook = {
def author = Author.get( params.id )
def book = Book.get(params.bookId)
author.addBook(book)
author.save()
redirect(action:'edit', params:[id:author.id])
} def removeBook = {
def author = Author.get( params.id )
def book = Book.get(params.bookId)
author.books.remove(book)
author.save()
redirect(action:'edit', params:[id:author.id])
}
…
}class BookController {
…
def delete = {
def book = Book.get( params.id )
if (book) {
def authors = book.authors
// Removing the book from its authors' list
authors.each { author ->
println (">>>>>>>>>>>> removing book:" + book.title + " from author: " + author.name)
author.books.remove(book)
author.save()
}
book.delete()
flash.message = "Book ${params.id} deleted."
redirect(action:list)
}
else {
flash.message = "Book not found with id ${params.id}"
redirect(action:list)
}
} def edit = {
println (">>>>>>>>>>>>> editBook: " + params.id)
def book = Book.get( params.id ) if(!book) {
flash.message = "Book not found with id ${params.id}"
redirect(action:list)
}
else {
def availableAuthors = Author.list(max:20,order:'asc')
def bookAuthors = book.authors
availableAuthors = availableAuthors - bookAuthors;
return [book : book , availableAuthors : availableAuthors]
}
} def addAuthor = {
def bookId = params.id
def book = Book.get(bookId)
def author = Author.get(params.authorId)
println (">>>>>>>>>>>>> addAuthor: " + params['authorId'] + ' for book: ' + book.title)// if the book is updated from here, all the other books for this author are removed
// book.addAuthor(author) // deletes the existing books for this author and adds this book
// book.authors.add(author) // Doesn't work at all, no change to authors
/* if (!book.save())
{
book.errors.each {
println '>>>>>>>>>>>>>>>>>>>> Error saving book' + it
}
}
*/
// MUST DO this because inverse='true' in hbm.xml
author.addBook(book)
author.save()
redirect(action:'edit',id:bookId)
} def removeAuthor = {
def bookId = params.id
def book = Book.get(bookId)
def author = Author.get(params.authorId)
println (">>>>>>>>>>>>> START removeAuthor: " + bookId + ' for author: ' + author.name)
author.books.remove(book)
println (">>>>>>>>>>>>> DONE removeAuthor: " + params.bookId + ' for author: ' + author.name)
author.save()
redirect(action:'edit',id:bookId)
}
…
}<!-- Author's books -->
<tr class='prop'>
<td valign='top' class='name'>
<label for='books'><nobr>Author's Books:</nobr></label>
</td>
<td valign='top' class='value ${hasErrors(bean:author,field:'books','errors')}'>
<ul>
<g:each var='b' in='${author?.books?}'>
<li>
${b.title}
<g:link controller='book' action='show' id='${b.id}'> (Show)</g:link>
<g:link controller='author'
params='["id":author?.id, "bookId":b.id]' action='removeBook'>
(Remove)
</g:link>
</li>
</g:each>
</ul>
</td>
</tr>
<!-- Available books -->
<tr class='prop'>
<td valign='top' class='name'>
<label for='books'><nobr>Available Books:</nobr></label>
</td>
<td valign='top' class='value ${hasErrors(bean:author,field:'books','errors')}'>
<ul>
<g:each var='b' in='${availableBooks?}'>
<li>
${b.title}
<g:link controller='book' action='show' id='${b.id}'> (Show)</g:link>
<g:link controller='author'
params='["id":author?.id, "bookId":b.id]' action='addBook'>
(Add)
</g:link>
</li>
</g:each>
</ul>
</td>
</tr><!-- Book Author(s) -->
<tr class='prop'>
<td valign='top' class='name'>
Authors:
</td>
<td valign='top' class='value ${hasErrors(bean:book,field:'authors','errors')}'>
<ul>
<g:each var='a' in='${book?.authors?}'>
<li>
${a.name}
<g:link controller='author' action='show' id='${a.id}'> (Show)</g:link>
<g:link controller='book'
params='["id":book?.id, "authorId":a.id]' action='removeAuthor'>
(Remove)
</g:link>
</li>
</g:each>
</ul>
</td>
</tr> <!-- Available Authors -->
<tr class='prop'>
<td valign='top' class='name'>
<nobr>Other Authors:</nobr>
</td>
<td valign='top' class='value ${hasErrors(bean:book,field:'availableAuthors','errors')}'>
<ul>
<g:each var='a' in='${availableAuthors?}'>
<li>
${a.name}
<g:link controller='author' action='show' id='${a.id}'> (Show)</g:link>
<g:link controller='book'
params='["id":book?.id, "authorId":a.id]' action='addAuthor'>
(Add)
</g:link>
</li>
</g:each>
</ul>
</td>
</tr>