Archive for November 14th, 2011

This Week in Grails (2011-45)

Monday, November 14th, 2011

Groovy 1.8.4 and a beta of Groovy 2.0 (renamed from Groovy 1.9) were released this week. Check out Guillaume’s post on what’s new, including static type checking and support for Java 7’s invokedynamic.

Peter Ledbrook did a webinar on new features in Grails 2.0 (“Better Productivity: Grails 2.0”) and you can watch it here.

I wrote a few posts this week; it seems weird to write “real” posts instead of just these summaries 🙂 Check out Accessing the GrailsApplication and ApplicationContext from domain classes without holders, Overriding Groovy constructors, and Create your own Grails holder class.


If you want to keep up with these “This Week in Grails” posts you can access them directly via their category link or in an RSS reader with the feed for just these posts.


Translations of this post:



Plugins

There were no new plugins released, but 12 were updated:

  • atmosphere version 0.4.1.2. Provides integration with the Atmosphere project, a portable AjaxPush/Comet and WebSocket framework
  • clover version 3.1.2. Integrates the Clover code coverage tool
  • code-coverage version 1.2.5. Generates code coverage reports using Cobertura
  • cxf-client version 1.2.2. Use existing (or new) Apache CXF wsdl2java generated content to invoke SOAP services
  • sanitizer version 0.7.0. Sanitizes markup(HTML, XHTML, CSS) using OWASP AntiSamy Filters
  • spring-security-twitter version 0.4. Twitter authentication as extension to the Spring Security Core plugin
  • struts-menu version 1.2. Basic Grails wrapper for Struts Menu
  • taggable version 1.0.1. Adds support for associating tags with domain classes
  • uploadr version 0.5.2. HTML5 Drag and Drop file uploader
  • website-optimizer version 0.2.1. Provides tags to easily integrate Google Website Optimizer experiments into your Grails application
  • yui-minify-resources version 0.1.4. Minifies static css and js resources using the YUI Compressor library
  • zkui version 0.5.M1. Seamlessly integrates ZK with Grails’ infrastructures; uses the Grails’ infrastructures such as GSP, controllers rather than zk’s zul as in ZKGrails plugin

Interesting Tweets

Jobs



User groups and Conferences



Hibernate Bags in Grails 2.0

Monday, November 14th, 2011

When I’ve talked in the past about collection mapping in Grails (you can see a video of a SpringOne/2GX talk here) I mentioned that the current approach of using Sets or Lists is problematic and provided workarounds. I mentioned at the time that Hibernate has support for Bags which don’t enforce uniqueness or order like Sets and Lists do, so if GORM supported Bags we could just use those. So I added support for Bags to GORM for Grails 2.0 and thought that was that.

I thought it’d be interesting to demo this at my GORM talk at this year’s SpringOne/2GX but when I created a small test application it wasn’t working like I remembered. In fact it was actually worse than the problems I was working around. So I put that away with a mental note to get back to this soon, and before 2.0 final is released.

It turns out there’s good news and bad news. The good news is that it’s not completely broken. The bad news is that it’s mostly broken.


First the good news. If you have a one-to-many that doesn’t use a join table, using a Bag works mostly as expected. As an example, consider an Author/Book mapping where a book has one author, and an author can have many books:

class Author {
   String name
   Collection books
   static hasMany = [books: Book]
}
class Book {
   String title
   static belongsTo = [author: Author]
}

Using the Map syntax for the belongsTo mapping is the key to avoiding the join table and relating the tables with a foreign key from the book table to the author table. If you run grails schema-export the output will be something like

create table author (
   id bigint generated by default as identity,
   version bigint not null,
   name varchar(255) not null,
   primary key (id)
);

create table book (
   id bigint generated by default as identity,
   version bigint not null,
   author_id bigint not null,
   title varchar(255) not null,
   primary key (id)
);

alter table book add constraint FK2E3AE9CD85EDFA
foreign key (author_id) references author;

If you run this initializing code in a Grails console with SQL logging enabled (add logSql = true in DataSource.groovy)

def author = new Author(name: 'Hunter S. Thompson')
author.addToBooks(title: 'Fear and Loathing in Las Vegas')
author.save()

you’ll see output like this:

insert into author (id, version, name) values (null, ?, ?)

insert into book (id, version, author_id, title) values (null, ?, ?, ?)

update author set version=?, name=? where id=? and version=?

which is ok; it inserts the author and the book, although it bumps the version of the Author. I’ll come back to that.

If you run this updating code:

def author = Author.get(1)
author.addToBooks(title: "Hell's Angels: A Strange and Terrible Saga")
author.save()

you’ll see output like this:

select author0_.id as id0_0_, author0_.version as version0_0_,
author0_.name as name0_0_ from author author0_ where author0_.id=?

