Last updated by
4 years ago
Page: Many-to-Many Mapping with Hibernate XML, Version:1
Tutorial: Many-to-Many mapping with Hibernate XML (Author/Book)
Here 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 "conf
DevelopmentDataSource.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%
lib
mysql-connector
mysql-connector-java-3.1.12-bin.jar". IMPORTANT: The version of Grails used is 0.3-SNAPSHOT of 17-Oct-06.%PROJECT_HOME%
hibernate
hibernate.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>
hibernate
Author.hbm.xml
<?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>
hibernate
Book.hbm.xml
<?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>