Reputation: 3910
My entity class:
@Entity
@Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "USER_ID_GENERATOR", sequenceName = "USER_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_ID_GENERATOR")
@Column(name = "user_id")
private long userId;
@Temporal(TemporalType.DATE)
private Date created;
@Temporal(TemporalType.DATE)
private Date modified;
//setters and getters...
}
I would like to CREATED and MODIFIED fields complement each other automatically when you create or modify the object. CREATED and MODIFIED fields should be of type TIMESTAMP.
How do I achieve that?
Upvotes: 23
Views: 58895
Reputation: 61
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
.
.
.
@CreationTimestamp
private Date created;
@UpdateTimestamp
private Date modified;
Upvotes: 6
Reputation: 4537
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.
Upvotes: 21
Reputation: 108
Recently I encountered the same problem and the JPA-Annotations @PrePersist
and @PreUpdate
won't work when using the hibernate sessionFactory.
A simple way which worked for me with Hibernate 5 is to declare the field as @Version
, which will properly update the timestamp/localDateTime of the entity each time you update the database instance.
Upvotes: 0
Reputation: 1941
In 4.3 Hibernate with JPA, one can use "@CreationTimestamp" and "@UpdateTimestamp" directly in the date fields
Upvotes: 48
Reputation: 41
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:
Upvotes: 1
Reputation: 200138
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.
Upvotes: 3
Reputation: 14061
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.
Upvotes: 26
Reputation: 691635
We do this with a PreInsertEventListener and a PreUpdateEventListener :
public class TracabilityListener implements PreInsertEventListener,PreUpdateEventListener {
private void setPropertyState(Object[] propertyStates, String[] propertyNames,String propertyName,Object propertyState) {
for(int i=0;i<propertyNames.length;i++) {
if (propertyName.equals(propertyNames[i])) {
propertyStates[i]=propertyState;
return;
}
}
}
private void onInsert(Object entity,Object[] state, String[] propertyNames) {
if (entity instanceof DomainObject) {
DomainObject domainObject = (DomainObject) entity;
Date date=new Date();
domainObject.setDateCreation(date);
setPropertyState(state, propertyNames, "dateCreation", date);
domainObject.setDateModification(date);
setPropertyState(state, propertyNames, "dateModification", date);
}
}
private void onUpdate(Object entity,Object[] state, String[] propertyNames) {
if (entity instanceof DomainObject) {
DomainObject domainObject = (DomainObject) entity;
Date date=new Date();
setPropertyState(state, propertyNames, "dateCreation", domainObject.getDateCreation());
domainObject.setDateModification(date);
setPropertyState(state, propertyNames, "dateModification", date);
}
}
@Override
public boolean onPreInsert(PreInsertEvent event) {
onInsert(event.getEntity(), event.getState(), event.getPersister().getPropertyNames());
return false;
}
@Override
public boolean onPreUpdate(PreUpdateEvent event) {
onUpdate(event.getEntity(), event.getState(), event.getPersister().getPropertyNames());
return false;
}
}
But if you want your properties to be timestamps, then they should be annotated with
@Temporal(TemporalType.TIMESTAMP)
Upvotes: 14