|
From: Daniel M. <mi...@pa...> - 2004-06-09 02:48:33
|
Hello, One of the current critiques of the OpenSessionInViewFilter/Interceptor that ships with Spring is that a Hibernate session is open for the entire duration of the request (not just during the rendering of the view). The danger here is that a failed session (due to DataAccessException) will not be discarded. I have devised a new implementation that only adds slightly more complexity to the pattern, but maintains the benefit of lazy loading in the view without a thread-local session during filter/controller processing. Conveniently, the HandlerInterceptor interface has three methods: preHandle is called at the beginning of the request; postHandle is called after the controller has completed, but before the view is rendered; and afterCompletion is called after the view has been rendered. The current implementation of OpenSessionInViewInterceptor opens a thread-local Hibernate session in the preHandle method. Instead of opening the thread-local session in preHandle, it can be opened in postHandle. Now we have a session for just the view, but there is a problem: the data objects loaded for the view by the controller are not associated with our new session. The solution to this problem is to re-bind objects to this session that need lazy loading capabilities during view rendering. This can be accomplished by populating a list of data objects during filter/controller processing. The list is kept as a request attribute so any logic along the request chain prior to postHandle can simply add loaded data objects to this list. Then in the postHandle method each of these objects can be bound to the new session (there is one gotcha here that I will go over later). Next the view is rendered, lazily loading objects if/when necessary. Finally, afterCompletion closes the session. Like I said, there is a hiccup to watch out for when using this pattern... If separate instances of the same data object (e.g. two or more objects of the same type that have the same identifier) are placed in the list of objects to be added to the session, Hibernate's NonUniqueObjectException will be thrown. The problem here is that Hibernate cannot bind an object to the session if another object with the same identifier already exists in the session. There are two workarounds that I have thought of to deal with this problem: 1. Check the list before loading an object from the database. Actually it's easier to wait until after the object is already loaded, and then check if the list contains that object. If it does, discard the just-loaded-one and use the one from the list. That way your view will be referencing a single object in memory instead of two or more separate copies, which means lazy loading only needs to be performed one time instead of for each separate instance that must be initialized. 2. If scenario 1 is a problem (I have run into a few of these cases, but they are too complicated to explain here), the simplest thing to do is to pre-initialize all but one of the instances that would otherwise use lazy initialization. Luckily this case is not very common. There is usually only a single object (tree) that must be pre-initialized, and usually one of the objects clearly benefits from lazy initialization while the other(s) only need(s) very simple initializations that can easily be performed in a DAO/SAO. I see a few benefits of this implementation that (IMHO) make the exceptions worth dealing with: Flexibility: lazy loading is possible in the view. This cuts down on the number of service methods that must be created and maintained to initialize all objects (and their related objects) that will be accessed by the view. This also adds flexibility to the view, as it can be modified more extensively without requiring modifications on the SAO/DAO layer. Stability: command (form) objects (or any data objects for that matter) that have been loaded and modified but not persisted (e.g. validation failed) are not required to participate in the view-session. One can choose which objects will participate in the view session. Thus, they will never be inadvertently flushed to the database during some unrelated data access (this can happen with the OpenSessionInView implementations provided by Spring currently). I would be happy to implement a version of this to ship with Spring if there is a demand. Let me know what you think. Daniel Miller |