310

For a certain Hibernate entity we have a requirement to store its creation time and the last time it was updated. How would you design this?

  • What data types would you use in the database (assuming MySQL, possibly in a different timezone that the JVM)? Will the data types be timezone-aware?

  • What data types would you use in Java (Date, Calendar, long, ...)?

  • Whom would you make responsible for setting the timestamps—the database, the ORM framework (Hibernate), or the application programmer?

  • What annotations would you use for the mapping (e.g. @Temporal)?

I'm not only looking for a working solution, but for a safe and well-designed solution.

ngn
  • 7,763
  • 6
  • 26
  • 35
  • I think it's better to use LocalDateTime instead of outdated Date in Java entities. Also, db should not be aware about time zone, it causing many problems with data migration. So I would use datetime SQL type. – chill appreciator Jul 17 '20 at 21:54

18 Answers18

308

If you are using the JPA annotations, you can use @PrePersist and @PreUpdate event hooks do this:

@Entity
@Table(name = "entities")    
public class Entity {
  ...

  private Date created;
  private Date updated;

  @PrePersist
  protected void onCreate() {
    created = new Date();
  }

  @PreUpdate
  protected void onUpdate() {
    updated = new Date();
  }
}

or you can use the @EntityListener annotation on the class and place the event code in an external class.

Baldrick
  • 23,882
  • 6
  • 74
  • 79
Guðmundur Bjarni
  • 4,082
  • 1
  • 18
  • 14
243

You can just use @CreationTimestamp and @UpdateTimestamp:

@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date")
private Date createDate;

@UpdateTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date")
private Date modifyDate;
idmitriev
  • 4,619
  • 4
  • 28
  • 44
  • 6
    thanks bro such a small thing need to update timestamp. I didnt know. you saved my day. – Virendra Sagar Jan 27 '17 at 14:28
  • There is `TemporalType.DATE` in the first case and `TemporalType.TIMESTAMP` in the second one. – v.ladynev Feb 08 '17 at 15:33
  • 1
    Are you saying this also automatically sets the values? That's not my experience; it seems even with `@CreationTimestamp` and `@UpdateTimestamp` one either needs some `@Column(..., columnDefinition = "timestamp default current_timestamp")`, or use `@PrePersist` and `@PreUpdate` (the latter also nicely ensuring clients cannot set a different value). – Arjan Mar 18 '17 at 13:57
  • Yeah, Hibernate sets it for you, don't set it manually. timestamp default current_timestamp - not needed – idmitriev Mar 18 '17 at 14:03
  • Why is not this the most voted answer? Any drawbacks with this? – McSonk Apr 03 '17 at 16:09
  • I can answer - actually I posted it recently, and it was not well known feature. I found it maybe a year ago, just browsing all Hibernate annotations. There are a lot of useful annotations in org.hibernate.annotations package, like '@Loader', '@Where', '@Subselect', and so on. – idmitriev Apr 03 '17 at 17:52
  • There are no drawbacks, except that you can't use java 8 time API with it before Hibernate 5.2.3, for that purpose you can use Spring Data '@LastModifiedDate' and '@CreatedDate' – idmitriev Apr 03 '17 at 17:55
  • 1
    In my case, the DB column (created_date) didn't have a default value and it was nullable, so in my case, i had to do @Column(name = "create_date" , nullable=false) for it to work, but this was a push in the right direction for me :) thank you ! – Seagull Apr 16 '17 at 17:05
  • 4
    When I update the object and persist it, the bd lost the create_date... why? – KpsLok Oct 19 '17 at 02:04
  • 3
    Im my case removing `nullable=false` from `@Column(name = "create_date" , nullable=false)` worked – Shantaram Tupe Nov 22 '17 at 10:14
  • @BrennoLeal Same here, an update is removing creation date even I applied nullable = false. using 5.2.17.Final – rogue lad Jul 02 '18 at 08:01
  • @Max I'm having the same issue. Were you able to fix it in the meantime? – tomasulo Dec 17 '18 at 16:24
  • 3
    @Tomasulo , use @Column(name = "creation_date", updatable = false) – rogue lad Jun 03 '19 at 21:31
  • Just add `updatable=false` to createdDate not for modifyDate like `@CreationTimestamp @Temporal(TemporalType.TIMESTAMP) @Column(name = "create_date", updatable=false) private Date createDate]` – Gopala Raja Naika Mar 30 '21 at 05:25
  • 1
    I found when using the timestamps I always have to add the following annotation to the class to make them function properly: @EntityListeners(AuditingEntityListener.class) – Nox Mar 31 '21 at 17:33
