使用Hibernate和MySQL创建时间戳和上次更新时间戳

ngn*_*ngn 228 java timestamp hibernate

对于某个Hibernate实体,我们需要存储其创建时间和上次更新时间.你会怎么设计这个?

  • 您将在数据库中使用哪些数据类型(假设MySQL,可能在与JVM不同的时区)?数据类型是时区感知的吗?

  • 你会在Java中使用什么数据类型(Date,Calendar,long,...)?

  • 您将负责设置时间戳 - 数据库,ORM框架(Hibernate)或应用程序员?

  • 您将使用哪些注释进行映射(例如@Temporal)?

我不只是在寻找一个可行的解决方案,而是一个安全且设计良好的解决方案.

小智 257

如果您使用的是JPA注释,则可以使用@PrePersist@PreUpdate事件挂钩执行此操作:

@Entity
@Table(name = "entities")    
public class Entity {
  ...

  private Date created;
  private Date updated;

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

  @PreUpdate
  protected void onUpdate() {
    updated = new Date();
  }
}
Run Code Online (Sandbox Code Playgroud)

或者您可以@EntityListener在类上使用注释并将事件代码放在外部类中.

  • 在使用JPA的当前Hibernate中,可以使用"@CreationTimestamp"和"@UpdateTimestamp" (37认同)
  • 良好的灵魂,但在使用Hibernate Session时无法工作. (13认同)
  • 在J2SE中没有任何问题,因为@PrePersist和@PerUpdate是JPA注释. (7认同)
  • @Kumar - 如果您使用普通的Hibernate会话(而不是JPA),您可以尝试使用hibernate事件监听器,尽管与JPA注释相比,它不是非常优雅和紧凑. (2认同)

idm*_*iev 130

你可以使用@CreationTimestamp@UpdateTimestamp:

@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;

@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;
Run Code Online (Sandbox Code Playgroud)

  • @Tomasulo ,使用 @Column(name = "creation_date", updatetable = false) (3认同)
  • 谢谢兄弟这么小的东西需要更新时间戳.我不知道.你救了我的一天. (2认同)
  • 当我更新对象并持久化时,bd丢失了create_date ...为什么? (2认同)
  • 我的情况是从 `@Column(name = "create_date" , nullable=false)` 中删除 `nullable=false` 工作 (2认同)

Oli*_*alo 108

利用这篇文章中的资源以及从不同来源左右传递的信息,我带来了这个优雅的解决方案,创建了以下抽象类

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}
Run Code Online (Sandbox Code Playgroud)

  • 这是好的,直到您想要向实体添加_different_独占行为(并且您不能扩展多个基类).afaik没有基类获得相同效果的唯一方法是尽管aspectj itd或事件监听器看到@kieren dixon回答 (5认同)
  • 我会使用MySQL触发器执行此操作,以便即使完整实体未保存或被任何外部应用程序或手动查询修改,它仍将更新这些字段. (3认同)
  • 你可以给我任何工作的例子,因为我遇到异常`not-null属性引用一个null或瞬态值:package.path.ClassName.created` (3认同)

Vla*_*cea 67

  1. 您应该使用哪些数据库列类型

你的第一个问题是:

您将在数据库中使用哪些数据类型(假设 MySQL,可能与 JVM 处于不同的时区)?数据类型会识别时区吗?

在 MySQL 中,TIMESTAMP列类型会从 JDBC 驱动程序本地时区转移到数据库时区,但它只能存储 时间戳2038-01-19 03:14:07.999999,因此它不是未来的最佳选择。

所以,最好DATETIME改用它,它没有这个上限限制。但是,DATETIME不知道时区。因此,出于这个原因,最好在数据库端使用 UTC 并使用hibernate.jdbc.time_zoneHibernate 属性。

  1. 您应该使用哪种实体属性类型

你的第二个问题是:

您会在 Java 中使用哪些数据类型(日期、日历、长整型……)?

在 Java 方面,您可以使用 Java 8 LocalDateTime。您也可以使用 legacy Date,但 Java 8 Date/Time 类型更好,因为它们是不可变的,并且在记录它们时不要将时区转换为本地时区。

