Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get old entity value in @HandleBeforeSave event to determine if a property is changed or not?

I'm trying to get the old entity in a @HandleBeforeSave event.

@Component
@RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {

    private CustomerRepository customerRepository;

    @Autowired
    public CustomerEventHandler(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    @HandleBeforeSave
    public void handleBeforeSave(Customer customer) {
        System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
        System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());

        Customer old = customerRepository.findOne(customer.getId());
        System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
        System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
    }
}

In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.

Is there a way to get the old entity?

I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.

like image 506
Marcel Overdijk Avatar asked Aug 10 '14 21:08

Marcel Overdijk


4 Answers

If using Hibernate, you could simply detach the new version from the session and load the old version:

@RepositoryEventHandler 
@Component
public class PersonEventHandler {

  @PersistenceContext
  private EntityManager entityManager;

  @HandleBeforeSave
  public void handlePersonSave(Person newPerson) {
      entityManager.detach(newPerson);
      Person currentPerson = personRepository.findOne(newPerson.getId());
      if (!newPerson.getName().equals(currentPerson.getName)) {
          //react on name change
      }
   }
}
like image 75
Gabriel Bauman Avatar answered Oct 21 '22 13:10

Gabriel Bauman


Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373

I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.

First, set a transient flag in your domain model (e.g. Account):

@JsonIgnore
@Transient
private boolean passwordReset;

@JsonIgnore
public boolean isPasswordReset() {
    return passwordReset;
}

@JsonProperty
public void setPasswordReset(boolean passwordReset) {
    this.passwordReset = passwordReset;
}

Second, check the flag in your EventHandler:

@Component
@RepositoryEventHandler
public class AccountRepositoryEventHandler {

    @Resource
    private PasswordEncoder passwordEncoder;

    @HandleBeforeSave
    public void onResetPassword(Account account) {
        if (account.isPasswordReset()) {
            account.setPassword(encodePassword(account.getPassword()));
        }
    }

    private String encodePassword(String plainPassword) {
        return passwordEncoder.encode(plainPassword);
    }

}

Note: For this solution you need to send an additionally resetPassword = true parameter!

For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:

{
    "passwordReset": true,
    "password": "someNewSecurePassword"
}
like image 24
GedankenNebel Avatar answered Oct 21 '22 11:10

GedankenNebel


You're currently using a spring-data abstraction over hibernate. If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.

I think you have three options:

  1. Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
  2. Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
  3. Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
like image 5
Maarten Winkels Avatar answered Oct 21 '22 11:10

Maarten Winkels


Create following and extend your entities with it:

@MappedSuperclass
public class OEntity<T> {

    @Transient
    T originalObj;

    @Transient
    public T getOriginalObj(){
        return this.originalObj;
    }

    @PostLoad
    public void onLoad(){
        ObjectMapper mapper = new ObjectMapper();
        try {
            String serialized = mapper.writeValueAsString(this);
            this.originalObj = (T) mapper.readValue(serialized, this.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
like image 2
ArslanAnjum Avatar answered Oct 21 '22 12:10

ArslanAnjum