123

Taking the resources in this post along with information taken left and right from different sources, I came with this elegant solution, create the following abstract class

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public abstract class AbstractTimestampEntity {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created", nullable = false)
    private Date created;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated", nullable = false)
    private Date updated;

    @PrePersist
    protected void onCreate() {
    updated = created = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
    updated = new Date();
    }
}

and have all your entities extend it, for instance:

@Entity
@Table(name = "campaign")
public class Campaign extends AbstractTimestampEntity implements Serializable {
...
}
Alex
  • 855
  • 7
  • 21
Olivier Refalo
  • 50,287
  • 22
  • 91
  • 122
  • 7
    this is good until you want to add _different_ exclusive behaviours to your entities (and you can't extend more than one base class). afaik the only way to obtain the same effect without a base class is though aspectj itd or event listeners see @kieren dixon answer – gpilotino Feb 02 '12 at 00:49
  • 3
    I would do this using a MySQL trigger so that even if the full entity is not saved or is modified by any external application or manual query, it'll still update these fields. – Ben Jul 25 '13 at 12:09
  • 3
    can you give me any working example because I'm experiencing exception `not-null property references a null or transient value: package.path.ClassName.created` – Sumit Ramteke Feb 14 '14 at 09:41
  • @rishiAgar, No I haven't. But for now I have assign date to my property from default constructor. Will let you know once I found. – Sumit Ramteke Dec 29 '14 at 06:50
  • 1
    Change it to `@Column(name = "updated", nullable = false, insertable = false)` to make it work. Interesting that this answer got so many upvotes .. – Stefan Falk May 08 '15 at 21:56
  • Looks like an implementation change from the answer published in 2010 and your comment which came 5 years later. On a sidenote, I don't understand what "insertable = false" has to do here. – Olivier Refalo May 11 '15 at 15:45
  • insertable="false" doesn't set values while persisting in this case, so the OP is right in his example – ngCoder Mar 28 '17 at 10:41
99
  1. What database column types you should use

Your first question was:

What data types would you use in the database (assuming MySQL, possibly in a different timezone that the JVM)? Will the data types be timezone-aware?

In MySQL, the TIMESTAMP column type does a shifting from the JDBC driver local time zone to the database timezone, but it can only store timestamps up to 2038-01-19 03:14:07.999999, so it's not the best choice for the future.

So, better to use DATETIME instead, which doesn't have this upper boundary limitation. However, DATETIME is not timezone aware. So, for this reason, it's best to use UTC on the database side and use the hibernate.jdbc.time_zone Hibernate property.

  1. What entity property type you should use

Your second question was:

What data types would you use in Java (Date, Calendar, long, ...)?

On the Java side, you can use the Java 8 LocalDateTime. You can also use the legacy Date, but the Java 8 Date/Time types are better since they are immutable, and don't do a timezone shifting to local timezone when logging them.

Now, we can also answer this question:

What annotations would you use for the mapping (e.g. @Temporal)?

If you are using the LocalDateTime or java.sql.Timestamp to map a timestamp entity property, then you don't need to use @Temporal since HIbernate already knows that this property is to be saved as a JDBC Timestamp.

Only if you are using java.util.Date, you need to specify the @Temporal annotation, like this:

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;

But, it's much better if you map it like this:

@Column(name = "created_on")
private LocalDateTime createdOn;

How to generate the audit column values

Your third question was:

Whom would you make responsible for setting the timestamps—the database, the ORM framework (Hibernate), or the application programmer?

What annotations would you use for the mapping (e.g. @Temporal)?

There are many ways you can achieve this goal. You can allow the database to do that..

For the create_on column, you could use a DEFAULT DDL constraint, like :

ALTER TABLE post 
ADD CONSTRAINT created_on_default 
DEFAULT CURRENT_TIMESTAMP() FOR created_on;

For the updated_on column, you could use a DB trigger to set the column value with CURRENT_TIMESTAMP() every time a given row is modified.

Or, use JPA or Hibernate to set those.

Let's assume you have the following database tables:

Database tables with audit columns

And, each table has columns like:

  • created_by
  • created_on
  • updated_by
  • updated_on

Using Hibernate @CreationTimestamp and @UpdateTimestamp annotations

Hibernate offers the @CreationTimestamp and @UpdateTimestamp annotations that can be used to map the created_on and updated_on columns.

You can use @MappedSuperclass to define a base class that will be extended by all entities:

@MappedSuperclass
public class BaseEntity {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @Column(name = "created_on")
    @CreationTimestamp
    private LocalDateTime createdOn;
 
    @Column(name = "created_by")
    private String createdBy;
 
    @Column(name = "updated_on")
    @UpdateTimestamp
    private LocalDateTime updatedOn;
 
    @Column(name = "updated_by")
    private String updatedBy;
 
    //Getters and setters omitted for brevity
}

And, all entities will extend the BaseEntity, like this:

@Entity(name = "Post")
@Table(name = "post")
public class Post extend BaseEntity {
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;
 
    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();
 
    //Getters and setters omitted for brevity
}

However, even if the createdOn and updateOn properties are set by the Hibernate-specific @CreationTimestamp and @UpdateTimestamp annotations, the createdBy and updatedBy require registering an application callback, as illustrated by the following JPA solution.

Using JPA @EntityListeners

You can encapsulate the audit properties in an Embeddable:

@Embeddable
public class Audit {
 
    @Column(name = "created_on")
    private LocalDateTime createdOn;
 
    @Column(name = "created_by")
    private String createdBy;
 
    @Column(name = "updated_on")
    private LocalDateTime updatedOn;
 
    @Column(name = "updated_by")
    private String updatedBy;
 
    //Getters and setters omitted for brevity
}

And, create an AuditListener to set the audit properties:

public class AuditListener {
 
    @PrePersist
    public void setCreatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();
 
        if(audit == null) {
            audit = new Audit();
            auditable.setAudit(audit);
        }
 
        audit.setCreatedOn(LocalDateTime.now());
        audit.setCreatedBy(LoggedUser.get());
    }
 
    @PreUpdate
    public void setUpdatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();
 
        audit.setUpdatedOn(LocalDateTime.now());
        audit.setUpdatedBy(LoggedUser.get());
    }
}

