VB_
VB_

Reputation: 45692

Hibernate @PreUpdate: check what has been changed

Question: How to check which fields has been changed inside method annotated with @PreUpdate?


OPTIONAL: if the answer to the question above is "It's impossible, than maybe there are another ways to solve my problem"

I want automatically update modified Tourist's field each time we change something in it.

Except the situation when we modify only location. Means if we change location only - it should be persisted, but modified mustn't be changed.

Already present code:

@Entity
public class Tourist {

  private long id;
  private String firstName;
  private String lastName;
  private Date created;
  private Date modified;
  private String location;

  @PreUpdate
  public void preUpdate() {
     modified = new Date(); //PROBLEM : change modified even if only location field has been changed!
  }   
  ....
}

Updated: After some investigations I found that I can solve it with help of interceptors (extend EmptyInterceptor):

public class TouristInterceptor extends EmptyInterceptor{

    Session session;
    private Set updates = new HashSet();

    public void setSession(Session session) {
        this.session=session;
    }

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

        if (entity instanceof Tourist){
            if (somethingChangedExceptLocation()) 
                updates.add(entity);
        }
        return false;
    }

But disadvantage of this approach is to intercept everything when you need to intercept the single entity.

Updated Questions:

Upvotes: 4

Views: 16414

Answers (3)

Alan Hay
Alan Hay

Reputation: 23226

There is a simple non-JPA solution which is as follows but which which does have some repetitive code but is a solution when you do not have too many fields:

@Entity
public class Tourist {

    private long id;
    private String firstName;
    private String lastName;
    private Date created;
    private Date modified;
    private String location;

    public void setFirstName(String firstName){
        if(! this.firstName.equals(firstName){
            modified = new Date();
        }

        this.firstName = firstName;
    }

    public void setLastName(String lastName){
        if(! this.lastName.equals(lastName){
            modified = new Date();
        }

        this.lastName= lastName;
    }
}

Otherwise I would go with saving the previous state on load as suggested in another answer but in a slightly cleaner way.

@Entity
public class Tourist {

  private long id;
  private String firstName;
  private String lastName;
  private Date created;
  private Date modified;
  private String location;
  
  @Transient
  private Tourist previousState;

  @PostLoad 
  public void setPreviousState() {
    previousState = new Tourist();
    //copy fields
  }
  
  @PreUpdate
  public void preUpdate() {
     if (isModified()) {
        modified = new Date();
     }
  }   
  
  private boolean isModified() {
    boolean modified = false;
    
    if (!firstName.equals(previousState.firstName) {
        modified = true;
    }
    
    //check other fields
    
    return modified;
  }
}

Upvotes: 5

ArslanAnjum
ArslanAnjum

Reputation: 1724

Although it is much late, however I enountered a problem where I required to apply update security on certain fields in entity. I solved it using Reflections. Have a look at this thread. spring security for certain entity variables

Upvotes: 0

Hichamov
Hichamov

Reputation: 636

I really encourage you not to do this logic in your Entity !

You should decide whether to change the modified or not at your business logic.

I mean, when you are changing only the location call merge only. And whenever you are changing something else call the <Tourist_instance>.setModified(new Date()); before you call the merge.


To verify if anything else got changed, I suggest having a transient boolean field in your Entity that you set to true whenever you change something else other than location (the idea in the comment won't be sufficient to test all the fields, only if you create a transient field to remember previous value of each one of them)

You will then test this boolean in your preUpdate method

But I highly don't recommend this workaround.


Or, another highly unrecommended way :

@Entity
public class Tourist {

  private long id;
  private String firstName;
  private String lastName;
  private Date created;
  private Date modified;
  private String location;

  @Transient 
  private String firstNamePrevious;
  @Transient 
  private String lastNamePrevious;
  @Transient 
  private Date createdPrevious;
  @Transient 
  private Date modifiedPrevious;
  @Transient 
  private String locationPrevious;

  @PreUpdate
  public void preUpdate() {
     if(!onlyLocationGotChanged()){
             modified = new Date(); 
     }
  }   
  ....

  @PostLoad
  public void postLoad(){
      //Set all previous fields to actual values
      firstNamePrevious = firstName;
      lastNamePrevious = lastName;
      //...etc.
  }
}

Upvotes: -1

Related Questions