休眠拦截器 - 为啥在 onSave 之后调用 onFlushDirty?

Posted

技术标签:

【中文标题】休眠拦截器 - 为啥在 onSave 之后调用 onFlushDirty?【英文标题】:Hibernate Interceptors - Why is onFlushDirty called after onSave?休眠拦截器 - 为什么在 onSave 之后调用 onFlushDirty? 【发布时间】:2014-09-29 02:55:35 【问题描述】:

计划

我正在使用 Hibernate 为一个小项目实现 createDate 和 lastUpdate Timestamps。我使用 EmptyInterceptor 并根据我找到的建议解决方案 here 重载提供的方法。 除非有一个小细节,否则该解决方案可以正常工作。我想添加一列指示对象是否已经更新。我知道我可以通过简单地比较两个创建和更新的时间戳是否存在差异来实现这一点,但我需要让这个字段指示有更新。

我使用在存储新对象时调用的 onSave 方法将 wasUpdated 值设置为“N”,表示没有更新。在 onFlushDirty() 方法中,我将此值设置为“Y”。

问题

我会解释,当我创建和持久化一个新对象时,createDate 和 lastUpdate 字段具有相同的日期,但 wasUpdated 字段设置为“N”,因为没有更新。我只在我的代码中使用 session.save(),没有 session.update(),也没有 session.saveOrUpdate()。 Hibernate 的日志表明实际上有一个更新,它将 wasUpdated 值设置为'Y'。

此更新的来源可能是什么?它在哪里触发?

对象初始化和持久化

我在 hibernate.cfg.xml 中禁用了自动提交。

<property name="hibernate.connection.autocommit">false</property>

这就是我创建对象的方式:

ExampleObject ex = new ExampleObject();
ex.setValue("TestStringValue");
this.session = HibernateUtil.getSessionFactory().openSession();
this.session.beginTransaction();
this.session.save(ex);
this.session.getTransaction().commit();
this.session.close();

拦截器

@Override
    public boolean onSave(Object entity, Serializable id, Object[] state,
                  String[] propertyNames, Type[] types) 


    if (entity instanceof TimeStamped) 

        Date insertDate = new Date();
        int indexOfCreateDateColumn = ArrayUtils.indexOf(propertyNames, "createdDate");
        int indexOfUpdatedDateColumn = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        state[indexOfCreateDateColumn] =insertDate;
        state[indexOfUpdatedDateColumn] =insertDate;
        state[indexOfWasUpdated] ='N';

        return true;
    
    return false;
    

第二种方法是设置 lastUpdatedDate 并将 wasUpdated 字段设置为 'Y'。

@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                    Object[] previousState, String[] propertyNames, Type[] types) 

    if (entity instanceof TimeStamped) 

        int indexOfLastUpdate = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        currentState[indexOfLastUpdate] = new Date();
        currentState[indexOfWasUpdated] ='Y';


        return true;
    
    return false;

HibernateUtil

我将这个配置用于会话。

public class HibernateUtil 
    private static SessionFactory sessionFactory;
    private static ServiceRegistry serviceRegistry;

    static 
    try 

        Configuration configuration = new Configuration().setInterceptor(new TimeStampInterceptor());
        configuration.configure();

        serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);


     catch (HibernateException he) 
        System.err.println("Error creating Session: " + he);
        throw new ExceptionInInitializerError(he);
    
    

    public static SessionFactory getSessionFactory() 
    return sessionFactory;
    

版本

我使用 Maven 和 Java 1.7.0_40。

    <!--. Hibernate -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.4.Final</version>
    </dependency>

【问题讨论】:

【参考方案1】:

onFlushDirty()方法的JavaDoc有如下语句:

在刷新期间检测到对象脏时调用。

因此对象是由于update() 调用而变脏还是由于save() 调用而变脏没有区别。因此,当会话刷新时,将在每个持久对象上调用 onFlushDirty() 方法。会话刷新可以由session.flush() 显式启动,或者在某些情况下当 Hibernate 需要它时隐式启动(在您的情况下 - 在事务提交之前)。

