如何在@HandleBeforeSave事件中获取旧实体值以确定属性是否已更改?

我正试图在@HandleBeforeSave事件中获取旧实体。

@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()); } } 

如果我尝试使用findOne方法获取旧实体,但这会返回新事件。 可能是因为当前会话中的Hibernate / Repository缓存。

有没有办法获得旧实体?

我需要这个来确定给定的属性是否被更改。 如果属性是更改,我需要执行一些操作。

您目前正在使用基于hibernate的spring-data抽象。 如果find返回新值,则spring-data显然已将对象附加到hibernate会话。

我认为你有三个选择:

  1. 在刷新当前季节之前,在单独的会话/事务中获取对象。 这很尴尬,需要非常精细的配置。
  2. 在弹簧附加新对象之前获取以前的版本。 这是非常可行的。 您可以在将对象传递到存储库之前在服务层中执行此操作。 但是,当另一个感染具有我们已知的相同类型和id的另一个感染时,您可以save对象save为hibernate会话。 在这种情况下使用mergeevict
  3. 使用较低级别的hibernate拦截器,如此处所述。 如您所见, onFlushDirty将两个值都作为参数。 请注意,hibernate通常不会查询以前的状态,只需保存已经存在的实体。 而在db(无选择)中发出简单更新。 您可以通过在实体上配置select-before-update来强制执行选择。

如果使用Hibernate,您只需从会话中分离新版本并加载旧版本:

 @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 } } } 

感谢Marcel Overdijk创建机票 – > https://jira.spring.io/browse/DATAREST-373

我看到了这个问题的其他解决方法,并且想要提供我的解决方法,因为我认为实现起来非常简单。

首先,在您的域模型中设置一个瞬态标志(例如Account):

 @JsonIgnore @Transient private boolean passwordReset; @JsonIgnore public boolean isPasswordReset() { return passwordReset; } @JsonProperty public void setPasswordReset(boolean passwordReset) { this.passwordReset = passwordReset; } 

其次,检查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); } } 

注意:对于此解决方案,您需要发送另外的resetPassword = true参数!

对我来说,我使用以下请求有效负载向我的资源端点发送HTTP PATCH:

 { "passwordReset": true, "password": "someNewSecurePassword" } 

使用模型的另一个解决方案

 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; } } 

替代考虑。 如果您需要对此用例进行一些特殊处理,然后单独处理,可能是合理的。 不允许在对象上直接写入属性。 使用自定义控制器创建单独的端点以重命名客户。

示例请求:

 POST /customers/{id}/identity { "name": "New name" } 

使用它创建以下内容并扩展您的实体:

 @MappedSuperclass public class OEntity { @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(); } } } 

我确实有这个需求,并解决了向实体添加瞬态字段以保留旧值,并修改setter方法以将先前值存储在瞬态字段中。

由于json反序列化使用setter方法将rest数据映射到实体,因此在RepositoryEventHandler中我将检查transient字段以跟踪更改。

 @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; } 

从应用程序日志:

 2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin CompartmentEntity [status=ACTIVE, oldStatus=CREATED] 

我遇到了同样的问题,但我希望在REST存储库实现(Spring Data REST)的save(S entity)方法中使用旧实体。 我所做的是使用’clean’实体管理器加载旧实体,我从中创建QueryDSL查询:

  @Override @Transactional public  S save(S entity) { EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager(); JPAQuery query = new JPAQuery(cleanEM); //here do what I need with the query which can retrieve all old values cleanEM.close(); return super.save(entity); } 

不知道你是否仍然在回答,这可能有点’hacky’,但你可以用EntityManager形成一个查询并以那种方式获取对象……

 @Autowired EntityManager em; @HandleBeforeSave public void handleBeforeSave(Customer obj) { Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId()); Customer ret = q.getSingleResult(); // ret should contain the 'before' object... }