tomasz-mer
tomasz-mer

Reputation: 3910

How to autogenerate created or modified timestamp field?

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

Answers (8)

user7404652
user7404652

Reputation: 61

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

.
.
.

@CreationTimestamp
private Date created;

@UpdateTimestamp
private Date modified;

Upvotes: 6

Naresh Joshi
Naresh Joshi

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

kaikun
kaikun

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

Rajesh
Rajesh

Reputation: 1941

In 4.3 Hibernate with JPA, one can use "@CreationTimestamp" and "@UpdateTimestamp" directly in the date fields

CreationTimestamp java doc

UpdateTimestamp java doc

Upvotes: 48

arcasys
arcasys

Reputation: 41

Since @PrePersist and @PreUpdate are ignored when Hibernate Session is used I've made a relatively simple solution using Interceptors:

  1. Define an interface "Auditable":

    public interface Auditable {
      public void setUpdated_at(Date date);
      public void setCreated_at(Date date);
    }
    
  2. 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;
        }
    }
    
  3. 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();
    
  4. 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:

  1. This example expects the properties created_at and updated_at. For different names the method names have also to be adapted.
  2. Type must be org.hibernate.type.Type!

Upvotes: 1

Marko Topolnik
Marko Topolnik

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:

  1. define two trivial field annotations, @CreatedDate and @ModifiedDate;
  2. use them to annotate the corresponding fields on your entity;
  3. register the entity class with the 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

jpkroehling
jpkroehling

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

JB Nizet
JB Nizet

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

Related Questions