views:

330

answers:

2

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 with 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.

A: 

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.

Brandon
thx for your response. in fact this is already the way it is done in my DocumentController.update action what i was trying at first was to detach the overall object graph, but that led into problems with dirty object collections, merging...did you have similar use-cases in your grails apps? if yes, how did you implement modifying associations and keeping the changes over multiple requests instead of saving the changes immediately?
Andre Steingress
I haven't dealt with similar use cases - so I'm not sure if this is correct. But I would use a service as part of a flow. It seems like the best way to manage what you're talking about would be to scope the service to your flow with another static property called scope. Set scope to 'flow' or 'conversation' (to include subflows) and remember to implement Serializable in your service class. Then you should be able to manage your changes across those scopes instead of worrying about individual requests.
Brandon
+1  A: 

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

  1. 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

  2. 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.

ad 1.) wouldn't it be possible to serialize the hibernate session (i guess the domain classes must be serializable than) and throw the data into a hidden field? http session's memory consumption would be reduced though...ad 2.) i thought using an appropriate flush-mode would disable automatic session flushing even when using transactional services etc.?
Andre Steingress
1. Hibernate's session is serializable so you could probably do this, however you would need to submit the serialized session for any request occuring within your conversation - including navigation and redirects. For this scenario it's more convenient to associate the hibernate session with a conversationId, and pass the conversationId around as a request parameter. Interesting idea though2. Looks like you're right. I misread the grails webflow docs (which say you have to mark services as non-transactional if you want to store them in flow/conversation scope).
i like that approach - i am going to implement it (not sure about the hidden field, but file serialization could suffice) and post my results afterwards...
Andre Steingress
i started an article series on my blog that deals with Hibernate integration in Grails: http://andresteingress.wordpress.com/2010/03/23/getting-the-persistence-context-picture-part-i/
Andre Steingress