PDA

View Full Version : Request-level transactions for loading, binding and persisti


assaf
Nov 5th, 2004, 11:00 AM
One of the nice things about Spring's MVC (SimpleFormController) is the ability to bind data directly to a domain (entity) object's bean properties.

So, going through what happens in handleRequest:
1) In formBackingobject, let us assume we load the entity from a transactionalized service layer.

2) The entity's simple properties are bound automatically.

3) In onBind, we manipulate some of the entity's collections.
If we don't use OpenSessionInView, we'll get a "[net.sf.hibernate.LazyInitializationException] - Failed to lazily initialize a collection - no session or session was closed."
So, for now we'll use OpenSessionInView, with singleSession = false.

4) However, in onSubmit, when we pass this domain object into a transactionalized service-layer "modify" method, we now get an "[net.sf.hibernate.HibernateException] Illegal attempt to associate a collection with two open sessions".

Now, what we really wanted to do here was to enclose the whole request into a transaction, so that the service-layer loading, Spring binding and service-layer persistence all use the same session within the same transaction.

So, my solution is to transactionalize the FormController itself, and specifically it's handleRequest method.
This does away with the need for OpenSessionInView (we can still use it as it was meant of course, for lazy collection access in the View).
It seems to work perfectly well as far as Hibernate is concerned, but is it kosher?

Best regards,
Assaf

Rod Johnson
Nov 6th, 2004, 08:57 AM
Sure, it will work, and it's a nice consequence of IoC management of web tier objects that it's possible, but I stil find it a bit questionable. I just don't like the idea of transactions--which are a business layer issue--being managed from UI code. However, there may not be so many strong arguments against what you're doing, and if it's working for you, it's defensible :-) It could limit reuse of your business objects, as they won't work properly without the UI tier driving transactions.

assaf
Nov 8th, 2004, 04:46 AM
If this approach is "questionable", my other option seems to be to create a UI object graph with near-identical properties to the domain objects, allow Spring to bind to the UI objects instead of domain objects, and then pass these UI objects into the service layer to be "cloned" onto the persistent domain objects.

This would effectively keep transactions at the service layer, where they belong, but it would seem a shame to create this parallel object graph and especially to write all of the cloning code when my gut feeling is that domain objects, representing the primary business entities of the problem domain, should be available to all layers: DAO, service and UI.

Is this the approach most people are taking? Or am I missing something here?

Best regards,
Assaf

assaf
Nov 10th, 2004, 08:26 AM
Just a note to explain what I settled on:
Basically, I wanted to maintain two architectural principles, if at all possible:
1) lazy-load the collections
2) manipulate domain objects on all levels: DAO, Service, UI

On the other hand, it does seem a shame to transactionalize the handleRequest call in the UI layer, since it breaks the layering abstraction.

To do away with UI-layer transactions, two things had to be done:
a) get rid of OpenSessionInView completely, to avoid "Illegal attempt to associate a collection with two open sessions" when sending an existing domain object back into a service-level call (e.g. after binding by Spring).
b) provide methods to touch any required collections in the Service layer - I settled on two methods for doing this - one which touches all of an object's collections when first loading it (but not its children's collections), and another to be used for an already-loaded object (e.g. initially loaded as a child in a collection).
The latter method first reconnects the object:
this.getHibernateTemplate().lock(entity, LockMode.NONE);
and then touches its collections.

So, now I can keep transactions on the service layer without incurring either the "two open sessions" error, or (if I'm careful) the "Failed to lazily initialize a collection".

However, the latter error can still occur at runtime anytime an entity's collections haven't been "touched" ahead of time. This still seems better than doing away with lazy-loading completely and constructing a whole object graph at every database load.

I'd be interested to know how others have dealt with this.
Best regards,
Assaf

bpolka
Nov 11th, 2004, 09:26 AM
you got it. That is exactly how I do it in my apps.

There are alternatives to it though. I have been playing around with AspectJ to enable transparent collection touching and current version verification, but have not had much time to finish anything.

People are also talking about spring managed hibernate created objects, that you might want to take a look at, here:

http://forum.springframework.org/showthread.php?t=9846

Ben Alex
Nov 11th, 2004, 03:59 PM
The DependencyInjectionInterceptorFactoryBean is very useful, especially for things like wiring an application context-managed Validator into each domain object instance, allowing it to self-manage its own validation.

assaf
Nov 12th, 2004, 05:21 AM
There are alternatives to it though. I have been playing around with AspectJ to enable transparent collection touching and current version verification, but have not had much time to finish anything.

Interesting idea... Just read through a bit of AspectJ documentation to see how it might work. All I was able to imagine so far was an aspect for reconnecting an entity whenever lazy instantiation was required. Since I can't even claim to be an AspectJ newbie, that might be very naive on my behalf.... Something like:
aspect ReconnectWhenNeeded {
pointcut getCollection(Entity entity):
(target(entity) &&
(call(List com.blah.domain.*.get*()) ||
call(Set com.blah.domain.*.get*()) ||
call(Map com.blah.domain.*.get*())));

before(Entity entity): getCollection(entity){
// somehow transactionalize this call and associate entity with current session
// equivalent of this.getHibernateTemplate().lock(entity, LockMode.NONE) in a DAO
}
}
Is this anything close to what you had in mind?
Can AspectJ aspects be wired with Spring?

I'm not sure how you'd use spring-managed hibernate entities (i.e. DependencyInjectionInterceptorFactoryBean) to enable on-demand collection touching... short of giving the entity a reference to a service object, and then starting every public getPersistentCollection method with a call to the service.reconnect(entity), which gets the DAO to reconnect the entity. Sound a bit convoluted! But if anybody has a suggestion here, I'd be interested in exploring it.

Best regards,
Assaf

specise
Aug 16th, 2005, 08:59 AM
The DependencyInjectionInterceptorFactoryBean is very useful, especially for things like wiring an application context-managed Validator into each domain object instance, allowing it to self-manage its own validation.

I've read the post and example for use of the DependencyInjectionInterceptorFactoryBean (http://forum.springframework.org/showthread.php?t=9846) and am interested in using it to wire dao's to some of my persistent Hibernate objects. However, Spring version 1.2.3 does not seem to include this class (?).

I discovered that the port of this class to support Hibernate 3 is yet to be released (http://opensource.atlassian.com/projects/spring/browse/SPR-1163), but it seems like from reading the forum, the class for Hibernate 2.x was in a previous Spring release (?). However, I don't see the class in the ...orm/hibernate/support package.

If anyone can tell a nubie what I'm missing, I would greatly appreciate it.

cepage
Aug 16th, 2005, 12:18 PM
However, Spring version 1.2.3 does not seem to include this class (?).

You will need to check out the source from CVS. The class is located in the sandbox, but it is not packaged in binary form as part of the Spring release.

specise
Aug 16th, 2005, 05:55 PM
You will need to check out the source from CVS. The class is located in the sandbox, but it is not packaged in binary form as part of the Spring release.

There is a reference to this class in chapter 1 of "Professional Java Development with the Spring Framework" (which is where I first found out about it), so I assumed the class was already in a production release.

Thanks! I'll grab it from CVS and give it a whirl.