现在,我们也可以回答这个问题:

您将使用哪些注释进行映射(例如@Temporal)?

如果您使用LocalDateTimeorjava.sql.Timestamp来映射时间戳实体属性,那么您不需要使用,@Temporal因为 HIbernate 已经知道该属性要保存为 JDBC 时间戳。

只有在使用时java.util.Date,才需要指定@Temporal注解,像这样:

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;
Run Code Online (Sandbox Code Playgroud)

但是,如果你这样映射它会好得多:

@Column(name = "created_on")
private LocalDateTime createdOn;
Run Code Online (Sandbox Code Playgroud)

如何生成审计列值

你的第三个问题是:

你会让谁来负责设置时间戳——数据库、ORM 框架(Hibernate)还是应用程序程序员?

您将使用哪些注释进行映射(例如@Temporal)?

您可以通过多种方式实现这一目标。您可以允许数据库这样做..

对于create_on列,您可以使用DEFAULTDDL 约束,例如:

ALTER TABLE post 
ADD CONSTRAINT created_on_default 
DEFAULT CURRENT_TIMESTAMP() FOR created_on;
Run Code Online (Sandbox Code Playgroud)

对于updated_on列,您可以使用数据库触发器在CURRENT_TIMESTAMP()每次修改给定行时设置列值。

或者,使用 JPA 或 Hibernate 来设置这些。

假设您有以下数据库表:

带有审计列的数据库表

而且,每个表都有如下列:

  • created_by
  • created_on
  • updated_by
  • updated_on

使用 Hibernate@CreationTimestamp@UpdateTimestamp注解

Hibernate 提供了可用于映射和列的@CreationTimestamp@UpdateTimestamp注释。created_onupdated_on

您可以使用@MappedSuperclass来定义将由所有实体扩展的基类:

@MappedSuperclass
public class BaseEntity {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @Column(name = "created_on")
    @CreationTimestamp
    private LocalDateTime createdOn;
 
    @Column(name = "created_by")
    private String createdBy;
 
    @Column(name = "updated_on")
    @UpdateTimestamp
    private LocalDateTime updatedOn;
 
    @Column(name = "updated_by")
    private String updatedBy;
 
    //Getters and setters omitted for brevity
}
Run Code Online (Sandbox Code Playgroud)

并且,所有实体都将扩展BaseEntity,如下所示:

@Entity(name = "Post")
@Table(name = "post")
public class Post extend BaseEntity {
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;
 
    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();
 
    //Getters and setters omitted for brevity
}
Run Code Online (Sandbox Code Playgroud)

但是,即使createdOnupdateOn属性是由特定于 Hibernate 的@CreationTimestamp@UpdateTimestamp注释设置的,createdBy和 也updatedBy需要注册应用程序回调,如下面的 JPA 解决方案所示。

使用 JPA @EntityListeners

您可以将审计属性封装在一个 Embeddable 中:

@Embeddable
public class Audit {
 
    @Column(name = "created_on")
    private LocalDateTime createdOn;
 
    @Column(name = "created_by")
    private String createdBy;
 
    @Column(name = "updated_on")
    private LocalDateTime updatedOn;
 
    @Column(name = "updated_by")
    private String updatedBy;
 
    //Getters and setters omitted for brevity
}
Run Code Online (Sandbox Code Playgroud)

并且,创建一个AuditListener来设置审计属性:

public class AuditListener {
 
    @PrePersist
    public void setCreatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();
 
        if(audit == null) {
            audit = new Audit();
            auditable.setAudit(audit);
        }
 
        audit.setCreatedOn(LocalDateTime.now());
        audit.setCreatedBy(LoggedUser.get());
    }
 
    @PreUpdate
    public void setUpdatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();
 
        audit.setUpdatedOn(LocalDateTime.now());
        audit.setUpdatedBy(LoggedUser.get());
    }
}
Run Code Online (Sandbox Code Playgroud)