在您的情况下,wasUpdated 属性将始终与“Y”值一起保存:首先将调用 onSave() 方法,当会话刷新时,将在同一实体调用 onFlushDirty() 方法。

要解决onFlushDirty() 方法中的问题,您应该检查实体是否已更新。如果我没记错的话,当实体插入表中(保存新的)时,先前的状态为空。建议像这样实现onFlushDirty()

@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                    Object[] previousState, String[] propertyNames, Type[] types) 

    if (entity instanceof TimeStamped && previousState!=null) 

        int indexOfLastUpdate = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        currentState[indexOfLastUpdate] = new Date();
        currentState[indexOfWasUpdated] ='Y';


        return true;
    
    return false;

【讨论】:

不幸的是,您的回答没有解决问题,但它给了我正确的提示。在以前的状态下检查 null 不起作用,因为我需要在与创建日期相同的数据上设置 lastUpdate 值。不过非常感谢你,它帮助我更好地理解了这个问题。【参考方案2】:

解决方案:

感谢@vp8106 提出的提示,我可以解决这个问题。当我在记录初始化期间将lastUpdate值设置为与creationDate值相同的日期时,我只是比较了这两个日期。如果它们相同,那么这是我需要将更新指示器wasUpdated设置为'Y'的第一个更新。

代码sn-p:

   @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
                            Object[] previousState, String[] propertyNames, Type[] types) 




    /**
     * Update the lastUpdateDate value and set the wasUpdated flag to 'Y'
     */
    if (entity instanceof TimeStamped) 


        int indexOfLastUpdate = ArrayUtils.indexOf(propertyNames, "lastUpdatedDate");
        int indexOfCreatedDate = ArrayUtils.indexOf(propertyNames, "createdDate");
        int indexOfWasUpdated = ArrayUtils.indexOf(propertyNames, "wasUpdated");

        Date createdDate = (Date) previousState[indexOfCreatedDate];
        Date lastUpdateDate = (Date) currentState[indexOfLastUpdate];


        /**
         * If createdDate equals lastUpdateDate, this is the first update.
         * Set the updated column to Y
         */
        if (createdDate.equals(lastUpdateDate)) 
            logger.warning("This is the first update of the record.");
            currentState[indexOfWasUpdated] = 'Y';
        
        // set the new date of the update event
        currentState[indexOfLastUpdate] = new Date();


        return true;
    
    return false;

【讨论】:

【参考方案3】:

我实现了一个类似的东西,它没有任何问题。有以下区别:

我不使用 wasUpdated 来比较 createdDatelastUpdatedDate 就足够了。 我使用lastUpdatedDate 进行休眠版本控制(需要毫秒精度才能工作),所以我不必自己设置。

 

public boolean onSave(Object entity, Serializable id, Object[] state,
              String[] propertyNames, Type[] types) 

    if (entity instanceof TimeStamped) 
        Date insertDate = new Date();
        int indexOfCreateDateColumn = ArrayUtils.indexOf(propertyNames, "createdDate");
        state[indexOfCreateDateColumn] = insertDate;
        return true;
    
    return false;

如果我是 OP,我会问自己是否真的需要 wasUpdated 作为字段。这显然是多余的,因为它可以像 OP 在他的回答中所做的那样随时计算。 TimeStamped 上的只读属性应该可以。

【讨论】:

以上是关于休眠拦截器 - 为啥在 onSave 之后调用 onFlushDirty?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在更新事务期间休眠调用删除?

Javascript window.onsave 事件

为啥休眠在获取较少行数时较慢?

为啥flutter dio拦截器不调用该方法?

Spring AOP为啥不能拦截从对象内部调用的方法

为啥 Chrome 的弹出窗口拦截器会在 target="_blank" 的锚标记上调用?