|
Some issues I ran into on this:
-supporting lazy initialization Since a view is rendered by the DispatcherServlet after the FlowController has finished processing (and hence, after the requestProcessed method is called for FlowExecutionlisteners), a session has to be kept open longer than the flow session is active in order to support lazy initialization within the view objects. This is something we needed, and so moved the disconnecting of a session out to a handler interceptor postHandle method. -supporting webflow continuations The ability to load a serialized flowExecution to continue at a flow at a particular point leads to some interesting questions on how it should be handled with a long hibernate session. Since the persistence context and the flow scope are two separate storage locations that might share object references, if the hibernate session is not kept inside the flowExecution, any differences between the state of objects in the flowExecution (flow scoped objects) and the state of the persistence context needs to be reconciled. If they are kept together, then the hibernate session has to be serializable. Neither of these seemed very attractive. The SessionStorage implementations attached are meant to represent these two options, one storing the hibernate externally in the http session, the other storing it in the flow scope. This brings up the following two issues. -keeping flow scoped objects in sync with objects in the persistence context The way I use it now, the state of objects in the persistence context "wins out" over that of the flow scope. When the flow session becomes active, the listener has an option to iterate through options in the flow scope and try to get the copy from the persistence context. In that way, even if you skip back several steps in a webflow, the object(s) you are working with will reflect the most recent state. I'm still testing what the result of that will be; for my application, it doesn't seem to be of consequence. -serializing/deserializing hibernate objects When you serialize and deserialize objects that reside in the persistence context, you end up with different object references, and disconnected persistent collections. For example, you have an object graph in flow scope that you just pulled from a session.get call. The flow scope is serialized out to continuation storage, then deserialized and you pull the object out of flow scope. You will have a different object reference than prior to serializing (and the one in the persistence context), yet if you tried to session.save the object, you probably would get a non-unique object exception because it generates the same persistence key. To complicate things more, any persistent collections in the object graph have a null session reference when deserialized. If you try and access unloaded elements, exceptions are thrown. This was addressed in the same way as above: when the session becomes active, the objects in flow scope are traded for those in the persistence context, which also produces functional collections. -new (unpersisted) flow objects One of my primary uses for webflow was as a "wizard" that lead users through creating a new object graph that would eventually be saved to the database. The previous issues I layed out make this more difficult with long sessions that spans several activations/deactivations of the hibernate session. The method for iterating through the flow scope, replacing objects with their persistence context copy, doesn't work if the object is not yet in the persistence context. Worse yet, if that new object has persistent objects attached to the object graph you are building, they will quickly lose sync with the persistence contex through serialization/deserialization, and attempts to save the new object at the end of the flow will fail. My solution for this was to persist objects early, and not flush them to the db until the transaction is committed. This can be done using session.persist() early (provided your id generation mechanism does not require that the entity be saved to the database before an id is generated), and a session with on "COMMIT" flush mode. Again, for my situation, this was not a problem, but in some cases it could be. I hope this is information someone find valuable. It would be great to hear ideas on other solutions to any of these problems. Keith - is this patch being considered? If so, can you assign a "fix version" to this issue? Thanks.
Andrew - have you got any unit tests to go along with this code? I've started using it and would like to create some unit tests, but it would be great if some already exist.
Updated for SWF 1.0
I've been using this openSessionInFlowInterceptor for couple of weeks, and things are looking great.
However, I had some problem getting it to work, with the update example posted here. Here is my 'setup', hope it helps someone! The following setup is in a Spring MVC - Spring WebFlow setup. Since the OpenSessionInView and OpenSessionInFlow can't exist side by side, I had to split them up. <!-- ########################### Maps url's to controllers No webflow's ########################### --> <util:properties id="urlMapping-mappings" location="classpath:xxx/xxx/xxx/web/urlMapping-controllerMappings.properties" /> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping "> <property name="mappings" ref="urlMapping-mappings" /> <property name="order" value="1" /> <property name="interceptors"> <list> <ref bean="openSessionInViewInterceptor" /> </list> </property> </bean> <!-- ########################### Maps url's to controllers Webflow's ########################### --> <util:properties id="urlMapping-mappings-flows" location="classpath:xxx/xxx/xxx/web/urlMapping- flows-controllerMappings.properties" /> <bean id="urlMapping-flows" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings" ref="urlMapping-mappings-flows" /> <property name="order" value="2" /> <property name="interceptors"> <list> <ref bean="openSessionInFlowListenerInterceptor" /> </list> </property> </bean> <!-- ########################### Interceptors ########################### --> <bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> <property name="sessionFactory"> <ref bean="sessionFactory" /> </property> </bean> <bean id="openSessionInFlowListenerInterceptor" class="org.springframework.webflow.session.OpenSessionInFlowListenerInterceptor "> <property name="sessionStorage" > <bean class="org.springframework.webflow.session.impl.HttpSessionBackedSessionWrapperStorage" /> </property> <property name="sessionFactory" ref="sessionFactory" /> </bean> from applicationContext-controller.xml <!-- ################### --> <!-- WEBFLOW CONTROLLERS --> <!-- ################### --> <bean name="customerFlowController" class="org.springframework.webflow.executor.mvc.FlowController"> <property name="flowExecutor" ref="flowExecutor" /> <property name="defaultFlowId" value="customer-flow" /> </bean> <bean id="flowExecutor" class="org.springframework.webflow.config.FlowExecutorFactoryBean "> <property name="definitionLocator" ref="flowRegistry" /> <property name="repositoryType" value="CONTINUATION" /> <property name="executionListenerLoader" ref="listenerLoader"/> </bean> <bean id="flowRegistry" class="org.springframework.webflow.engine.builder.xml.XmlFlowRegistryFactoryBean"> <property name="flowLocations"> <list> <value>/WEB-INF/flows/xxx/**-flow.xml</value> </list> </property> </bean> <bean id="listenerLoader" class="org.springframework.webflow.execution.factory.ConditionalFlowExecutionListenerLoader"> <property name="listeners"> <map> <entry> <key><ref bean="openSessionInFlowListenerInterceptor" /></key> <value>*</value> </entry> </map> </property> </bean> Added a HibernateSessionPerConversationListener. Add the SessionFactory to the listener and the listener to the executor. All standard Spring/Hibernate behavior will work. Both native Hibernate and HibernateTemplate repositories will participate in the session and transaction created by the listener.
Revision 7064 Hi Ben, nice work.
A couple of questions: i) Does the session remain bound for the rendering of the view? it looks like it doesn't. ii) How will this work in an AJAX scenario? access the hibernate session would need to be serialised In both of these cases HibernateSessionPerConversationListener could be extended to provide the required functionality but this may be something to keep in mind. Can the updated patch, (including HibernateSessionPerConversationListener), of this enhancement be made available before 1.1 release? If not, when is 1.1 due to be released?
Apologies if this update is available - I cant seem to locate it. The attachments above our out-dated. Ignore the above - I've accessed the snapshot.
A comment from the forums (by Will Parker):
Hi With regards to the above class which is still in beta version... Shouldn't the exceptionThrown() method rollback the transaction? Currently it calls unBindSession() which performs a commit(). Should the lines in exceptionThrown() be changed from: Session hibSession = getHibernateSession(context); unBindSession(hibSession); to.... Session hibSession = getHibernateSession(context); hibSession.getTransaction().rollback(); TransactionSynchronizationManager.unbindResource(h ibSession); |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
See the javadocs for info and example config, specifically the javadoc for OpenSessionInFlowListenerInterceptor.java
I'll post a comment with general issues I ran into. Also, this version is adapted from an earlier one that worked with previous versions of swf. I'll comment on how it got harder with 1.0 ,as well as 1.0's potential for simplifying things.