要注册AuditListener,您可以使用@EntityListenersJPA 注释:

@Entity(name = "Post")
@Table(name = "post")
@EntityListeners(AuditListener.class)
public class Post implements Auditable {
 
    @Id
    private Long id;
 
    @Embedded
    private Audit audit;
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;
 
    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();
 
    //Getters and setters omitted for brevity
}
Run Code Online (Sandbox Code Playgroud)


Kie*_*xon 17

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

创建一个名为TimeStamped的接口,您的实体将实现该接口

public interface TimeStamped {
    public Date getCreatedDate();
    public void setCreatedDate(Date createdDate);
    public Date getLastUpdated();
    public void setLastUpdated(Date lastUpdatedDate);
}
Run Code Online (Sandbox Code Playgroud)

定义拦截器

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并在会话工厂注册


end*_*iju 16

使用Olivier的解决方案,在更新语句期间,您可能遇到:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列'created'不能为null

要解决此问题,请将updatable = false添加到"created"属性的@Column注释中:

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
Run Code Online (Sandbox Code Playgroud)


ngn*_*ngn 12

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

  • 数据库列类型:自1970年以来与时区无关的毫秒数表示为decimal(20)因为2 ^ 64有20位数且磁盘空间便宜; 让我们直截了当.此外,我将既不使用也不使用DEFAULT CURRENT_TIMESTAMP触发器.我想在DB中没有魔力.

  • Java字段类型:long.Unix时间戳在各种库中得到很好的支持,long没有Y2038问题,时间戳算法快速而简单(主要是运算符<和运算符+,假设计算中没有涉及天/月/年).并且,最重要的是,原始longs和java.lang.Longs都是不可变的 -有效地通过值传递 - 与java.util.Dates 不同; foo.getLastUpdate().setTime(System.currentTimeMillis())在调试其他人的代码时,我真的很生气.

  • ORM框架应负责自动填写数据.

  • 我还没有测试过这个,但只看了我认为@Temporal可以完成工作的文档; 不确定我是否可以@Version用于此目的. @PrePersist并且@PreUpdate是手动控制的好方法.将其添加到所有实体的图层超类型(公共基类),这是一个可爱的想法,前提是您确实需要为所有实体设置时间戳.

  • 没关系.Hibernate无论如何都需要setter(或者它会尝试直接通过反射访问该字段).我在谈论追逐谁从我们的应用程序代码修改时间戳的困难.使用getter可以做到这一点很棘手. (2认同)

Moh*_*vad 10

对于那些想要创建或修改用户详细信息以及使用 JPA 和 Spring Data 的时间的人可以遵循此操作。您可以在基本域中添加 @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;

}
Run Code Online (Sandbox Code Playgroud)

由于我们用 标记了基本域,因此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());
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,如果Optional不起作用,您可以使用 Java 7 或更早版本。在这种情况下,请尝试Optional使用进行更改String

现在要启用上述 Auditior 实现,请使用以下代码

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

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


vic*_*cch 6

如果您使用的是Session API,则根据此答案,PrePersist和PreUpdate回调将不起作用.

我在我的代码中使用Hibernate Session的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;
    }
}
Run Code Online (Sandbox Code Playgroud)


ber*_*rdn 1

一个好的方法是为所有实体提供一个公共基类。在此基类中,如果 id 属性在所有实体(通用设计)、创建和上次更新日期属性中通用,则可以拥有 id 属性。

对于创建日期,您只需保留java.util.Date属性即可。确保始终使用new Date()对其进行初始化。

对于最后更新字段,可以使用 Timestamp 属性,需要使用 @Version 进行映射。使用此注释,该属性将由 Hibernate 自动更新。请注意,Hibernate 也会应用乐观锁定(这是一件好事)。

  • 使用时间戳列进行乐观锁定是一个坏主意。始终使用整数版本列。原因是,2 个 JVM 可能处于不同的时间,并且可能不具有毫秒精度。如果您让 hibernate 使用数据库时间戳,那将意味着从数据库中进行额外的选择。相反,只需使用版本号。 (2认同)