managing associations over multiple requests in grails application
i am currently implementing a grails web-app, with a couple of complex forms where modifications on an association graph should be managed "in-memory" (that is the http session) as long as the entity or top-level domain object is not saved.
e.g.
top-to-bottom: Document -> Categories -> Sub-Categories ...
requirement: modifications to document/categories/sub-categories should only be saved whenever the document is saved and under no other circumstances.
my first approach was to store the association ids in the http session, but this ends up 开发者_如何学Gowith lot of clue code in my DocumentController.update action that synchronizes session state with the current persistent state
// update some abstract association
for (def Iterator it = documentInstance.association.iterator(); it.hasNext();) {
if (!session.association.contains(it.next().someEntity.id)) {
it.remove()
}
}
for (def roleTypeId in session.association) {
// add/update association
...
}
clue code is getting even worse when it comes to actually modifying data e.g. of a category, meaning that the modified category object has to be detached/reattached/merged when the top-level entity is saved.
I would be very interested in your thoughts on such long-spanned unit of works.
You could use the session-per-conversation pattern a.k.a. "long conversations". Try the grails webflow plugin, which works this way or if you think webflow is inappropriate for your needs implement session-per-conversation yourself.
The basic premise is at the start of a conversation you open a new hibernate session (with flush mode = manual) and store it in the users' http session. At the start of each subsequent http request you need to ensure sessionFactory.getCurrentSession returns the conversation's hibernate session, and remember to disconnect this session at the end of each request to close the jdbc connection between requests. When you reach the end of the conversation you flush the session to persist all changes, or close without flushing to cancel them.
The hibernate web site / Java Persistence with Hibernate book has some really good info on how to do this, but other than webflow there's no out of the box support in grails. I'm in the process of writing a SessionPerConversation plugin, but it's very early days. My approach was to look at the grails 1.2.0 source code and copy how they implemented .withNewSession, then decorate my controllers with methods for .withConversation, .endConversation and .discardConversation. When I've got a bit further I'll probably post some code on State Your Bizness
The gotchas I've come across so far are...
If the user never ends their conversation the hibernate session will be kept open (although not the jdbc connection) until their http session times out. If you support multiple conversations, then each user may have multiple hiberate sessions and for a high usage site you could get memory problems
You have to watch out for automatic session flushing. This can happen when you new up entities depending on which strategy you use for id generation,
or if your calling transactional services.
Some thoughts:
Break your code out of the controller and put it in a service.
Set the static 'transactional' property of the service to false and take control of the transaction. It might look a little like this:
class DocumentService {
// take control from spring
static transactional = false
void updateMethod() {
Document.withTransaction { transact ->
// handle your business
// problems? - you can always rollback without breaking anything
transact.setRollbackOnly()
}
}
}
This will let you inject the service into your controller with the line 'def documentService'. You can handle all of your logic in the service and test everything more thoroughly.
精彩评论