1. 概述
在这篇文章中,我们将学习如何使用Hibernate注解@CreationTimestamp
和@UpdateTimestamp
来追踪实体的创建和更新时间。
2. 模型
为了演示这些注解的工作方式,我们从一个简单的Book
实体开始:
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String title;
public Book() {
}
// standard setters and getters
}
我们将使用H2数据库,其基于Book
的模式DDL会自动生成。Hibernate要求与@CreationTimestamp
或@UpdateTimestamp
注解字段关联的数据库列必须是基于时间戳的类型,如Timestamp
或DateTime
。
3. 使用@CreationTimestamp
追踪创建日期和时间
我们经常需要持久化实体的创建日期。@CreationTimestamp
是一个方便的注解,当实体首次保存时,它会将字段值设置为当前时间戳。让我们在Book
中添加一个名为createdOn
的字段,带有这个注解。由于该字段需要存储过去的一个确切时刻,我们将声明它为Instant
,它代表UTC中的一个特定时刻。在生成的模式中,这个字段将具有Timestamp
类型:
@Entity
public class Book {
//other fields
@CreationTimestamp
private Instant createdOn;
// standard setters and getters
现在,让我们验证Hibernate在保存新书后是否设置了createdOn
:
@Test
void whenCreatingEntity_ThenCreatedOnIsSet() {
session = sessionFactory.openSession();
session.beginTransaction();
Book book = new Book();
session.save(book);
session.getTransaction()
.commit();
session.close();
assertNotNull(book.getCreatedOn());
}
4. 使用@UpdateTimestamp
追踪最后更新时间
类似地,我们可能需要记录实体最后一次更新的时间。@UpdateTimestamp
是Hibernate提供的另一个注解,它会在每次更新实体时自动设置字段值。让我们添加一个名为lastUpdatedOn
的Instant
字段,这次带有@UpdateTimestamp
注解:
@Entity
public class Book {
//other fields
@UpdateTimestamp
private Instant lastUpdatedOn;
// standard setters and getters
让我们检查Hibernate在实体创建时是否填充了lastUpdatedOn
:
@Test
void whenCreatingEntity_ThenCreatedOnAndLastUpdatedOnAreBothSet() {
session = sessionFactory.openSession();
session.beginTransaction();
Book book = new Book();
session.save(book);
session.getTransaction()
.commit();
session.close();
assertNotNull(book.getCreatedOn());
assertNotNull(book.getLastUpdatedOn());
}
我们确认Hibernate按预期生成了lastUpdatedOn
。接下来,我们也检查lastUpdatedOn
在更新book
时是否改变,而createdOn
保持不变:
@Test
void whenUpdatingEntity_ThenLastUpdatedOnIsUpdatedAndCreatedOnStaysTheSame() {
session = sessionFactory.openSession();
session.setHibernateFlushMode(MANUAL);
session.beginTransaction();
Book book = new Book();
session.save(book);
session.flush();
Instant createdOnAfterCreation = book.getCreatedOn();
Instant lastUpdatedOnAfterCreation = book.getLastUpdatedOn();
String newName = "newName";
book.setTitle(newName);
session.save(book);
session.flush();
session.getTransaction().commit();
session.close();
Instant createdOnAfterUpdate = book.getCreatedOn();
Instant lastUpdatedOnAfterUpdate = book.getLastUpdatedOn();
assertEquals(newName, book.getTitle());
assertNotNull(createdOnAfterUpdate);
assertNotNull(lastUpdatedOnAfterUpdate);
assertEquals(createdOnAfterCreation, createdOnAfterUpdate);
assertNotEquals(lastUpdatedOnAfterCreation, lastUpdatedOnAfterUpdate);
}
在我们为book
设置新标题后,只有lastUpdatedOn
发生了变化。
5. 当前日期的来源
为了使这些注解发挥作用,我们必须确保时钟正确设置,以确保时间戳的准确性。默认情况下,这两个注解在设置属性值时都使用Java虚拟机的当前日期。
从Hibernate 6.0.0版本开始,我们可以选择性地指定数据库作为日期的来源:
@CreationTimestamp(source = SourceType.DB)
private Instant createdOn;
@UpdateTimestamp(source = SourceType.DB)
private Instant lastUpdatedOn;
在这种情况下,底层数据库指定了如何确定当前日期。例如,这可能是数据库函数current_timestamp()
。
6. 注意事项
正如我们之前展示的,我们在创建实体时设置了createdOn
和lastUpdatedOn
。
@Test
void whenCreatingEntity_ThenCreatedOnAndLastUpdatedOnAreEqual() {
session = sessionFactory.openSession();
session.beginTransaction();
Book book = new Book();
session.save(book);
session.getTransaction()
.commit();
session.close();
assertEquals(book.getCreatedOn(), book.getLastUpdatedOn());
}
创建和更新之间的差异可能仅相差毫秒。如果出于某种原因,我们需要在创建后这两个日期完全相同,我们应该使用其他方法设置时间戳。我们可以通过描述的JPA @PrePersist
和@PreUpdate
回调来实现这一点,如使用JPA、Hibernate和Spring Data JPA进行审计。
此外,我们必须记住,这些注解只会在我们的Java应用程序创建和修改数据时生成新的时间戳。它们对表的作用范围仅限于此。如果其他应用程序或SQL脚本修改book
表,我们必须使用其他方法更新我们的时间戳。
Instant
是Java 8中添加的新日期和时间API的一部分,从Hibernate 5.2.3版本开始原生支持。推荐使用这些新类来表示日期,它们位于java.time
包中。然而,如果我们需要支持较旧的Hibernate或Java版本,可能需要使用不同的时间戳类型。有关如何使用Hibernate将时间戳映射到Java类字段的更多信息,请阅读Hibernate - 映射日期和时间。
7. 总结
在这篇教程中,我们展示了如何使用@CreationTimestamp
和@UpdateTimestamp
自动生成时间戳。使用这些注解是监控实体修改日期的一种简单方法。但是,当我们使用它们时,必须记住Hibernate是以字段为单位生成新时间戳的。这会导致即使是由同一个INSERT
或UPDATE
语句设置的,多个时间戳也可能不同。
另外,我们需要注意,这些注解并没有为表创建全局机制。如果我们的数据库表将被不同的应用程序修改,我们需要确保每个应用程序都能正确设置更新和创建时间戳。
如往常一样,本文的完整代码示例可在GitHub上找到。