对于某个Hibernate实体,我们需要存储它的创建时间和最后一次更新时间。你会怎么设计呢?
您将在数据库中使用什么数据类型(假设MySQL,可能位于与JVM不同的时区)?数据类型是否支持时区? 你会在Java中使用什么数据类型(日期,日历,长,…)? 您会让谁负责设置时间戳——数据库、ORM框架(Hibernate)还是应用程序程序员? 你会为映射使用什么注释(例如@Temporal)?
我不仅在寻找一个可行的解决方案,而且在寻找一个安全、设计良好的解决方案。
对于某个Hibernate实体,我们需要存储它的创建时间和最后一次更新时间。你会怎么设计呢?
您将在数据库中使用什么数据类型(假设MySQL,可能位于与JVM不同的时区)?数据类型是否支持时区? 你会在Java中使用什么数据类型(日期,日历,长,…)? 您会让谁负责设置时间戳——数据库、ORM框架(Hibernate)还是应用程序程序员? 你会为映射使用什么注释(例如@Temporal)?
我不仅在寻找一个可行的解决方案,而且在寻找一个安全、设计良好的解决方案。
当前回答
您可以考虑将时间存储为DateTime,并且使用UTC。我通常使用DateTime而不是Timestamp,因为MySql在存储和检索数据时将日期转换为UTC和本地时间。我宁愿把所有这些逻辑都放在一个地方(业务层)。我相信在其他情况下使用时间戳更可取。
其他回答
利用本文中的资源以及来自不同来源的左右信息,我提出了这个优雅的解决方案,创建以下抽象类
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 {
...
}
我认为在Java代码中不这样做更整洁,你可以简单地在MySql表定义中设置列的默认值。
应该使用哪些数据库列类型
你的第一个问题是:
您将在数据库中使用什么数据类型(假设MySQL,可能位于与JVM不同的时区)?数据类型是否支持时区?
在MySQL中,TIMESTAMP列类型从JDBC驱动程序本地时区转移到数据库时区,但它只能存储到2038-01-19 03:14:07.999999的时间戳,因此它不是未来的最佳选择。
因此,最好使用DATETIME,它没有这个上限限制。然而,DATETIME不支持时区。因此,出于这个原因,最好在数据库端使用UTC,并使用hibernate.jdbc。time_zone休眠属性。
你应该使用什么实体属性类型
你的第二个问题是:
你会在Java中使用什么数据类型(日期,日历,长,…)?
在Java端,您可以使用Java 8 LocalDateTime。您也可以使用传统的Date,但是Java 8 Date/Time类型更好,因为它们是不可变的,并且在记录它们时不会将时区转移到本地时区。
现在,我们也可以回答这个问题:
你会为映射使用什么注释(例如@Temporal)?
如果您正在使用LocalDateTime或java.sql.Timestamp来映射一个时间戳实体属性,那么您不需要使用@Temporal,因为HIbernate已经知道这个属性将被保存为JDBC时间戳。
只有当你使用java.util。日期,你需要指定@Temporal注释,像这样:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;
但是,如果你像这样映射它会更好:
@Column(name = "created_on")
private LocalDateTime createdOn;
如何生成审计列值
你的第三个问题是:
您会让谁负责设置时间戳——数据库、ORM框架(Hibernate)还是应用程序程序员? 你会为映射使用什么注释(例如@Temporal)?
有很多方法可以实现这个目标。你可以让数据库来做。
对于create_on列,你可以使用一个DEFAULT DDL约束,比如:
ALTER TABLE post
ADD CONSTRAINT created_on_default
DEFAULT CURRENT_TIMESTAMP() FOR created_on;
对于updated_on列,您可以使用DB触发器在每次修改给定行的时候使用CURRENT_TIMESTAMP()设置列值。
或者,使用JPA或Hibernate来设置这些。
让我们假设你有以下数据库表:
并且,每个表都有这样的列:
created_by created_on updated_by updated_on
使用Hibernate的@CreationTimestamp和@UpdateTimestamp注释
Hibernate提供了@CreationTimestamp和@UpdateTimestamp注解,可以用来映射created_on和updated_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
}
并且,所有实体都将扩展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
}
然而,即使createdOn和updateOn属性是由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
}
并且,创建一个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());
}
}
要注册AuditListener,您可以使用@EntityListeners JPA注释:
@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
}
现在还有@CreatedDate和@LastModifiedDate注解。
= > https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Spring框架)
您还可以使用拦截器来设置这些值
创建一个名为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;
}
}
并将其注册到会话工厂