由于这是一个常见问题,并且通过搜索可以找到很多半生不熟的解决方案,让我介绍一下我的解决方案:
- 定义两个平凡的字段注解
@CreatedDate和@ModifiedDate;
- 使用它们来注释您实体上的相应字段;
- 向
Traceability 监听器注册实体类,如下所示。
这是您的实体类的外观:
@EntityListeners(Traceability.class)
public class MyEntity {
@CreatedDate @Temporal(TIMESTAMP) public Date created;
@ModifiedDate @Temporal(TIMESTAMP public Date modified;
....
}
这些单行代码定义了注解:
@Retention(RUNTIME) @Target(FIELD) public @interface CreatedDate {}
@Retention(RUNTIME) @Target(FIELD) public @interface ModifiedDate {}
您可以将它们放在自己的文件中,也可以将它们放在某个现有的类中。我更喜欢前者,所以我可以为它们取一个更简洁的完全限定名称。
这是实体侦听器类:
public class Traceability
{
private final ConcurrentMap<Class<?>, TracedFields> fieldCache = new ConcurrentHashMap<>();
@PrePersist
public void prePersist(Object o) { touchFields(o, true); }
@PreUpdate
public void preUpdate(Object o) { touchFields(o, false); }
private void touchFields(Object o, boolean creation) {
final Date now = new Date();
final Consumer<? super Field> touch = f -> uncheckRun(() -> f.set(o, now));
final TracedFields tf = resolveFields(o);
if (creation) tf.created.ifPresent(touch);
tf.modified.ifPresent(touch);
}
private TracedFields resolveFields(Object o) {
return fieldCache.computeIfAbsent(o.getClass(), c -> {
Field created = null, modified = null;
for (Field f : c.getFields()) {
if (f.isAnnotationPresent(CreatedDate.class)) created = f;
else if (f.isAnnotationPresent(ModifiedDate.class)) modified = f;
if (created != null && modified != null) break;
}
return new TracedFields(created, modified);
});
}
private static class TracedFields {
public final Optional<Field> created, modified;
public TracedFields(Field created, Field modified) {
this.created = Optional.ofNullable(created);
this.modified = Optional.ofNullable(modified);
}
}
// Java's ill-conceived checked exceptions are even worse when combined with
// lambdas. Below is what we need to achieve "exception transparency" (ability
// to let checked exceptions escape the lambda function). This disables
// compiler's checking of exceptions thrown from the lambda, so it should be
// handled with utmost care.
public static void uncheckRun(RunnableExc r) {
try { r.run(); }
catch (Exception e) { sneakyThrow(e); }
}
public interface RunnableExc { void run() throws Exception; }
public static <T> T sneakyThrow(Throwable e) {
return Traceability.<RuntimeException, T> sneakyThrow0(e);
}
@SuppressWarnings("unchecked")
private static <E extends Throwable, T> T sneakyThrow0(Throwable t) throws E {
throw (E) t;
}
}
最后,如果您不使用 JPA 而是使用经典 Hibernate,则需要激活其 JPA 事件模型集成。这很简单,只要确保类路径包含文件META-INF/services/org.hibernate.integrator.spi.Integrator,其内容中包含以下单行:
org.hibernate.jpa.event.spi.JpaIntegrator
通常对于 Maven 项目,您只需将其放在您的 src/main/resources 目录下。