Last updated by 5 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 mapping

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 "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>

%PROJECT_HOME%hibernateAuthor.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>

%PROJECT_HOME%hibernateBook.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>

%PROJECT_HOME%grails-appconfDevelopmentDataSource.groovy
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 = ""
}
%PROJECT_HOME%confApplicationBootStrap.groovy
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() } }

&nbsp;%PROJECT_HOME%grails-appcontrollersAuthorController.groovy
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]) } … }

&nbsp;%PROJECT_HOME%grails-appcontrollersBookController.groovy
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) } … }

&nbsp; %PROJECT_HOME%grails-appviewsauthoredit.gsp
<!-- 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>
&nbsp; %PROJECT_HOME%grails-appviewsbookedit.gsp
<!-- 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>

Download the example files:

AuthorBook.zip