Announcement Announcement Module
Collapse
No announcement yet.
AOP Data Access and Hibernate Transactions Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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

  • #2
    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....

    Comment


    • #3
      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!

      Comment


      • #4
        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.

        Comment


        • #5
          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

          Comment


          • #6
            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!).

            Comment


            • #7
              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.

              Comment


              • #8
                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.

                Comment


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

                  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:
                  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....
                  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:
                  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:
                  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!

                  Comment


                  • #10
                    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).

                    Comment

                    Working...
                    X