-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: HistoryEnabled objects (insert new row on update)
PostPosted: Mon Feb 19, 2007 7:09 am 
Newbie

Joined: Wed Jan 24, 2007 12:30 pm
Posts: 2
HistoryEnabled objects.

On the project I'm currently on we need a feature that enables the user to see what data look like at any point in time. To me this looked like a classic out of the book example where you just add the validFrom and validTo columns to the entities, and end up with 2 queries:

- get current data: select * from mytable where validTo is null
- get "historical" data: select * from mytable where givenDate between validFrom and validTo

I discussed this with some other programmers on the project an we agreed that this would probably be a good approach. However it would be nice to implement it as general and transparent as possible so that the cost of adding this feature to any entity would be as low as possible.

I came up with the following solution:

1 Entities have to extend the "HistoryEnabled" class (has validFrom and validTo properties).
2 EventListener plugged into Hibernate intercept save-update events. If object to be saved/update is instanceof HistoryEnabled: inserts a new row instead of overwriting the existing.
3 Use a filter to exclude "nonvalid" (date out of scope) objects when quering.

This was quite easy to implement, however I am a bit unsure how good step 2 is. The reason for that is that we actually change the primary key before saving the object to make Hibernate insert a new row instead of updating the existing row. We've tried to think about scenarious where this would cause issues and have only come up with one so far; If there is a relation between two entities that are both "HistoryEnabled" an "update" would make them inconsistent because they would both reference the old copy instead of the new. This is not a serious problem to us as our domain model is designed in a way that entities that need historical data are not directly associated with other historical objects.

Have anyone got comments on this solution to save historical data?

Here is how we implemented the save-update event listener:

Code:
public class HistoryEnabled{

    private Long id;
    private Date validFrom;
    private Date validTo;
(...)
}

public class SaveHistoricalEventListener extends DefaultSavOrUpdateEventListener {

    public void onSaveOrUpdate(SaveOrUpdateEvent evt) throws HibernateException {       
        boolean abortNormalSave = false;

        /* the object we want to save */
        Object toBeSaved = evt.getObject();

        if (toBeSaved instanceof HistoryEnabled) {
           
            HistoryEnabled dirty = (HistoryEnabled) toBeSaved;

            if (dirty.getId() != null) {
                Session session = evt.getSession();

                /*
                 * To enable "historical" saving we have to load the original from
                 * database. Two object with the same primary key can not be
                 * within the same session, so we have to evict dirty before loading
                 * the original
                 */
                session.evict(dirty);

                /* Get the original */
                HistoryEnabled original = (HistoryEnabled) session
                                .get(dirty.getClass(), dirty.getId());


                if (original != null &&  original.getValidTo() == null) {
                    /*
                     * Ok, we're here. this means that the object exist
                     * in the database and we are trying to overwrite the
                     * latest version of this object (validTo == null).
                     *
                     * We will change that so
                     * "dirty" gets a new primary key (doesn't overwrite original)
                     * "dirty.validFrom" - set value to "new Date()"  (becomes the new "current"t)
                     * "original.validTo" - set value to "new Date()"  (becomes "historical")
                     */

                    /* remove primary key so a new row is inserted
                     * and original is not overwritten */
                    dirty.setId(null);

                    /* set correct validTo/From */
                    dirty.setValidFrom(new Date());
                    original.setValidTo(dirty.getValidFrom());
                                                           
                    session.update(original); // update existing
                    session.save(dirty);  // insert new
                   
                    /* we've done the saving our way, stop hibernate from messing up */
                    abortNormalSave = true;
                } else if( original != null
                        && original.getValidTo() != null ) {
                    /* not allowed to overwrite historical objects */
                    abortNormalSave = true;
                }
            }
        }

        /* Continue "normal" save if "historical save" was not done */
        if (!abortNormalSave) {
            super.onSaveOrUpdate(evt);
        }
    }
}


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 26, 2007 9:15 am 
Beginner
Beginner

Joined: Fri Feb 11, 2005 12:03 pm
Posts: 48
Location: Kiel, Germany
Hi,

I am currently also implementing a history for entities.

I am using the following approach:

1. entities to be historized are annotated with my own annotation Historicizable
2. the SessionFactory is configured with an Interceptor which implements onSave, onFlushDirty and onDelete
3. the Interceptor writes information about the changes in the database such that every state of an entity can be restored by applying all changes for an entity in the history

This approach keeps the entities separated from the aspect of historization and the read operations do not have to care about times when the entity was valid.

Best regards
Achim


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.