Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Interceptors - Why is onFlushDirty called after onSave?

The Plan

I am using Hibernate to implement createDate and lastUpdate Timestamps for a little project. I use an EmptyInterceptor and overload the provided methods based on the proposed solution i found here. The solution works fine unless one little detail. I want to add a column indicating if an object has been updated already. I know I could achieve this by simply comparing if there is a difference in the two created and updated timestamps, but I need to have this field indicating that there was an update.

I use the onSave method which gets called when a new object is stored to set the wasUpdated value to 'N', indicating that there was no update. In the onFlushDirty() method I set this value to 'Y'.

The Problem

I would expext that when I create and persist a new Object, that the createDate and the lastUpdate fields have the same Date, but the wasUpdated field is set to 'N' as there was no update. I only use session.save() in my code, there is no session.update() and also no session.saveOrUpdate(). The logs of Hibernate indicate that there is actually an Update, which sets the wasUpdated value to 'Y'.

What can be the source of this update? Where is it triggered?

Object initialization and persistence

I disabled auto-commit in the hibernate.cfg.xml.

<property name="hibernate.connection.autocommit">false</property>

This is how I create the object:

ExampleObject ex = new ExampleObject();
ex.setValue("TestStringValue");
this.session = HibernateUtil.getSessionFactory().openSession();
this.session.beginTransaction();
this.session.save(ex);
this.session.getTransaction().commit();
this.session.close();

The Interceptor

@Override
    public boolean onSave(Object entity, Serializable id, Object[] state,
                  String[] propertyNames, Type[] types) {


    if (entity instanceof TimeStamped) {

        Date insertDate = new Date();
        int indexOfCreateDateColumn = ArrayUtils.indexOf(propertyNames, "createdDate");
        int indexOfUpdatedDateColumn = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        state[indexOfCreateDateColumn] =insertDate;
        state[indexOfUpdatedDateColumn] =insertDate;
        state[indexOfWasUpdated] ='N';

        return true;
    }
    return false;
    }

The second method is for setting the lastUpdatedDate and setting the wasUpdated field to 'Y'.

@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                    Object[] previousState, String[] propertyNames, Type[] types) {

    if (entity instanceof TimeStamped) {

        int indexOfLastUpdate = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        currentState[indexOfLastUpdate] = new Date();
        currentState[indexOfWasUpdated] ='Y';


        return true;
    }
    return false;
}

HibernateUtil

I use this configuration for the session.

public class HibernateUtil {
    private static SessionFactory sessionFactory;
    private static ServiceRegistry serviceRegistry;

    static {
    try {

        Configuration configuration = new Configuration().setInterceptor(new TimeStampInterceptor());
        configuration.configure();

        serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);


    } catch (HibernateException he) {
        System.err.println("Error creating Session: " + he);
        throw new ExceptionInInitializerError(he);
    }
    }

    public static SessionFactory getSessionFactory() {
    return sessionFactory;
    }
}

Versions

I use Maven and Java 1.7.0_40.

    <!--. Hibernate -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.4.Final</version>
    </dependency>
like image 952
Stefan Avatar asked Aug 06 '14 08:08

Stefan


People also ask

Which is the given event called in appendage public boolean onSave session s?

onSave()- this method is called before on object is getting saved and we can modify the state of an instance. Modified state will be persisted. onDelete()-is called before on object is getting deleted.

Which of the following interceptor method will be called when the flush () method is called on a session object?

findDirty() This method is be called when the flush() method is called on a Session object.

Which events is called after the object is loaded from the database?

onLoad. Called after an entity is loaded.

What is $$ hibernate interceptor?

The Hibernate Interceptor is an interface that allows us to react to certain events within Hibernate. These interceptors are registered as callbacks and provide communication links between Hibernate's session and application.


2 Answers

JavaDoc of onFlushDirty() method has the following statement:

Called when an object is detected to be dirty, during a flush.

So there is no difference whether object became dirty due to update() call or it is dirty due to save() call. Thus onFlushDirty() method will be called on every persistent object when session flushing. Session flush may be initiated explicitly by session.flush() or implicitly in some cases when Hibernate needs it (in your case - before transaction commit).

In your case wasUpdated property will allways be saved with 'Y' value: at first onSave() method will be called, that when session flushes onFlushDirty() method will be called at the same entity.

To solve your problem in onFlushDirty() method you should check if entity was updated. If my memory serves me right, when entity is inserting into the table (saving new), previous state is null. Suggesting to implement onFlushDirty() like this:

@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                    Object[] previousState, String[] propertyNames, Type[] types) {

    if (entity instanceof TimeStamped && previousState!=null) {

        int indexOfLastUpdate = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        currentState[indexOfLastUpdate] = new Date();
        currentState[indexOfWasUpdated] ='Y';


        return true;
    }
    return false;
}
like image 91
vp8106 Avatar answered Sep 20 '22 12:09

vp8106


Solution:

Thanks to the hint proposed by @vp8106 I could work around the problem. As I set the lastUpdatevalue to the same date as the creationDate value during record initialization, I just compare the two dates. If they are identical, then this is the firstupdate where I need to set the update indicator wasUpdatedto 'Y'.

Code snippet:

   @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                            Object[] previousState, String[] propertyNames, Type[] types) {




    /**
     * Update the lastUpdateDate value and set the wasUpdated flag to 'Y'
     */
    if (entity instanceof TimeStamped) {


        int indexOfLastUpdate = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfCreatedDate = ArrayUtils.indexOf(propertyNames, "createdDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        Date createdDate = (Date) previousState[indexOfCreatedDate];
        Date lastUpdateDate = (Date) currentState[indexOfLastUpdate];


        /**
         * If createdDate equals lastUpdateDate, this is the first update.
         * Set the updated column to Y
         */
        if (createdDate.equals(lastUpdateDate)) {
            logger.warning("This is the first update of the record.");
            currentState[indexOfWasUpdated] = 'Y';
        }
        // set the new date of the update event
        currentState[indexOfLastUpdate] = new Date();


        return true;
    }
    return false;
}
like image 32
Stefan Avatar answered Sep 17 '22 12:09

Stefan