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

Java 使用Hibernate和MySQL创建时间戳和上次更新时间戳,java,hibernate,timestamp,Java,Hibernate,Timestamp,对于某个Hibernate实体,我们需要存储其创建时间和上次更新时间。你会如何设计这个 您会在数据库中使用什么数据类型(假设MySQL,可能在JVM使用的不同时区)?数据类型是否具有时区意识 您将在Java中使用哪些数据类型(Date,Calendar,long,…) 您会让谁负责设置数据库、ORM框架(Hibernate)或应用程序程序员的时间戳 您将使用哪些注释进行映射(例如@Temporal) 我不仅在寻找一个有效的解决方案,而且在寻找一个安全且设计良好的解决方案。一个好方法是为所有实

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

  • 您会在数据库中使用什么数据类型(假设MySQL,可能在JVM使用的不同时区)?数据类型是否具有时区意识

  • 您将在Java中使用哪些数据类型(
    Date
    Calendar
    long
    ,…)

  • 您会让谁负责设置数据库、ORM框架(Hibernate)或应用程序程序员的时间戳

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


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

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

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


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

作为JAVA中的数据类型,我强烈建议使用JAVA.util.Date。我在使用日历时遇到了非常严重的时区问题。看这个


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

只是为了强调:
java.util.Calendar
不是用于时间戳的
java.util.Date
只是暂时的,与时区等区域性事物无关。大多数数据库都以这种方式存储内容(即使它们看起来不是这样;这通常是客户端软件中的时区设置;数据很好)

如果使用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();
  }
}

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

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

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

  • 数据库列类型:自1970年以来时区不可知的毫秒数,表示为十进制(20),因为2^64有20位数字,磁盘空间便宜;让我们直截了当地说。此外,我既不使用默认的当前时间戳,也不使用触发器。我不想在DB里有魔法


  • Java字段类型:
    long
    。Unix时间戳在各种LIB中都得到了很好的支持,
    long
    没有Y2038问题,时间戳算法又快又简单(主要是操作员
    利用本文中的资源以及从不同来源获取的左右信息,我提供了这个优雅的解决方案,创建了以下抽象类

    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 {
    ...
    }
    

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

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

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

    并使用Olivier的解决方案向会话工厂注册,在更新语句期间,您可能会遇到:

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

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

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

    在使用会话API的情况下,PrePersist和PreUpdate回调将无法按照此方式工作

    我在我的代码中使用了Hibernate会话的persist()方法,所以我唯一能做到这一点的方法就是使用下面的代码并遵循它(也发布在文章中)


    您只需使用
    @CreationTimestamp
    @UpdateTimestamp

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

    我们也遇到过类似的情况,我们使用的是Mysql 5.7

    CREATE TABLE my_table (
            ...
          updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        );
    

    这对我们很有用。

    现在还有@CreatedDate和@LastModifiedDate注释

    =>


    (Spring框架)

    下面的代码对我很有用

    package com.my.backend.models;
    
    import java.util.Date;
    
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.MappedSuperclass;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    
    import org.hibernate.annotations.ColumnDefault;
    import org.hibernate.annotations.CreationTimestamp;
    import org.hibernate.annotations.UpdateTimestamp;
    
    import lombok.Getter;
    import lombok.Setter;
    
    @MappedSuperclass
    @Getter @Setter
    public class BaseEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        protected Integer id;
    
        @CreationTimestamp
        @ColumnDefault("CURRENT_TIMESTAMP")
        protected Date createdAt;
    
        @UpdateTimestamp
        @ColumnDefault("CURRENT_TIMESTAMP")
        protected Date updatedAt;
    }
    

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


    在这种情况下,使用saveAndFlush(…)完成了这项任务

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

  • 应该使用哪些数据库列类型

  • 你的第一个问题是:

    您会在数据库中使用什么数据类型(假设MySQL,可能在JVM使用的另一个时区)?这些数据类型会支持时区吗

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

    因此,最好改用
    DATETIME
    ,它没有这个上限限制。但是,
    DATETIME
    不支持时区。因此,出于这个原因,最好在数据库端使用UTC并使用
    hibernate.jdb
    
    package com.my.backend.models;
    
    import java.util.Date;
    
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.MappedSuperclass;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    
    import org.hibernate.annotations.ColumnDefault;
    import org.hibernate.annotations.CreationTimestamp;
    import org.hibernate.annotations.UpdateTimestamp;
    
    import lombok.Getter;
    import lombok.Setter;
    
    @MappedSuperclass
    @Getter @Setter
    public class BaseEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        protected Integer id;
    
        @CreationTimestamp
        @ColumnDefault("CURRENT_TIMESTAMP")
        protected Date createdAt;
    
        @UpdateTimestamp
        @ColumnDefault("CURRENT_TIMESTAMP")
        protected Date updatedAt;
    }
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_on")
    private Date createdOn;
    
    @Column(name = "created_on")
    private LocalDateTime createdOn;
    
    ALTER TABLE post 
    ADD CONSTRAINT created_on_default 
    DEFAULT CURRENT_TIMESTAMP() FOR created_on;
    
    @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
    }
    
    @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
    }
    
    @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
    }
    
    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());
        }
    }
    
    @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
    }
    
    @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;
    
    }
    
    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());
        }
    }
    
    @Configuration
    @EnableJpaAuditing(auditorAwareRef = "auditorAware")
    public class JpaConfig {
        @Bean
        public AuditorAware<String> auditorAware() {
            return new AuditorAwareImpl();
        }
    }