使用Hibernate和MySQL创建时间戳和上次更新时间戳
对于某个Hibernate实体,我们需要存储其创建时间和上次更新的时间。你会如何设计?使用Hibernate和MySQL创建时间戳和上次更新时间戳
你会在数据库中使用哪些数据类型(假设MySQL可能与JVM在不同的时区)?数据类型是否可以识别时区?
你会在Java中使用哪些数据类型(
Date
,Calendar
,long
,...)?你会为数据库,ORM框架(Hibernate)或应用程序员设置时间戳—负责谁?
您将使用哪些注释进行映射(例如
@Temporal
)?
我不仅在寻找一个可行的解决方案,而是寻找一个安全和设计良好的解决方案。
一个好方法是为所有的实体设置一个通用的基类。在这个基类中,如果你的id属性在你的所有实体(一个通用设计)*同命名,你的创建和最后一次更新日期属性,你可以拥有你的id属性。
对于创建日期,您只需保留一个java.util.Date属性。请确保始终使用新日期()进行初始化。
对于上次更新字段,您可以使用Timestamp属性,您需要使用@Version将其映射。通过这个Annotation,这个属性将会被Hibernate自动更新。请注意,Hibernate也会应用乐观锁定(这是一件好事)。
作为JAVA中的数据类型,我强烈建议使用java.util.Date。使用日历时,我遇到了非常讨厌的时区问题。看到这个Thread。
对于设置我会建议您使用AOP方法或者你可以简单地使用触发器表上的时间戳(其实这是我曾经发现使用触发器可以接受的唯一的事情)。
只是为了加强:java.util.Calender
不适用于时间戳。 java.util.Date
是一段时间,不区分时区等区域性事物。以这种方式多数数据库存储的东西(即使他们似乎不,这通常是在客户端软件中的时区设置,数据良好)
如果您使用的是JPA注解,你可以使用@PrePersist
和事件挂钩做到这一点:
@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
注释并把事件代码在外部类。
J2SE中没有任何问题,因为@PrePersist和@PerUpdate是JPA注释。 – Kdeveloper 2010-12-16 19:52:18
使用Hibernate Session的时候很好,但不会工作。 – 2013-09-15 14:11:32
@Kumar - 如果您使用纯Hibernate会话(而不是JPA),您可以尝试使用hibernate事件侦听器,尽管这不像JPA注释那么优雅和紧凑。 – Shailendra 2013-10-09 10:16:12
你可能会考虑将时间作为一个DateTime,和UTC。我通常使用DateTime而不是Timestamp,因为MySql在存储和检索数据时将日期转换为UTC并返回到本地时间。我宁愿将任何一种逻辑放在一个地方(业务层)。我敢肯定还有其他的情况,尽管使用Timestamp更可取。
谢谢大家谁帮助。做一些研究我自己(我是谁问的问题的家伙)后,这里是我发现意义最:
数据库列类型:自1970年以来表现为毫秒的时区无关的数
decimal(20)
因为2^64有20个数字,磁盘空间便宜;让我们直截了当。另外,我也不会使用DEFAULT CURRENT_TIMESTAMP
,也不会触发。我不想要DB中的魔法。Java字段类型:
long
。 Unix时间戳很好地支持各种库,long
没有Y2038问题,时间戳算法是快速和容易的(主要是运算符<
和运算符+
,假设计算中不涉及天/月/年)。并且,最重要的是,原始的long
s和java.lang.Long
s都是不可变的 —有效地通过值—而不是java.util.Date
s;在调试其他人的代码时,我会非常生气,找到类似foo.getLastUpdate().setTime(System.currentTimeMillis())
的东西。ORM框架应该负责自动填写数据。
我还没有测试过,但只看文档我认为
@Temporal
将完成这项工作;不确定我是否可以使用@Version
达到此目的。@PrePersist
和是手动控制的好替代品。将所有实体添加到图层超类型(通用基类),这是一个可爱的想法,前提是您确实需要为实体的全部加上时间戳。
虽然多头和多头可能是不变的,但在你描述的情况下,这并不会帮助你。他们仍然可以说foo.setLastUpdate(new Long(System。的currentTimeMillis()); – 2008-10-23 14:52:35
以资源在这篇文章中的信息一起进行左右从不同的来源,我想出了这个优雅的解决方案,创建以下抽象类
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 {
...
}
您也可以使用拦截器来设置值
创建一个名为时间戳的接口,你的实体imple换货
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'列不能为空
为了解决这个问题,添加更新= false来“创造”属性的@Column批注:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
如果您正在使用的会话API的PrePersist和更新前的回调,根据将无法正常工作到这个answer。
我在我的代码中使用Hibernate Session的persist()方法,所以我可以使这项工作的唯一方法是使用下面的代码并在此blog post(也发布在answer)中。
@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;
}
}
你可以只使用@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
);
这对我们有效。
使用时间戳列进行乐观锁定是个坏主意。始终使用整数版本列。原因在于,2个JVM可能处于不同的时间,并且可能不具有毫秒级的准确性。如果您改为使用数据库时间戳进行休眠,那将意味着来自数据库的额外选择。相反,只需使用版本号。 – sethu 2013-02-18 04:46:26