Search code examples
javahibernatecachingjpaannotations

Getting object field previous value hibernate JPA


Let's assume I have this class :

@EntityListeners({MyListener.class})
class MyClass {
  String name;
  String surname;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name; 
  }

  public String getSurname() {
    return name;
  }

  public void setSurname(String name) {
    this.name = name; 
  }

  public void save() {
    JPA.em().persist(this);
    return this;
  }

  public void update() {
    JPA.em().merge(this);
  }

  public static MyClass findById(Long id) {
    return JPA.em().find(MyClass.class, id);
  }
}

Now in my MyListener class I'm trying to figure out the previous value MyClass instance versus the new value that is about to get saved(updated) to database. I do this with preupdate metdhod :

@PreUpdate
public void preUpdate(Object entity) {
...some logic here...unable to get the old object value in here           
}

So assuming I have a MyClass objects instance with name and surname :

MyClass mycls = new MyClass();
mycls.setName("Bob");
mycls.setSurname("Dylan");
mycls.save();

This wasn't picked up by listener which is ok because I'm listening only to updates. Now if I were to update this instance like so :

MyClass cls = MyClass.findById(someLongId)
cls.setSurname("Marley");
cls.update();

So this triggers the preUpdate method in mylistener and when I try to :

MyClass.findById(someLongId);

When I debug I already get the newly updated instance but the update hasn't happened yet because when I check in database in column surname it's still Dylan.

How do I get the value from database in my preUpdate method and not the one I just updated?


Solution

  • I think an easy way is to save the previous value in a transient variable that JPA will not persist. So just introduce a variable previousSurname and save the actual value before you overwrite it in the setter.

    If you want to save multiple properties it would be easy if your class MyClass is Serializable.

    If so add a post load listener

    public class MyClass implements Serializable {
    
         @Transient
         private transient MyClass savedState;
    
         @PostLoad
         private void saveState(){
            this.savedState = SerializationUtils.clone(this); // from apache commons-lang
         }
    
    }
    

    But be aware that the savedState is a detached instance.

    You can then access the previous state in your EntityListener.

    You can also move the PostLoad listener to the EntityListener class. But then you need access to the savedState field. I recommend to make it either package scoped or use a package scoped accessor and put MyClass and MyListener in the same package,

    public class MyListener {
    
        @PostLoad
        private void saveState(MyClass myClass){
             myClass.saveState(SerializationUtils.clone(myClass)); // from apache commons-lang
        }
    
    }
    
    public class MyClass implements Serializable {
    
         @Transient
         private transient MyClass savedState;
    
         void saveState(MyClass savedState){
            this.savedState = savedState;
         }
    
    }
    

    EDIT

    Why not put the saved state in the listener during postload? That way you won't need access to the attributes of MyClass

    First of all listener types are registered for a specific class, not an object

    @EntityListeners(class=MyListener.class)
    

    So, if hibernate creates a new listener instance, you can not be sure that the same listener instance is used for the same target object. The hibernate documentation states:

    An entity listener is a stateless class with a no-arg constructor.

    Since hibernate assumes that the entity listener has no state, it can create new instances whenever it likes and use them however it likes. Therefore, it would be a bad idea to put entity instance specific state in a listener instance.

    Finally, if you also want to access the previous state in your entity you have no chance if it would be stored in the listener.