UNDERSTANDING GORM Alonso Torres @alotor http://goo.gl/U6sK5E
Ego-slide Alonso Torres alotor
@alotor
Engineer at Kaleidos
Ego-slide Alonso Torres alotor
@alotor
Engineer at Kaleidos
GORM? Really? Is so easy, the easiest part of Grails! Only a few POGO's to access the database Peachy!
Some pitfalls The 'When did I modified that object?' def renderBook(String isbn) { def book = Book.findByIsbn(isbn) // Render as uppercase book.title = book.title.toUpperCase() [book: book] }
Some pitfalls The 'Rollback where are you?' def buyBook(String user, String bookTitle, Long qty) { def found = Book.findByTitle(bookTitle)
// Save a new order for the user def order = new BookOrder(user:user, book:found, quantity: qty) order.save() found.stock = found.stock - 1 // When not found throw exception to rollback if (found.stock < 0) { throw new Exception("This should rollback!") } return found
}
Some pitfalls The 'Parallel processing, It's easy!!' def processOrders() { def orders = BookOrder.list()
}
// Parallel update orders GParsPool.withPool(10) { orders.eachParallel { order -> dispatchingService.dispatch(order) order.sent = true order.save() } }
Some pitfalls The 'Fail whale' class User { String name static hasMany = [followers:User] }
user.followers.isEmpty()
Check Burt talk about "GORM Performance"
GORM is dark and full of terrors
Let's take a step back... and start at the beginning
Understanding Bootstrapping WHAT'S GOING ON BEHIND THE SCENES?
Your first Domain class
class Book { String name String isbn Author author }
Grails Bootstrap grails run-app
Inspect /grails-app Classes inside /domain Annotates them with @grails.persistence.Entity
1) Domain classes are found
DEBUG commons.DefaultGrailsApplication Inspecting [es.greach.gorm.Book] [es.greach.gorm.Book] is not a Filters class. [es.greach.gorm.Book] is not a Codec class. [es.greach.gorm.Book] is not a TagLib class. [es.greach.gorm.Book] is not a Service class. [es.greach.gorm.Book] is not a Controller class. [es.greach.gorm.Book] is not a Bootstrap class. [es.greach.gorm.Book] is a Domain class. Adding artefact class es.greach.gorm.Book of kind Domain
Grails initializes the Domain Class Prepares the relationship properties Resolves class hierarchy Add validation capabilities Integrates domain classes with Spring
@grails.persistence.Entity Includes 'id' and 'version' Marks the classes as domain entities You can put domain classes inside /src/main/groovy Manualy annotate them with @Entity
@Entity class Book { Long id Long version String name String isbn Author author }
2) GORM enhances classes
class Book { static mapWith = "mongo" String name String isbn }
DEBUG cfg.HibernateUtils
- Enhancing GORM entity Book
GORM Enhancer Instances API (save, delete...) Classes API (findAll, where, withCriteria...) Dynamic Finders Validation (unique)
GORM Enhancer Instances API (save, delete...) Classes API (findAll, where, withCriteria...) Dynamic Finders Validation (unique)
Bootstraping Distinct Grails-base vs GORM @Entity could potentialy be used outside Grails Problems with AST's Grails dependencies
Understanding Spring HOW INTERACT DOMAIN CLASSES AND SPRING
class Book Book { { def bookService def bookService String name String isbn Author author String toString() { return return bookService.parseBook(this bookService.parseBook(this) ) } }
def myBook = new def myBook new Book() Book() println myBook.toString()
[DEBUG] [DEBUG] BookService::parseBook
DI needs needs control on obje object ct instantiation i nstantiation new Book() new Book()
Spring should create the object
So Grails kind of "cheats"
Spring Beans for a Domain Class BookValidator BookPersistentClass BookDomainClass Book
new Book()
Book.constructor = {-> def ctx = .... // Spring context context.getBean("Book") }
What does that means? Spring creates your objects Be careful when using some capabilities Example: Spring AOP
Understanding Hibernate GORM WHERE THE DARKNESS LURKS
GORM > Hibernate GORM provides a beautiful abstraction over hibernate Convention over configuration No more complicated XML or annotations Better collections
But, Hibernate it's still there
class Book { String name String isbn Author author }
grails generate-controller Book
// Grails 2.2 scafolding class BookController { def save() { def instance = new Book(params) if (!instance.save(flush: true)) { render(view: "create", model: [bookInstance: instance]) return } redirect(action: "show", id: instance.id) } }
// Grails 2.2 scafolding class BookController { def save() { def bookInstance = new Book(params) if (!bookInstance.save(flush: true)) { render(view: "create", model: [bookInstance: bookInstance]) return } redirect(action: "show", id: bookInstance.id) } }
OpenSessionInViewInterceptor Creates a new Session within a Controller scope Doesn't create a Transaction So... there is NO transaction at the controller After render the session is flushed
Session? Transaction? I'm confused
Session Entry point to the Hibernate Framework In-Memory cache No thread safe and normaly thread-bound
GORM Entities Entities have a relationship with the session This defines a "life-cycle" Transient - not yet persisted Persistent - has a session Detached - persisted but without session
Session Flush Session checks "DIRTY OBJECTS" When flushed the changes are persisted to database
After flush, are my objects in the DB?
DEPENDS
Transaction Database managed Provider specific Accessed through JDBC Driver
Peter Ledbrok - http://spring.io/blog/2010/06/23/gorm-gotchas-part-1/
Hibernate Session FLUSHING vs COMMIT Database Transaction
Automatic session flushing Query executed A controller completes Before transaction commit
Some pitfalls The 'When did I modified that object?' def renderBook(String isbn) { def book = Book.findByIsbn(isbn) // Render as uppercase book.title = book.title.toUpperCase() [book: book] }
Some pitfalls The 'When did I modified that object?' def renderBook(String isbn) { def book = Book.findByIsbn(isbn) // Deattach the object from the session book.discard() // Render as uppercase book.title = book.title.toUpperCase() [book: book] }
Where do I put my transactional logic?
withTransaction Book.withTransaction { // Transactional stuff }
Transactional services @Transactional class BookService { def doTransactionalStuff(){ ... } }
Be careful! Don't instanciate the services new TransactionalService().doTransactionalStuff()
Don't use closures def transactionalMethod = { ... }
And...
DON'T THROW CHECKED EXCEPTIONS
Some pitfalls The 'Rollback where are you?' def buyBook(String user, String bookTitle, Long qty) { def found = Book.findByTitle(bookTitle)
// Save a new order for the user def order = new BookOrder(user:user, book:found, quantity: qty) order.save()
found.stock = found.stock - 1
// When not found throw exception to rollback if (found.stock < 0) { throw new Exception("This should rollback!") } return found
}
Some pitfalls The 'Rollback where are you?' def buyBook(String user, String bookTitle, Long qty) { def found = Book.findByTitle(bookTitle)
// Save a new order for the user def order = new BookOrder(user:user, book:found, quantity: qty) order.save()
found.stock = found.stock - 1 // When not found throw exception to rollback if (found.stock < 0) { throw new RuntimeException("This should rollback!") } return found
}
@Transactional vs @Transactional Spring @Transactional creates a PROXY Grails new @Transactional is an AST
Understanding Parallel GORM BRACE YOURSELVES
Session is Thread-Local
Some pitfalls The 'Concurrency, It's easy!!' def processOrders() { def orders = BookOrder.list()
}
// Parallel update orders GParsPool.withPool(10) { orders.eachParallel { order -> dispatchingService.dispatch(order) order.sent = true order.save() } }
Thread-local session Recovers a list of orders def orders = Orders.findTodayOrders()
Each item is bound to the request session When we spawn the concurrent threads the objects don't know where is their session
Some pitfalls The 'Concurrency, It's easy!!' def processOrders() { def orders = BookOrder.list()
// Parallel update orders GParsPool.withPool(10) { orders.eachParallel { order -> BookOrder.withNewSession { order.merge()
dispatchingService.dispatch(order) order.sent = true order.save() } } }
}
Rule of thumb Session = Thread If entity has recovered by another thread Put it in the current thread session
Grails 2.3 Async GORM The new async GORM solves some problems Manages the session for the promise def promise = Person.async.findByFirstName("Homer") def person = promise.get()
Grails 2.3 Async GORM Problem: Object has been retrieved by another thread After the promise is resolved we have to attach the object def promise = Person.async.findByFirstName("Homer") def person = promise.get() person.merge() // Rebound the object to the session person.firstName = "Bart"
Closing thoughts GORM is a very cool abstraction You have to be aware of some of how things work With great power, comes great responsibility
THANKS @alotor http://goo.gl/U6sK5E
Bonus track (references) http://spring.io/blog/2010/06/23/gorm-gotchas-part-1/ http://sacharya.com/tag/gorm-transaction/ http://www.anyware.co.uk/2005/2012/11/12/the-falseoptimism-of-gorm-and-hibernate/ http://docs.jboss.org/hibernate/orm/3.6/reference/enUS/html_single/#transactions-basics