insert into book (id, version, author_id, title) values (null, ?, ?, ?)

update author set version=?, name=? where id=? and version=?

This is also basically ok – it loads the author, inserts the book, and versions the author.

If you map the belongsTo with the non-map syntax (static belongsTo = Author) you’ll get this DDL:

create table author (
   id bigint generated by default as identity,
   version bigint not null,
   name varchar(255) not null,
   primary key (id)
);

create table author_book (
   author_books_id bigint,
   book_id bigint
);

create table book (
   id bigint generated by default as identity,
   version bigint not null,
   title varchar(255) not null,
   primary key (id)
);

alter table author_book add constraint FK2A7A111D3FA913A
foreign key (book_id) references book;

alter table author_book add constraint FK2A7A111DC46A00AF
foreign key (author_books_id) references author;

and running the initializing code above will result in output that’s similar to before, with the addition of inserting into the join table:

insert into author (id, version, name) values (null, ?, ?)

insert into book (id, version, title) values (null, ?, ?)

update author set version=?, name=? where id=? and version=?

insert into author_book (author_books_id, book_id) values (?, ?)

but running the updating code results in this:

select author0_.id as id4_0_, author0_.version as version4_0_,
author0_.name as name4_0_ from author author0_ where author0_.id=?

select books0_.author_books_id as author1_4_0_, books0_.book_id as
book2_0_ from author_book books0_ where books0_.author_books_id=?

select book0_.id as id3_0_, book0_.version as version3_0_,
book0_.title as title3_0_ from book book0_ where book0_.id=?

insert into book (id, version, title) values (null, ?, ?)

update author set version=?, name=? where id=? and version=?

delete from author_book where author_books_id=?

insert into author_book (author_books_id, book_id) values (?, ?)

insert into author_book (author_books_id, book_id) values (?, ?)

This is not good. It reads the author, then all of the books for that author (the part we’re trying to avoid), inserts the book, and then deletes every row from the join table for this author, and re-inserts rows for each element in the Bag. Ouch.


If you convert the relationship to a many-to-many with Bags on both sides:

class Author {
   String name
   Collection books
   static hasMany = [books: Book]
}
class Book {
   String title
   Collection authors
   static hasMany = [authors: Author]
   static belongsTo = Author
}

and run this initializing code:

def author = new Author(name: 'Hunter S. Thompson')
author.addToBooks(title: 'Fear and Loathing in Las Vegas')
author.save()

you get this output:

insert into author (id, version, name) values (null, ?, ?)

insert into book (id, version, title) values (null, ?, ?)

update author set version=?, name=? where id=? and version=?

update book set version=?, title=? where id=? and version=?

insert into author_books (author_id, book_id) values (?, ?)

It inserts the author and the book, then versions both rows, and inserts a row into the join table.

If you run this updating code:

def author = Author.get(1)
author.addToBooks(title: "Hell's Angels: A Strange and Terrible Saga")
author.save()

then the output is similar to the output for one-to-many with a join table:

select author0_.id as id0_0_, author0_.version as version0_0_,
author0_.name as name0_0_ from author author0_ where author0_.id=?

select books0_.author_id as author1_0_0_, books0_.book_id as book2_0_
from author_books books0_ where books0_.author_id=?

insert into book (id, version, title) values (null, ?, ?)

update author set version=?, name=? where id=? and version=?

update book set version=?, title=? where id=? and version=?

delete from author_books where author_id=?

insert into author_books (author_id, book_id) values (?, ?)

insert into author_books (author_id, book_id) values (?, ?)

It loads the author, then all of the book ids from the join table (to create proxies, which are lighter-weight than full domain class instances but there will still be N of them in memory), then inserts the new book, versions both rows, and again deletes every row from the join table and reinserts them. Ouch again.


So for the two cases where there are join tables, we have a problem. Hibernate doesn’t worry about duplicates or order in-memory, but the join tables can’t have duplicate records, so it has to pessimistically clear the data and reinsert it. This has all of the negatives of the non-Bag approach and adds another big one.

Even in the first case I described where there’s no join table, there’s still a problem. Since the Author’s version gets incremented when you add a Book (you’re editing a property of the Author, so it’s considered to be updated even though it’s a collection pointing to another table) there’s a high risk that concurrently adding child instances will cause optimistic locking exceptions for the Author, even though you just want to insert rows into the book table. And this is the case for all three scenarios.


So I guess I’m back to advocating the approach from my earlier talks; don’t map a collection of Books in the Author class, but add an Author field to the Book class instead:

class Author {
   String name
}
class Book {
   String title
   Author author
}

And for many-to-many case map the “author_books” table with a domain class:

class Author {
   String name
}
class Book {
   String title
}
class AuthorBook {
   Author author
   Book book
   ...
}

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.