Answers:
如果使用的是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
在类上使用注释,并将事件代码放置在外部类中。
您可以使用@CreationTimestamp
和@UpdateTimestamp
:
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;
@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;
TemporalType.DATE
第一种情况和TemporalType.TIMESTAMP
第二种情况都有。
@CreationTimestamp
,@UpdateTimestamp
也可能需要@Column(..., columnDefinition = "timestamp default current_timestamp")
或使用@PrePersist
和@PreUpdate
(后者也很好地确保了客户不能设置其他值)。
nullable=false
从@Column(name = "create_date" , nullable=false)
工作
利用本文中的资源以及从不同来源获得的左右信息,我带来了这个优雅的解决方案,创建了以下抽象类
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 {
...
}
not-null property references a null or transient value: package.path.ClassName.created
@Column(name = "updated", nullable = false, insertable = false)
以使其起作用。有趣的是,这个答案获得了很多好评..
您的第一个问题是:
您将在数据库中使用哪些数据类型(假设使用MySQL,可能与JVM时区不同)?数据类型是否支持时区?
在MySQL中,TIMESTAMP
列类型确实从JDBC驱动程序本地时区转换为数据库时区,但是它只能存储时间戳,最大时为'2038-01-19 03:14:07.999999
,因此它不是将来的最佳选择。
因此,最好使用DATETIME
没有上限的限制。但是,DATETIME
不是时区感知的。因此,由于这个原因,最好在数据库端使用UTC并使用hibernate.jdbc.time_zone
Hibernate属性。
有关
hibernate.jdbc.time_zone
设置的更多详细信息,请参阅本文。
您的第二个问题是:
您将在Java中使用哪些数据类型(日期,日历,长型,...)?
在Java方面,您可以使用Java 8 LocalDateTime
。您也可以使用legacy Date
,但是Java 8 Date / Time类型更好,因为它们是不可变的,并且在记录它们时不要将时区转换为本地时区。
有关Hibernate支持的Java 8日期/时间类型的更多详细信息,请参阅本文。
现在,我们也可以回答这个问题:
您将使用哪些注释进行映射(例如
@Temporal
)?
如果使用LocalDateTime
或java.sql.Timestamp
映射时间戳实体属性,则不需要使用,@Temporal
因为HIbernate已经知道该属性将另存为JDBC时间戳。
仅当使用时java.util.Date
,才需要指定@Temporal
注释,如下所示:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;
但是,如果这样映射它会更好:
@Column(name = "created_on")
private LocalDateTime createdOn;
您的第三个问题是:
您将由谁负责设置时间戳(数据库,ORM框架(休眠)或应用程序程序员)?
您将使用哪些注释进行映射(例如@Temporal)?
您可以通过多种方式实现此目标。您可以允许数据库执行此操作。
对于该create_on
列,您可以使用DEFAULT
DDL约束,例如:
ALTER TABLE post
ADD CONSTRAINT created_on_default
DEFAULT CURRENT_TIMESTAMP() FOR created_on;
对于updated_on
列,您可以在CURRENT_TIMESTAMP()
每次修改给定行时使用DB触发器来设置列值。
或者,使用JPA或Hibernate进行设置。
假设您具有以下数据库表:
而且,每个表都有类似以下的列:
created_by
created_on
updated_by
updated_on
@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
}
有关使用的更多详细信息
@MappedSuperclass
,请查看本文。
但是,即使createdOn
and updateOn
属性是由特定于Hibernate的@CreationTimestamp
and @UpdateTimestamp
注释设置的,createdBy
and也updatedBy
需要注册一个应用程序回调,如以下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
}
有关使用JPA实施审核属性的更多详细信息
@EntityListener
,请参阅本文。
datetime
过timestamp
。您希望数据库知道时间戳的时区。这样可以防止时区转换错误。
timestsmp
类型不存储时区信息。它只是进行从应用程序TZ到DB TZ的对话。实际上,您要单独存储客户端TZ,并在呈现UI之前在应用程序中进行对话。
timestamp
始终采用UTC。MySQL将TIMESTAMP
值从当前时区转换为UTC以进行存储,然后从UTC转换为当前时区以进行检索。 MySQL文档:DATE,DATETIME和TIMESTAMP类型
您也可以使用拦截器来设置值
创建一个称为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;
}
}
并在会话工厂中注册
使用奥利维尔的解决方案,在更新语句期间,您可能会遇到:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列'created'不能为null
要解决此问题,请将updatable = false添加到“创建的”属性的@Column注释中:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
@Version
。设置实体后,将进行两次调用,一次调用保存,另一次更新。因此,我面临着同样的问题。一旦添加,@Column(updatable = false)
就解决了我的问题。
谢谢大家的帮助。自己做了一些研究之后(我是问这个问题的人),这是我发现最有意义的:
数据库列类型:自1970年以来与时区无关的毫秒数,表示为decimal(20)
2 ^ 64有20位数字,并且磁盘空间便宜;让我们简单明了。另外,我将既不使用DEFAULT CURRENT_TIMESTAMP
,也不使用触发器。我不希望数据库中有任何魔术。
Java字段类型:long
。Unix时间戳在各个库中得到了很好的支持,long
没有Y2038问题,时间戳算法快速简便(主要是operator <
和operator +
,假定计算中不涉及日/月/年)。而且,最重要的是,long
与java.lang.Long
s 不同,原始s和s都是不可变的-有效地通过值传递java.util.Date
。foo.getLastUpdate().setTime(System.currentTimeMillis())
当调试其他人的代码时,我会很生气地发现类似的东西。
ORM框架应负责自动填充数据。
我还没有测试过,但是仅查看我认为@Temporal
可以完成此工作的文档。不知道我是否可以@Version
用于此目的。 @PrePersist
并且@PreUpdate
是手动控制的好选择。如果您确实想为所有实体加盖时间戳,那么将其添加到所有实体的层超类型(公共基类)中是一个不错的主意。
如果您正在使用会话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;
}
}
updated.clone()
其他对象一样返回克隆的对象,否则其他组件可以操纵内部状态(日期)
现在还有@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;
}
protected Integer id;
为protected
在一般的父类,因为我不能在我的测试案例使用它.getId()
一个好的方法是为所有实体都使用一个通用的基类。在此基类中,如果您的id属性在所有实体中都被通用命名(一个共同的设计),您的创建和最后更新日期属性,则可以具有它的id属性。
对于创建日期,您只需保留一个java.util.Date属性。确保始终使用new Date()对其进行初始化。
对于最后一个更新字段,您可以使用Timestamp属性,您需要使用@Version对其进行映射。使用此注释,该属性将由Hibernate自动更新。注意,Hibernate也将应用乐观锁定(这是一件好事)。
只是为了加强:java.util.Calender
不适用于Timestamps。 java.util.Date
在一段时间内,与时区等区域性事物无关。大多数数据库都以这种方式存储事物(即使它们看起来不那么存储;这通常是客户端软件中的时区设置;数据很好)