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

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

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


当前回答

作为JAVA中的数据类型,我强烈建议使用JAVA .util. date。在使用Calendar时,我遇到了非常糟糕的时区问题。请看这个帖子。

对于设置时间戳,我建议使用AOP方法,或者您可以简单地在表上使用触发器(实际上,这是我发现使用触发器唯一可以接受的事情)。

其他回答

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

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

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

作为JAVA中的数据类型,我强烈建议使用JAVA .util. date。在使用Calendar时,我遇到了非常糟糕的时区问题。请看这个帖子。

对于设置时间戳,我建议使用AOP方法,或者您可以简单地在表上使用触发器(实际上,这是我发现使用触发器唯一可以接受的事情)。

对于那些想要使用JPA和Spring Data创建或修改用户详细信息以及时间的人,可以遵循这一点。你可以在base域中添加@CreatedDate,@LastModifiedDate,@CreatedBy和@LastModifiedBy。用@MappedSuperclass和@EntityListeners(AuditingEntityListener.class)标记基域,如下所示:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseDomain implements Serializable {

    @CreatedDate
    private Date createdOn;

    @LastModifiedDate
    private Date modifiedOn;

    @CreatedBy
    private String createdBy;

    @LastModifiedBy
    private String modifiedBy;

}

因为我们用AuditingEntityListener标记了基本域,所以我们可以告诉JPA当前登录的用户。因此,我们需要提供AuditorAware的实现并重写getCurrentAuditor()方法。在getCurrentAuditor()内部,我们需要返回当前授权的用户Id。

public class AuditorAwareImpl implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication == null ? Optional.empty() : Optional.ofNullable(authentication.getName());
    }
}

在上面的代码中,如果Optional不能工作,你可以使用Java 7或更老的版本。在这种情况下,尝试用String改变Optional。

现在使用下面的代码来启用上述审计器实现

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfig {
    @Bean
    public AuditorAware<String> auditorAware() {
        return new AuditorAwareImpl();
    }
}

现在,您可以将BaseDomain类扩展到您需要创建和修改日期和时间以及用户Id的所有实体类

您可以考虑将时间存储为DateTime,并且使用UTC。我通常使用DateTime而不是Timestamp,因为MySql在存储和检索数据时将日期转换为UTC和本地时间。我宁愿把所有这些逻辑都放在一个地方(业务层)。我相信在其他情况下使用时间戳更可取。

谢谢所有帮助过我的人。在我自己做了一些研究之后(我是问这个问题的人),以下是我发现最有意义的:

Database column type: the timezone-agnostic number of milliseconds since 1970 represented as decimal(20) because 2^64 has 20 digits and disk space is cheap; let's be straightforward. Also, I will use neither DEFAULT CURRENT_TIMESTAMP, nor triggers. I want no magic in the DB. Java field type: long. The Unix timestamp is well supported across various libs, long has no Y2038 problems, timestamp arithmetic is fast and easy (mainly operator < and operator +, assuming no days/months/years are involved in the calculations). And, most importantly, both primitive longs and java.lang.Longs are immutable—effectively passed by value—unlike java.util.Dates; I'd be really pissed off to find something like foo.getLastUpdate().setTime(System.currentTimeMillis()) when debugging somebody else's code. The ORM framework should be responsible for filling in the data automatically. I haven't tested this yet, but only looking at the docs I assume that @Temporal will do the job; not sure about whether I might use @Version for this purpose. @PrePersist and @PreUpdate are good alternatives to control that manually. Adding that to the layer supertype (common base class) for all entities, is a cute idea provided that you really want timestamping for all of your entities.