To register the AuditListener, you can use the @EntityListeners JPA annotation:

@Entity(name = "Post")
@Table(name = "post")
@EntityListeners(AuditListener.class)
public class Post implements Auditable {
 
    @Id
    private Long id;
 
    @Embedded
    private Audit audit;
 
    private String title;
 
    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostComment> comments = new ArrayList<>();
 
    @OneToOne(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private PostDetails details;
 
    @ManyToMany
    @JoinTable(
        name = "post_tag",
        joinColumns = @JoinColumn(
            name = "post_id"
        ),
        inverseJoinColumns = @JoinColumn(
            name = "tag_id"
        )
    )
    private List<Tag> tags = new ArrayList<>();
 
    //Getters and setters omitted for brevity
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Very thorough answer, thanks. I disagree about preferring `datetime` over `timestamp`. You want your database to know the time zone of your timestamps. This prevents time zone conversion errors. – Ole V.V. Feb 23 '20 at 11:46
  • 1
    The `timestsmp` type does not store timezone info. It just does a conversation from app TZ to DB TZ. In reality, you want to store the client TZ separately and do the conversation in the application prior to rendering the UI. – Vlad Mihalcea Feb 23 '20 at 13:02
  • Correct. MySQL `timestamp` is always in UTC. *MySQL converts `TIMESTAMP` values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval.* [MySQL documentation: The DATE, DATETIME, and TIMESTAMP Types](https://dev.mysql.com/doc/refman/8.0/en/datetime.html) – Ole V.V. Feb 23 '20 at 13:23
  • Thanks a lot for this detailed and very clear answer! Although, I'd like to use the JPA embeddable class but is it possible if my tables have different column names for the createdBy and createdOn... Is it possible to specify the column names in the class that uses the embeddable class? – Elie G. Mar 03 '21 at 18:56
  • Yes, of course, Use `@AttributeOverride` for that. – Vlad Mihalcea Mar 03 '21 at 20:04
20

With Olivier's solution, during update statements you may run into:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'created' cannot be null

To solve this, add updatable=false to the @Column annotation of "created" attribute:

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable = false, updatable=false)
private Date created;
endriju
  • 1,508
  • 13
  • 10
  • 1
    We are using `@Version`. When an entity is set two calls are made one is to save and another one to update. I was facing the same issue because of this. Once I added `@Column(updatable = false)` it solved my problem. – Ganesh Satpute Jan 21 '19 at 10:04
18

You can also use an interceptor to set the values

Create an interface called TimeStamped which your entities implement

public interface TimeStamped {
    public Date getCreatedDate();
    public void setCreatedDate(Date createdDate);
    public Date getLastUpdated();
    public void setLastUpdated(Date lastUpdatedDate);
}

Define the interceptor

public class TimeStampInterceptor extends EmptyInterceptor {

    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, 
            Object[] previousState, String[] propertyNames, Type[] types) {
        if (entity instanceof TimeStamped) {
            int indexOf = ArrayUtils.indexOf(propertyNames, "lastUpdated");
            currentState[indexOf] = new Date();
            return true;
        }
        return false;
    }

    public boolean onSave(Object entity, Serializable id, Object[] state, 
            String[] propertyNames, Type[] types) {
            if (entity instanceof TimeStamped) {
                int indexOf = ArrayUtils.indexOf(propertyNames, "createdDate");
                state[indexOf] = new Date();
                return true;
            }
            return false;
    }
}

And register it with the session factory

Rohit Banga
  • 18,458
  • 31
  • 113
  • 191
Kieren Dixon
  • 947
  • 10
  • 9
  • This is one solution, if you work with SessionFactory instead of EntityManager! – olivmir Feb 27 '18 at 14:07
  • Just for those, who suffer with a similar problem as I did in this context: if your entity does not itself define these extra fields (createdAt, ...) but inherit it from a parent class, then this parent class has to be annotated with @MappedSuperclass - otherwise Hibernate doesn't find these fields. – olivmir Feb 27 '18 at 14:11
12

Thanks everyone who helped. After doing some research myself (I'm the guy who asked the question), here is what I found to make sense most:

  • Database column type: the timezone-agnostic number of milliseconds since 1970 represented as decimal(20) because 2^64 has 20 digits and disk space is cheap; let's be straightforward. Also, I will use neither DEFAULT CURRENT_TIMESTAMP, nor triggers. I want no magic in the DB.

  • Java field type: long. The Unix timestamp is well supported across various libs, long has no Y2038 problems, timestamp arithmetic is fast and easy (mainly operator < and operator +, assuming no days/months/years are involved in the calculations). And, most importantly, both primitive longs and java.lang.Longs are immutable—effectively passed by value—unlike java.util.Dates; I'd be really pissed off to find something like foo.getLastUpdate().setTime(System.currentTimeMillis()) when debugging somebody else's code.

  • The ORM framework should be responsible for filling in the data automatically.

  • I haven't tested this yet, but only looking at the docs I assume that @Temporal will do the job; not sure about whether I might use @Version for this purpose. @PrePersist and @PreUpdate are good alternatives to control that manually. Adding that to the layer supertype (common base class) for all entities, is a cute idea provided that you really want timestamping for all of your entities.

ngn
  • 7,763
  • 6
  • 26
  • 35
  • While longs and Longs may be immutable, that will not help you in the situation you describe. They can still say foo.setLastUpdate(new Long(System.currentTimeMillis()); – Ian McLaird Oct 23 '08 at 14:52
  • 2
    That's fine. Hibernate requires the setter anyway (or it will try to access the field directly through reflection). I was talking about difficulty chasing down who's modifying the timestamp from our application code. It is tricky when you can do that using a getter. – ngn Oct 23 '08 at 15:21
  • I agree with your claim that the ORM framework should be responsible for filling the date automatically, but I would go one step farther and say that the date should be set from the clock of the database server, rather than the client. I'm not clear if this accomplishes this goal. In sql, I can do this by using the sysdate function, but I don't know how to do this in Hibernate or any JPA implementation. – MiguelMunoz Jun 06 '17 at 21:12
  • 1
    _I want no magic in the DB._ I see what you mean, but I like to consider the fact that database should protect itself from bad/new/clueless developers. Data integrity is very important in a large company, you can't rely on others to insert good data. Constraints, defaults, and FKs will help achieve that. – Icegras Jul 12 '17 at 14:37
9

For those whose want created or modified user detail along with the time using JPA and Spring Data can follow this. You can add @CreatedDate,@LastModifiedDate,@CreatedBy and @LastModifiedBy in the base domain. Mark the base domain with @MappedSuperclass and @EntityListeners(AuditingEntityListener.class) like shown below:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseDomain implements Serializable {

    @CreatedDate
    private Date createdOn;

    @LastModifiedDate
    private Date modifiedOn;

    @CreatedBy
    private String createdBy;

    @LastModifiedBy
    private String modifiedBy;

}

Since we marked the base domain with AuditingEntityListener we can tell JPA about currently logged in user. So we need to provide an implementation of AuditorAware and override getCurrentAuditor() method. And inside getCurrentAuditor() we need to return the currently authorized user Id.

public class AuditorAwareImpl implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication == null ? Optional.empty() : Optional.ofNullable(authentication.getName());
    }
}

In the above code if Optional is not working you may using Java 7 or older. In that case try changing Optional with String.

Now for enabling the above Audtior implementation use the code below

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfig {
    @Bean
    public AuditorAware<String> auditorAware() {
        return new AuditorAwareImpl();
    }
}

Now you can extend the BaseDomain class to all of your entity class where you want the created and modified date & time along with user Id

Mohammed Javad
  • 629
  • 8
  • 18
6

In case you are using the Session API the PrePersist and PreUpdate callbacks won't work according to this answer.

I am using Hibernate Session's persist() method in my code so the only way I could make this work was with the code below and following this blog post (also posted in the answer).

@MappedSuperclass
public abstract class AbstractTimestampEntity {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created")
    private Date created=new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated")
    @Version
    private Date updated;

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }
}
Community
  • 1
  • 1
