My entity class:
@Entity
@Table(name = \"user\")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
In 4.3 Hibernate with JPA, one can use "@CreationTimestamp" and "@UpdateTimestamp" directly in the date fields
CreationTimestamp java doc
UpdateTimestamp java doc
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
.
.
.
@CreationTimestamp
private Date created;
@UpdateTimestamp
private Date modified;
You can just create a new Date()
whenever your instance is created, and then update the updated
field whenever the entity gets updated:
private Date created = new Date();
private Date updated = new Date();
@PreUpdate
public void setLastUpdate() { this.updated = new Date(); }
Don't provide a setter for any of these methods, only getters.
Since @PrePersist and @PreUpdate are ignored when Hibernate Session is used I've made a relatively simple solution using Interceptors:
Define an interface "Auditable":
public interface Auditable {
public void setUpdated_at(Date date);
public void setCreated_at(Date date);
}
Define a class "AuditableInterceptor"
public class AuditableInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = -3557239720502671471L;
@override
public boolean onFlushDirty(Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types) {
if (entity instanceof Auditable) {
for (int i = 0; i < propertyNames.length; i++) {
if ("updated_at".equals(propertyNames[i])) {
currentState[i] = new Date();
return true;
}
}
}
return false;
}
@override
public boolean onSave(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if (entity instanceof Auditable) {
for (int i = 0; i < propertyNames.length; i++) {
if ("created_at".equals(propertyNames[i])) {
state[i] = new Date();
return true;
}
}
}
return false;
}
}
Specify the Interceptor when ever you open a new session (you'll likely have this in a utility class)
sessionFactory.openSession(new AuditableInterceptor());
// sessionFactory.openSession();
Implement the Auditable interface in your entities, e.g.
@Entity
public class Product implements Auditable {
...
private Date created_at;
private Date updated_at;
...
public Product() {
}
...
@Temporal(javax.persistence.TemporalType.TIMESTAMP)
public Date getCreated_at() {
return created_at;
}
public void setCreated_at(Date created_at) {
this.created_at = created_at;
}
@Temporal(javax.persistence.TemporalType.TIMESTAMP)
public Date getUpdated_at() {
return updated_at;
} @Override
@Override
public void setUpdated_at(Date updated_at) {
this.updated_at = updated_at;
}
...
}
Notes:
You can go with Spring Data JPA, Spring has made it as easy using annotation @CreatedBy, @CreatedDate, @LastModifiedBy, @LastModifiedDate on your fields. You can follow below simple example
// Will need to enable JPA Auditing
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
class JpaConfig {
// Creating a bean of AuditorAwareImpl which will provide currently logged in user
@Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
// Moving necessary fields to super class and providing AuditingEntityListener entity listner class
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
abstract class Auditable<U> {
@CreatedBy
protected U createdBy;
@CreatedDate
@Temporal(TIMESTAMP)
protected Date createdDate;
@LastModifiedBy
protected U lastModifiedBy;
@LastModifiedDate
@Temporal(TIMESTAMP)
protected Date lastModifiedDate;
// Getters and Setters
}
// Creating implementation of AuditorAware and override its methods to provide currently logged in user
class AuditorAwareImpl implements AuditorAware<String> {
@Override
public String getCurrentAuditor() {
return "Naresh";
// Can use Spring Security to return currently logged in user
// return ((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername()
}
}
@Entity
class File extends Auditable<String> {
@Id
@GeneratedValue
private Integer id;
private String name;
private String content;
// Getters and Setters
}
You can read more on my article Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically for more details.
Since this is a common problem, and there are a lot of half-bred solutions reachable by searching, let me present what I settled on:
@CreatedDate
and @ModifiedDate
;Traceability
listener, presented below.This is how your entity class would look:
@EntityListeners(Traceability.class)
public class MyEntity {
@CreatedDate @Temporal(TIMESTAMP) public Date created;
@ModifiedDate @Temporal(TIMESTAMP public Date modified;
....
}
These one-liners define the annotations:
@Retention(RUNTIME) @Target(FIELD) public @interface CreatedDate {}
@Retention(RUNTIME) @Target(FIELD) public @interface ModifiedDate {}
You can put them in their own files, or you can bunch them up inside some existing class. I prefer the former so I get a cleaner fully-qualified name for them.
Here's the Entity listener class:
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;
}
}
Finally, if you aren't working with JPA but with classic Hibernate, you need to activate its JPA event model integration. This is very simple, just make sure that the classpath contains the file META-INF/services/org.hibernate.integrator.spi.Integrator
, with the following single line in its contents:
org.hibernate.jpa.event.spi.JpaIntegrator
Typically for a Maven project, you just need to put this under your src/main/resources
directory.