Java 使用 Hibernate 和 MySQL 创建时间戳和上次更新时间戳
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/221611/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Creation timestamp and last update timestamp with Hibernate and MySQL
提问by ngn
For a certain Hibernate entity we have a requirement to store its creation time and the last time it was updated. How would you design this?
对于某个 Hibernate 实体,我们需要存储其创建时间和上次更新时间。你会如何设计这个?
What data types would you use in the database (assuming MySQL, possibly in a different timezone that the JVM)? Will the data types be timezone-aware?
What data types would you use in Java (
Date
,Calendar
,long
, ...)?Whom would you make responsible for setting the timestamps—the database, the ORM framework (Hibernate), or the application programmer?
What annotations would you use for the mapping (e.g.
@Temporal
)?
您将在数据库中使用哪些数据类型(假设 MySQL,可能与 JVM 处于不同的时区)?数据类型会识别时区吗?
你会在Java中使用什么数据类型(
Date
,Calendar
,long
,...)?你会让谁来负责设置时间戳——数据库、ORM 框架(Hibernate)还是应用程序程序员?
您将使用哪些注释进行映射(例如
@Temporal
)?
I'm not only looking for a working solution, but for a safe and well-designed solution.
我不仅要寻找可行的解决方案,还要寻找安全且精心设计的解决方案。
回答by bernardn
A good approach is to have a common base class for all your entities. In this base class, you can have your id property if it is commonly named in all your entities (a common design), your creation and last update date properties.
一个好的方法是为所有实体拥有一个公共基类。在这个基类中,如果您的 id 属性在您的所有实体(一种通用设计)、您的创建和上次更新日期属性中普遍命名,则您可以拥有它。
For the creation date, you simply keep a java.util.Dateproperty. Be sure, to always initialize it with new Date().
对于创建日期,您只需保留一个java.util.Date属性。请确保始终使用new Date()对其进行初始化。
For the last update field, you can use a Timestamp property, you need to map it with @Version. With this Annotation the property will get updated automatically by Hibernate. Beware that Hibernate will also apply optimistic locking (it's a good thing).
对于最后一个更新字段,您可以使用 Timestamp 属性,您需要使用 @Version 映射它。有了这个注解,Hibernate 会自动更新属性。请注意 Hibernate 也会应用乐观锁定(这是一件好事)。
回答by huo73
As data type in JAVA I strongly recommend to use java.util.Date. I ran into pretty nasty timezone problems when using Calendar. See this Thread.
作为 JAVA 中的数据类型,我强烈建议使用 java.util.Date。使用日历时,我遇到了非常讨厌的时区问题。看到这个线程。
For setting the timestamps I would recommend using either an AOP approach or you could simply use Triggers on the table (actually this is the only thing that I ever find the use of triggers acceptable).
对于设置时间戳,我建议使用 AOP 方法,或者您可以简单地在表上使用触发器(实际上这是我发现使用触发器可接受的唯一方法)。
回答by davetron5000
Just to reinforce: java.util.Calender
is not for Timestamps. java.util.Date
is for a moment in time, agnostic of regional things like timezones. Most database store things in this fashion (even if they appear not to; this is usually a timezone setting in the client software; the data is good)
只是为了加强:java.util.Calender
不适用于 Timestamps。 java.util.Date
暂时不知道时区等区域性事物。大多数数据库以这种方式存储东西(即使它们看起来没有;这通常是客户端软件中的时区设置;数据很好)
回答by Guemundur Bjarni
If you are using the JPA annotations, you can use @PrePersist
and @PreUpdate
event hooks do this:
如果你正在使用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();
}
}
or you can use the @EntityListener
annotation on the class and place the event code in an external class.
或者您可以@EntityListener
在类上使用注释并将事件代码放在外部类中。
回答by mmacaulay
You might consider storing the time as a DateTime, and in UTC. I typically use DateTime instead of Timestamp because of the fact that MySql converts dates to UTC and back to local time when storing and retrieving the data. I'd rather keep any of that kind of logic in one place (Business layer). I'm sure there are other situations where using Timestamp is preferable though.
您可能会考虑将时间存储为 DateTime,并以 UTC 格式存储。我通常使用 DateTime 而不是 Timestamp 因为 MySql 在存储和检索数据时将日期转换为 UTC 并返回到本地时间。我宁愿将任何一种逻辑保留在一个地方(业务层)。我确信在其他情况下使用 Timestamp 更可取。
回答by ngn
Thanks everyone who helped. After doing some research myself (I'm the guy who asked the question), here is what I found to make sense most:
感谢所有帮助过的人。在自己做了一些研究之后(我是提出问题的人),这是我发现最有意义的:
Database column type: the timezone-agnostic number of milliseconds since 1970 represented as
decimal(20)
because 2^64 has 20 digits and disk space is cheap; let's be straightforward. Also, I will use neitherDEFAULT CURRENT_TIMESTAMP
, nor triggers. I want no magic in the DB.Java field type:
long
. The Unix timestamp is well supported across various libs,long
has no Y2038 problems, timestamp arithmetic is fast and easy (mainly operator<
and operator+
, assuming no days/months/years are involved in the calculations). And, most importantly, both primitivelong
s andjava.lang.Long
s are immutable—effectively passed by value—unlikejava.util.Date
s; I'd be really pissed off to find something likefoo.getLastUpdate().setTime(System.currentTimeMillis())
when debugging somebody else's code.The ORM framework should be responsible for filling in the data automatically.
I haven't tested this yet, but only looking at the docs I assume that
@Temporal
will do the job; not sure about whether I might use@Version
for this purpose.@PrePersist
and@PreUpdate
are good alternatives to control that manually. Adding that to the layer supertype (common base class) for all entities, is a cute idea provided that you really want timestamping for allof your entities.
数据库列类型:自 1970 年以来与时区无关的毫秒数,表示为
decimal(20)
因为 2^64 有 20 位数字且磁盘空间便宜;让我们直截了当。另外,我既不会使用DEFAULT CURRENT_TIMESTAMP
, 也不会使用触发器。我不想在数据库中使用魔法。Java 字段类型:
long
. Unix 时间戳在各种库中得到很好的支持,long
没有 Y2038 问题,时间戳算法快速简单(主要是 operator<
和 operator+
,假设计算中不涉及天/月/年)。而且,最重要的是,原始long
s 和java.lang.Long
s 都是不可变的——有效地通过值传递——与java.util.Date
s不同;foo.getLastUpdate().setTime(System.currentTimeMillis())
在调试其他人的代码时找到类似的东西,我真的很生气。ORM 框架应该负责自动填充数据。
我还没有测试过这个,但只查看我认为
@Temporal
可以完成这项工作的文档;不确定我是否可以@Version
用于此目的。@PrePersist
并且@PreUpdate
是手动控制的不错选择。将它添加到所有实体的层超类型(公共基类)是一个可爱的想法,前提是您确实希望为所有实体添加时间戳。
回答by Olivier Refalo
Taking the resources in this post along with information taken left and right from different sources, I came with this elegant solution, create the following abstract class
使用这篇文章中的资源以及从不同来源左右获取的信息,我提出了这个优雅的解决方案,创建以下抽象类
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();
}
}
and have all your entities extend it, for instance:
并让您的所有实体扩展它,例如:
@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}
回答by Kieren Dixon
You can also use an interceptor to set the values
您还可以使用拦截器来设置值
Create an interface called TimeStamped which your entities implement
创建一个名为 TimeStamped 的接口,由您的实体实现
public interface TimeStamped {
public Date getCreatedDate();
public void setCreatedDate(Date createdDate);
public Date getLastUpdated();
public void setLastUpdated(Date lastUpdatedDate);
}
Define the interceptor
定义拦截器
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;
}
}
And register it with the session factory
并将其注册到会话工厂
回答by endriju
With Olivier's solution, during update statements you may run into:
使用 Olivier 的解决方案,在更新语句期间,您可能会遇到:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'created' cannot be null
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列“创建”不能为空
To solve this, add updatable=false to the @Column annotation of "created" attribute:
要解决此问题,请将 updatable=false 添加到“created”属性的 @Column 注释中:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
回答by vicch
In case you are using the Session API the PrePersist and PreUpdate callbacks won't work according to this answer.
如果您使用的是会话 API,则根据此答案,PrePersist 和 PreUpdate 回调将不起作用。
I am using Hibernate Session's persist() method in my code so the only way I could make this work was with the code below and following this blog post(also posted in the answer).
我在我的代码中使用了 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;
}
}