vicch
  • 564
  • 3
  • 10
  • 25
  • Should return cloned objects like `updated.clone()` otherwise other components can manipulate the internal state (date) – 1ambda Aug 06 '17 at 06:38
4

Now there is also @CreatedDate and @LastModifiedDate annotations.

=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html

(Spring framework)

Oreste Viron
  • 3,592
  • 3
  • 22
  • 34
4

If we are using @Transactional in our methods, @CreationTimestamp and @UpdateTimestamp will save the value in DB but will return null after using save(...).

In this situation, using saveAndFlush(...) did the trick

chomp
  • 1,352
  • 13
  • 31
  • 1
    this, TIL save doesn't necessarily save changes immediately to the database, flush however synchronizes the entity with the database. – Yassine Addi May 03 '22 at 00:56
3

Following code worked for me.

package com.my.backend.models;

import java.util.Date;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

import com.fasterxml.jackson.annotation.JsonIgnore;

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

import lombok.Getter;
import lombok.Setter;

@MappedSuperclass
@Getter @Setter
public class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @CreationTimestamp
    @ColumnDefault("CURRENT_TIMESTAMP")
    protected Date createdAt;

    @UpdateTimestamp
    @ColumnDefault("CURRENT_TIMESTAMP")
    protected Date updatedAt;
}
prranay
  • 1,789
  • 5
  • 23
  • 33
  • Hi, why would we need `protected Integer id;` as `protected` in parent class in general, because i could not use it in my test cases as `.getId()` – shareef Jun 18 '20 at 13:06
