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