对于某个Hibernate实体,我们需要存储它的创建时间和最后一次更新时间。你会怎么设计呢?

您将在数据库中使用什么数据类型(假设MySQL,可能位于与JVM不同的时区)?数据类型是否支持时区? 你会在Java中使用什么数据类型(日期,日历,长,…)? 您会让谁负责设置时间戳——数据库、ORM框架(Hibernate)还是应用程序程序员? 你会为映射使用什么注释(例如@Temporal)?

我不仅在寻找一个可行的解决方案,而且在寻找一个安全、设计良好的解决方案。


当前回答

利用本文中的资源以及来自不同来源的左右信息,我提出了这个优雅的解决方案,创建以下抽象类

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public abstract class AbstractTimestampEntity {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created", nullable = false)
    private Date created;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated", nullable = false)
    private Date updated;

    @PrePersist
    protected void onCreate() {
    updated = created = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
    updated = new Date();
    }
}

并让你的所有实体扩展它,例如:

@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}

其他回答

一个好的方法是为所有实体使用一个公共基类。在这个基类中,你可以有你的id属性,如果它在你所有的实体中都是通用命名的(一个通用设计),你的创建和最后更新日期属性。

对于创建日期,只需保留一个java.util.Date属性。请确保始终使用new Date()初始化它。

对于最后一个更新字段,您可以使用Timestamp属性,您需要将其与@Version映射。使用这个Annotation, Hibernate将自动更新属性。注意Hibernate也会应用乐观锁定(这是一件好事)。

如果我们在方法中使用@Transactional, @CreationTimestamp和@UpdateTimestamp将值保存在DB中,但在使用save(…)后将返回null。

在这种情况下,使用saveAndFlush(…)达到了目的

我认为在Java代码中不这样做更整洁,你可以简单地在MySql表定义中设置列的默认值。

您还可以使用拦截器来设置这些值

创建一个名为TimeStamped的接口,由实体实现

public interface TimeStamped {
    public Date getCreatedDate();
    public void setCreatedDate(Date createdDate);
    public Date getLastUpdated();
    public void setLastUpdated(Date lastUpdatedDate);
}

定义拦截器

public class TimeStampInterceptor extends EmptyInterceptor {

    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, 
            Object[] previousState, String[] propertyNames, Type[] types) {
        if (entity instanceof TimeStamped) {
            int indexOf = ArrayUtils.indexOf(propertyNames, "lastUpdated");
            currentState[indexOf] = new Date();
            return true;
        }
        return false;
    }

    public boolean onSave(Object entity, Serializable id, Object[] state, 
            String[] propertyNames, Type[] types) {
            if (entity instanceof TimeStamped) {
                int indexOf = ArrayUtils.indexOf(propertyNames, "createdDate");
                state[indexOf] = new Date();
                return true;
            }
            return false;
    }
}

并将其注册到会话工厂

如果你正在使用会话API,根据这个答案,PrePersist和PreUpdate回调将不起作用。

我在我的代码中使用Hibernate会话的persist()方法,所以我能做到这一点的唯一方法是使用下面的代码和下面的这篇博客文章(也在答案中发布)。

@MappedSuperclass
public abstract class AbstractTimestampEntity {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created")
    private Date created=new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated")
    @Version
    private Date updated;

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }
}