Hibernate @PreUpdate:检查已更改的内容

Posted

技术标签:

【中文标题】Hibernate @PreUpdate:检查已更改的内容【英文标题】:Hibernate @PreUpdate: check what has been changed 【发布时间】:2015-02-17 08:12:18 【问题描述】:

问题: 如何检查带有@PreUpdate注解的方法中哪些字段已更改?


可选:如果上述问题的答案是“这是不可能的,那么也许还有其他方法可以解决我的问题”

我想要在我们每次更改时自动更新 modified Tourist 的字段。

除非我们修改location的情况。意味着如果我们只更改 location - 它应该被持久化,但不能更改 modified

已经存在代码:

@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!
     
  ....


更新:经过一番调查,我发现我可以在拦截器的帮助下解决它(扩展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;
    

但这种方法的缺点是在需要拦截单个实体时拦截所有内容。

更新问题:

如何仅拦截旅游实体刷新调用? 是否可以在事件的帮助下做同样的事情?表示 PreUpdateEvent 包含新旧状态

【问题讨论】:

为什么不使用临时字段来存储以前的位置,并在更新前测试它,然后再更新modified 字段? 我建议您创建一个边类,您可以在其中跟踪事件(创建、修改等),并在业务级别而不是 DAO 级别更新它 @Hichamov 我真的很喜欢你关于额外瞬态场的想法! @Hichamov 但我需要检查其他字段是否未更改。请详细说明模式 对不起,我以前从未使用过。我可能说错了 【参考方案1】:

我真的鼓励你不要在你的Entity 中这样做!

您应该根据您的业务逻辑决定是否更改modified

我的意思是,当您仅更改位置时,仅调用 merge。每当您更改其他内容时,请先致电 <Tourist_instance>.setModified(new Date());,然后再致电 merge


要验证是否有其他任何更改,我建议在您更改位置以外的其他内容时在您的Entity 中设置一个临时的boolean 字段,将其设置为true(评论中的想法不会是足以测试所有字段,前提是您创建一个临时字段来记住每个字段的先前值)

然后您将在您的preUpdate method 中测试此boolean

但我强烈不推荐这种解决方法。


或者,另一种非常不推荐的方式:

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

【讨论】:

你是对的,但应用程序太大了,我无法确定所有用途。或者强迫大家以正确的方式保存Tourist实体。所以我正在寻找一些通用的方法。你觉得 EmtpyInterceptor#onFlushDirty ***.com/questions/1927098/… 怎么样? 在业务逻辑中做的提议未能封装操作。然后这个类的每个客户都需要知道它还应该更新修改日期,这是非常糟糕的! 我真的不明白它是什么,因为我从未使用过它。但是Object[] newValuesObject[] oldValues 似乎很方便 @AlanHay 我完全同意(我支持你),这就是我保留其他建议的原因。我只是想指出它以防 V_B 可行 还要注意这里的transient关键字在JPA环境下是没有作用的。您正在寻找的是 @Transient 注释。【参考方案2】:

有一个简单的非 JPA 解决方案如下,但它确实有一些重复的代码,但是当你没有太多字段时是一个解决方案:

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

否则,我会按照另一个答案中的建议保存先前的加载状态,但方式更简洁。

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

【讨论】:

我有大约二十个字段((. 您可以查看使用 BeanUtils 在加载时将数据复制到瞬态实例并在保存时比较两个 bean:***.com/questions/6099040/… 您能看看我的问题的更新部分吗?我有一些想法 我不再担心二十个字段,而是开始输入它们。 @VB_【参考方案3】:

虽然已经很晚了,但是我遇到了一个问题,我需要在实体中的某些字段上应用更新安全性。我使用反射解决了它。看看这个线程。 spring security for certain entity variables

【讨论】:

以上是关于Hibernate @PreUpdate:检查已更改的内容的主要内容,如果未能解决你的问题,请参考以下文章

所有警报对话框消息和textField都已更改为单行。请检查图像

使用 SessionFactory 配置 Hibernate 4.3 以使用 @PrePersist

@PrePersist 运行时,@PreUpdate 是不是总是运行?

在学说-mongodb 中的 preUpdate 事件时创建/保留新文档

添加到集合时不触发 PreUpdate [重复]

在 Symfony 2.1 中为 preUpdate 调用添加额外的持久化调用