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

后端 未结 10 1715
余生分开走
余生分开走 2020-12-13 13:33

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

@Component
@RepositoryEventHandler(Customer.class)
public class CustomerEventHan         


        
相关标签:
10条回答
  • 2020-12-13 14:08

    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.
    0 讨论(0)
  • 2020-12-13 14:08

    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"
    }
    
    0 讨论(0)
  • 2020-12-13 14:12

    I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.

    Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.

    @Column(name="STATUS")
    private FundStatus status;
    @JsonIgnore
    private transient FundStatus oldStatus;
    
    public FundStatus getStatus() {
        return status;
    }
    public FundStatus getOldStatus() {
        return this.oldStatus;
    }
    public void setStatus(FundStatus status) {
        this.oldStatus = this.status;
        this.status = status;
    }
    

    from application logs:

    2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
    CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
    
    0 讨论(0)
  • 2020-12-13 14:13

    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();
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-13 14:20

    Just another solution using model:

    public class Customer {
    
      @JsonIgnore
      private String name;
    
      @JsonIgnore
      @Transient
      private String newName;
    
      public void setName(String name){
        this.name = name;
      }
    
      @JsonProperty("name")
      public void setNewName(String newName){
        this.newName = newName;
      }
    
      @JsonProperty
      public void getName(String name){
        return name;
      }
    
      public void getNewName(String newName){
        return newName;
      }
    
    }
    

    Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.

    Example request:

    POST /customers/{id}/identity
    
    {
      "name": "New name"
    }
    
    0 讨论(0)
  • 2020-12-13 14:21

    I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST). What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:

        @Override
        @Transactional
        public <S extends Entity> S save(S entity) {
            EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
            JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
    //here do what I need with the query which can retrieve all old values
            cleanEM.close();
            return super.save(entity);
        }
    
    0 讨论(0)
提交回复
热议问题