First, let me say that I find it interesting that someone else was having this problem almost simultaneously! 
So, for some quick backstory... my need is the ability to generate a delta from the state differences between the entity in the database and the entity to-be-persisted. We have an external system (PeopleSoft) that requires updates of particular value changes on properties of persitent domain objects, and it has no knowledge of our application's domain model. So, what I need to ship to the external system is a collection of property names and the associated before-and-after data values for each property. Sounds pretty simple, right?
...exactly. Man, did I bang against this problem for a long time! At first, like you, I thought, "hey, I'll just write a little delta generator aspect class and wrap it around the transactional service methods I care about (create/save/delete) and it'll be done."
Wrong! The ApplicationContext / HibernateTransactionManager didn't like an Aspect being wired with a reference to the SessionFactory it was managing that was not, itself, being managed by the HibernateTransactionManager. Consequently, my ApplicationContext would blow up and never initialize. My apologies for not providing the specific Exception trace here from that nightmarish expedition.
Next attempt: a class that implemented the Hibernate Interceptor interface. I had a kind of eureka moment when I remembered / realized that Hibernate has an event architecture, and that it tracks all the phases of the Session lifecycle at a very high level of granularity. Anyway, I wrote a DeltaGeneratorInterceptor that I then wired into the SessionFactory bean definition as follows, for per-Session interception:
Code:
<bean id="deltaGeneratorInterceptor"
class="x.y.z.DeltaGeneratorInterceptor"/>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="entityInterceptorBeanName"
value="deltaGeneratorInterceptor"/>
</bean>
Sadly, this also didn't work. In the DeltaGenerator#onFlushDirty method, the 'previousState' argument was alway null, and therefore I didn't have the old state values against which to compare the new, transient, about-to-be-persisted values.
At this point (yesterday), I stepped back and totally reevaluated my requirement and concerns:
- Entity is passed to transactional service method (create/save/delete)
- Pull the persistent 'old' state from the database prior to creating/saving/deleting the transient, 'current' state
- Compare both states, and produce a delta representation (aka Map)
- Allow the create/save/delete to occur
- Done!
I realized that my solution had to be involved or 'inside' the transactional context somehow, because my attempts to wrap around or intercept the transactional method execution in a loosely decoupled way were not working. This is when I had my major eureka moment: the friggin' Strategy pattern, man!
So, I have an interface, DeltaStrategy, that handles the save/delete on behalf of the Service:
Code:
public interface DeltaStrategy<V> {
public V saveEntity(V entity);
public void deleteEntity(V entity);
public void generateDelta(V previousEntityState, V currentEntityState);
}
Next, I have another interface, DeltaStrategizable, that will be implemented by objects using a DeltaStrategy object (this may be optional):
Code:
public interface DeltaStrategizable<V> {
public void setDeltaStrategy(DeltaStrategy<V> deltaStrategy);
}
Then, I update my Service interface like so:
Code:
public interface PersonService extends DeltaStrategizable<Person> {
...
}
Here's the config XML for that:
Code:
<bean id="personDeltaStrategy" class="x.y.z.PersonDeltaStrategy">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="personRepository" ref="personRepository"/>
</bean>
<bean id="personService" class="x.y.z.service.PersonServiceDefaultImpl">
<constructor-arg ref="personRepository"/>
<constructor-arg ref="personDeltaStrategy"/>
</bean>
So now, when PersonService.savePerson(Person p) is called, it delegates the actual save to its injected DeltaStrategy. Inside the DeltaStrategy object, I retrieve the 'old' entity state by calling personRepository.findPersonById(person.getId()). The important thing to do right after retrieving the 'old' entity is to evict it from the Session, because otherwise you'll get a NonUniqueObjectException from having 2 instances of the same Session-managed entity in the scope of a single Session. Once you do that, you'll have a detached entity that contains the 'old' state and the transient 'current' entity that can then be handed off to the Repository for the save/update/delete.
Now, you can go ahead with generating a delta, writing to an audit log, etc!!
HTH