Поскольку это распространенная проблема, и есть много полукровных решений, доступных с помощью поиска, позвольте мне представить, на чем я остановился:
- определяют две тривиальные аннотации полей,
@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 {}
Вы можете поместить их в свои собственные файлы или связать ихвнутри некоторого существующего класса.Я предпочитаю первое, поэтому я получаю более чистое полное имя для них.
Вот класс слушателя Entity:
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.Это очень просто, просто убедитесь, что classpath содержит файл META-INF/services/org.hibernate.integrator.spi.Integrator
со следующей строкой в его содержании:
org.hibernate.jpa.event.spi.JpaIntegrator
Как правило, для проекта Maven, вам просто нужно поместить это подsrc/main/resources
каталог.