2

Just to reinforce: java.util.Calender is not for Timestamps. java.util.Date is for a moment in time, agnostic of regional things like timezones. Most database store things in this fashion (even if they appear not to; this is usually a timezone setting in the client software; the data is good)

davetron5000
  • 24,123
  • 11
  • 70
  • 98
1

A good approach is to have a common base class for all your entities. In this base class, you can have your id property if it is commonly named in all your entities (a common design), your creation and last update date properties.

For the creation date, you simply keep a java.util.Date property. Be sure, to always initialize it with new Date().

For the last update field, you can use a Timestamp property, you need to map it with @Version. With this Annotation the property will get updated automatically by Hibernate. Beware that Hibernate will also apply optimistic locking (it's a good thing).

bernardn
  • 1,701
  • 5
  • 19
  • 23
  • 2
    using a timestamp column for optimistic locking is a bad idea. Always use an integer version column. Reason being, 2 JVMs might be on different times and might not have millisecond accuracy. If you instead make hibernate use the DB timestamp, that would mean additional selects from the DB. Instead just use version number. – sethu Feb 18 '13 at 04:46
1

As data type in JAVA I strongly recommend to use java.util.Date. I ran into pretty nasty timezone problems when using Calendar. See this Thread.

For setting the timestamps I would recommend using either an AOP approach or you could simply use Triggers on the table (actually this is the only thing that I ever find the use of triggers acceptable).

Community
  • 1
  • 1
huo73
  • 599
  • 6
  • 17
1

You might consider storing the time as a DateTime, and in UTC. I typically use DateTime instead of Timestamp because of the fact that MySql converts dates to UTC and back to local time when storing and retrieving the data. I'd rather keep any of that kind of logic in one place (Business layer). I'm sure there are other situations where using Timestamp is preferable though.

mmacaulay
  • 3,049
  • 23
  • 27
1

We had a similar situation. We were using Mysql 5.7.

CREATE TABLE my_table (
        ...
      updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

This worked for us.

amdg
  • 2,211
  • 1
  • 14
  • 25
  • It also works in a case when the data is modified by a SQL query directly in the database. `@PrePersist` and `@PrePersist` don't cover such a case. – pidabrow Jan 20 '20 at 12:47
0

I think it is neater not doing this in Java code, you can simply set column default value in MySql table definition. enter image description here

Wayne Wei
  • 2,907
  • 2
  • 13
  • 19