我想在@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会话。
我认为你有三个选择:
save
作为一个对象也是一个hibernate会话。在这种情况下使用merge
或evict
。onFlushDirty
将两个值都作为参数。请注意,hibernate通常不会查询您之前的状态只是保存已经存在的实体。而不是在db中发出简单的更新(没有选择)。您可以通过在实体上配置select-before-update来强制执行选择。不知道你是否仍然在回答,这可能有点'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...
}
如果使用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
我看到了这个问题的其他解决方法,并且想要提供我的解决方法,因为我认为实现起来非常简单。
首先,在您的域模型中设置一个瞬态标志(例如,帐户):
@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"
}
我确实有这个需求并解决了向实体添加瞬态字段以保留旧值,并修改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]
使用模型的另一个解决方案
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<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();
}
}
}
我有同样的问题,但我希望在REST存储库实现(Spring Data REST)的save(S entity)
方法中提供旧实体。我所做的是使用'clean'实体管理器加载旧实体,我从中创建QueryDSL查询:
@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);
}
由于触发事件的位置,Spring Data Rest不能也可能无法执行此操作。如果您正在使用Hibernate,您可以使用Hibernate spi事件和事件侦听器来执行此操作,您可以实现PreUpdateEventListener,然后在sessionFactory中使用EventListenerRegistry注册您的类。我创建了一个小型弹簧库来为您处理所有设置。
https://github.com/teastman/spring-data-hibernate-event
如果你正在使用Spring Boot,它的主旨就是这样,添加依赖项:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
然后将注释@HibernateEventListener添加到第一个参数是您要侦听的实体的任何方法,第二个参数是您要监听的Hibernate事件。我还添加了静态util函数getPropertyIndex,以便更轻松地访问要检查的特定属性,但您也可以查看原始Hibernate事件。
@HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
以下对我有用。在不启动新线程的情况下,hibernate会话将提供已更新的版本。启动另一个线程是一种单独的JPA会话的方法。
@PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
如果有人想要更多背景,请告诉我。