Results 1 to 10 of 10

Thread: AOP Data Access and Hibernate Transactions

  1. #1
    Join Date
    Dec 2004
    Location
    New York
    Posts
    30

    Default AOP Data Access and Hibernate Transactions

    Hi All:

    Have not found a thread that addresses this issue, so hopefully it has not been covered before. I am trying to write a generic Auditer class that will track changes to objects made through the service layer. The Auditer class is applied as an aspect against certain service layer calls. Specifically, the joinpoint is set to fire on execution of service methods matching addFooEntity, updateFooEntity, deleteFooEntity and the Auditer class supplies the appropriate advice (e.g., log something like "User JoeBlow added FooEntity with Id 123"). This all works fine, except in the case of an update, where I want to track which fields change. For an update, I would like to use Before advice to get the existing object from the database before the update and compare it against the updated entity.

    Here is the relevant code from the Auditer class:

    Code:
    @Before("com.xonos.common.aop.Auditer.editActionPointcut()")
    public void doEditAudit(JoinPoint jp) throws Throwable {
    	Object service = jp.getThis();
    	Object updatedEntity = jp.getArgs()[0];
    	Method serviceGetMethod = service.getClass().getMethod("get"+updatedEntity.getClass().getSimpleName(), new Class[]{java.lang.Long.class});
    	Method entityIdGetter = updatedEntity.getClass().getMethod("getId");
    	Long id = (Long)entityIdGetter.invoke(updatedEntity);
    	// get existing entity
    	Object existingEntity = serviceGetMethod.invoke(service, id);
    	existingEntity.getClass().getMethod("getId").invoke(existingEntity);
    	Map existingEntityPropMap = BeanUtils.describe(existingEntity);
    	Map updatedEntityPropMap = BeanUtils.describe(updatedEntity);
    	// at this point, just write the two objects' properties to STDOUT
    	for(Iterator i = existingEntityPropMap.keySet().iterator(); i.hasNext();){
    		Object x = i.next();
    		System.out.println(x+" : "+existingEntityPropMap.get(x));
    	}
    	for(Iterator q = updatedEntityPropMap.keySet().iterator(); q.hasNext();){
    		Object y = q.next();
    		System.out.println(y+" : "+updatedEntityPropMap.get(y));
    	}
    	
    }
    Here's my problem: everything works OK, but the existingEntity always shows up as the same as the updatedEntity. After looking closely at the logs, this is what I see: 1) the Hibernate transaction begins and the updated entity is placed into the session, 2) my AOP Auditer class fires and tries to retrieve the existing object 3) Hibernate sees that the object exists in session and returns the object with updated information, never hitting the database. I have tried setting the precedence of the Auditor class higher than the Hibernate transaction proxy using the order attribute in my config, but this didn't help. There are probably some kludgy ways to get around this issue programmatically, but want to see if anyone has an idea for elegantly solving this problem declaratively.

    Thanks in advance for any ideas,

    Dave
    Dave Reed
    Xonos ECommerce

  2. #2
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,695

    Default

    Isn't this easier to implement using a hibernate interceptor, that way you have access to the old and new values. That is easier/better then to hack around....
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  3. #3
    Join Date
    Dec 2004
    Location
    New York
    Posts
    30

    Smile

    Hi Marten - thank you for the tip. This will make the Auditer tied to Hibernate, but don't think that will matter in this case, and we can always write something up for other data access methods. Thanks!
    Dave Reed
    Xonos ECommerce

  4. #4
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,695

    Default

    It makes your code a lot easier.

    The problem is the first level cache if you use the same session objects will be retrieved from the cache instead of the database.

    You could manually create a new session (tying your interceptor to hibernate due to the sessionfactory) and retrieve the object from there. But that needs you to manage the session etc.

    Implementing a Hibernate interceptor is much easier because you have all the needed stuff at hand.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  5. #5
    Join Date
    May 2006
    Location
    Charlotte, NC, US
    Posts
    10

    Lightbulb Look to the Strategy pattern, young Skywalker...

    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:
    1. Entity is passed to transactional service method (create/save/delete)
    2. Pull the persistent 'old' state from the database prior to creating/saving/deleting the transient, 'current' state
    3. Compare both states, and produce a delta representation (aka Map)
    4. Allow the create/save/delete to occur
    5. 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
    Dave Joyce
    AVP / Sr Application Consultant
    Capital Markets Front Office
    Bank of America
    www.bankofamerica.com

  6. #6
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,695

    Default

    Shouldn't this be part of your domain model instead of on the save? If you would create some delegate (possible interception by AOP) which acts on the set methods (or whatever methods you find necessary) and creates the delta that way. This would also mean that the resonsibility is within the object itself instead of comparing all properties of an object to see if something was updated. (Which can also be quite a performance hit!).
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  7. #7
    Join Date
    May 2006
    Location
    Charlotte, NC, US
    Posts
    10

    Default

    Yes, you could decorate / wrap your domain object with some proxy that caches old values on calls to set*() methods, but then you have to have some way of having all those decorators / proxies talk to each other to produce a single delta representation... if that's what you're after

    Additionally, while my example code doesn't illustrate this, there is the additional point that deltas (or whatever you're trying to do) may be dependent upon some other "business rules" that form the conditions for determining whether or not the delta/whatever should be generated. This additional logic is outside the concern of the domain model. Actually, it's even outside the concern of the service layer. Encapsulating all of that logic inside a Strategy object that the Service delegates to provides that clean separation of concern, and it keeps the details of your particular need (delta generation, audit logging, etc.) in a single place.
    Dave Joyce
    AVP / Sr Application Consultant
    Capital Markets Front Office
    Bank of America
    www.bankofamerica.com

  8. #8
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,695

    Default

    It isn't that hard to do. A while ago I implemented such a thing for a client, they had something similair to what you build, however with a large object structure with many collections, which had collections, it was hard to determine what changed and what didn't. You have to check each object and each property (resulting in a lot of queries etc.).

    What you implemented in your service/dao is perfectly implementable with AOP, giving you a performance gain.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  9. #9
    Join Date
    May 2006
    Location
    Charlotte, NC, US
    Posts
    10

    Default

    Marten, now I'm confused. In the 1st post, Dave said:

    Quote Originally Posted by dreed View Post
    Here's my problem: everything works OK, but the existingEntity always shows up as the same as the updatedEntity. After looking closely at the logs, this is what I see: 1) the Hibernate transaction begins and the updated entity is placed into the session, 2) my AOP Auditer class fires and tries to retrieve the existing object 3) Hibernate sees that the object exists in session and returns the object with updated information, never hitting the database. I have tried setting the precedence of the Auditor class higher than the Hibernate transaction proxy using the order attribute in my config, but this didn't help.
    Then you said:
    Quote Originally Posted by mdeinum View Post
    Isn't this easier to implement using a hibernate interceptor, that way you have access to the old and new values. That is easier/better then to hack around....
    Quote Originally Posted by mdeinum View Post
    Implementing a Hibernate interceptor is much easier because you have all the needed stuff at hand.
    Which I tried, but I explained the problem with that:
    Quote Originally Posted by david.joyce13 View Post
    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.
    And now you're telling me that I can solve the problem with AOP, which is different than the advice you gave Dave, the 1st poster. As I said in my initial post, I couldn't inject a reference to the SessionFactory into the aspect, because the aspect was not managed by the HibernateTransactionManager. This results in an irrecoverable exception that prevents the ApplicationContext from initializing. So, now, you say:
    Quote Originally Posted by mdeinum View Post
    What you implemented in your service/dao is perfectly implementable with AOP, giving you a performance gain.
    How does one do this with AOP, then? Because I would love to go that direction... it was how I wanted to do this in the first place!

    Thanks!
    Dave Joyce
    AVP / Sr Application Consultant
    Capital Markets Front Office
    Bank of America
    www.bankofamerica.com

  10. #10
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,695

    Default

    It all depends on what you want to achieve. If you already put the changed object into the hibernate session and use a manual compare to determine any changes (i.e. iterate over all the fields, collections etc. and check for differences) that fails, also that can be very slow (from a performance p.o.v.).

    The fact that it doesn't work with your setup, doesn't mean the HibernateInterceptor approach doesn't work (I would try to figure out WHY it doesn't work and fix that instead of doing an ugly manual compare between 2 objects!). I guess retrieving the object, update the fields on the retrieved object and use the HibernateInterceptor probably works.

    I've implemented a Delta/Audit in different ways, with a HibernateInterceptor, local to the object and with AOP.

    It isn't that hard with AOP you only have to let go of the fact that you NEED the stuff stored in the database. You already have that state in memory! Simply create an aspect which stores your Delta in a ThreadLocal and add stuff on each call to a setXXX method (retrieving the value with the getXXX method) if changed store old/new values. Implementing a strategy (like you have) shouldn't be that hard.

    Once it was quite a business requirement and we decided to make it part of the domain, so each call to a method which changed something did also a call to an AuditTrail implementation local to that instance (some aop scoping etc. was in place but still quite straith forward non the less).
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •