19

I'd like to add a Date/DateTime/Timestamp field on an entity object, that will be automatically created when the entity is created/persisted and set to "now", never to be updated again.

Other use cases included fields that were always updated to contain the last modification date of the entity.

I used to achieve such requirements in the mysql schema.

What's the best way to do this in Play! / JPA?

Community
  • 1
  • 1
ripper234
  • 222,824
  • 274
  • 634
  • 905

3 Answers3

41

There is a code snippet that you can adapt to achieve what you want. Take a look:

// Timestampable.java

package models;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Version;

import play.db.ebean.Model;

@MappedSuperclass
public class Timestampable extends Model {

  @Id
  @GeneratedValue
  public Long id;

  @Column(name = "created_at")
  public Date createdAt;

  @Column(name = "updated_at")
  public Date updatedAt;

  @Version
  public int version;

  @Override
  public void save() {
    createdAt();
    super.save();
  }

  @Override
  public void update() {
    updatedAt();
    super.update();
  }

  @PrePersist
  void createdAt() {
    this.createdAt = this.updatedAt = new Date();
  }

  @PreUpdate
  void updatedAt() {
    this.updatedAt = new Date();
  }
}
marcospereira
  • 12,045
  • 3
  • 46
  • 52
  • This worked, thanks. In addition to the above if you dont put the column property @Column(name = "created_date", updatable = false) during update it will be null. – Buminda Jan 09 '18 at 00:10
  • Will this set the system's time or the database system's time? I have the problem that sometimes the system time might be wrong and have no control over it, while the db system's time is correct (and more importantly unique) – ntg Jun 07 '21 at 09:16
  • Hey @ntg, this will use the system time. I highly recommend that you use UTC to save the dates to avoid this problem between the system and the database. But, depending on the database, you can use default values too. For example, in PostgreSQL: https://www.postgresql.org/docs/8.4/ddl-default.html – marcospereira Jun 08 '21 at 17:47
  • @marcospereira Yes, I tried to fill date as default (e.g. https://stackoverflow.com/questions/16609724/using-current-time-in-utc-as-default-value-in-postgresql ) but the problem was I did not know how to add the value from JPA (https://stackoverflow.com/questions/67901807/adding-a-jpa-record-bean-with-defaults), even leaving the field null, it was trying to insert null in the column, ended up making a separate request to fill it... (https://stackoverflow.com/questions/1659030/how-to-get-the-database-time-with-jpql/67873402#67873402) – ntg Jun 09 '21 at 10:15
25

I think you can achieve it in at least two ways.

Database default value

I think the easiest way would be to mark the column as updateable=false, insertable=false (this will give you immutability - the JPA will not include this column into INSERT and UPDATE statements) and setting the database column to have a default value of NOW.

JPA Lifecycle callback methods

Other way would be to provide a @PrePersist lifecycle callback method which would set your date object to the actual date new Date(). Then you would need to make sure no one will edit this value, so you shouldn't provide any setters for this property.

If you want the date to be updated when entity is modified you could, similarly, implement @PreUpdate lifecycle callback method which would set the actual modification date.

Just remember that if you're working with Date objects you should do a defensive copy of your Date object (so you should return something like new Date(oldDate.getTime()); instead of plain return oldDate).
This will prevent users from using getter of your Date and modifying its state.

Piotr Nowicki
  • 17,914
  • 8
  • 63
  • 82
  • 1
    Where would I mark the column updateable=false etc... ? Using an annotation? Also, why do I need a defensive copy on getters? Getters will not call `save()` anyway, so how can the value change? – ripper234 Nov 20 '11 at 15:10
  • 1
    Yes, adding the `@Column` annotation on your date field (or doing in the `orm.xml` file if you use it). The `java.util.Date` is a mutable class. User can get it using getter, so he obtains a reference to your class Date field. He can invoke i.e. date.setTime(1) thus changing the date state. Then the JPA will persist your date object which was changed by the user. – Piotr Nowicki Nov 20 '11 at 15:16
  • JPA will only persist if you call `save()` explicitly. Anyway, if by "users" you mean "people who are able to write Java code in my project", that's a non-issue for me. If you meant actual visitors to my site, I don't get how they can do anything to call `set` on the date object. – ripper234 Nov 20 '11 at 15:23
  • 1. I was referring not only to persist but to update of your entity (or do you plan not to update it ever?). 2. By 'user' I mean 'user of your class', not the visitors of your website. – Piotr Nowicki Nov 20 '11 at 15:28
  • A word of explanation. In EclipseLink, there is no need for defensive copy of a `java.util.Date` object. EclipseLink, by default, has `eclipselink.temporal.mutable` option set to `true` which requires it to treat Java-mutable `Date` field as JPA-immutable `Date` field. Every operation which tries to invoke `oldDate.setTime(-)` will be ignored and will not be reflected in persisted value. Don't know how Hibernate works in this case. – Piotr Nowicki Nov 24 '11 at 21:12
4

I think it's a little bit clearly

    @Column(name = "created_at")
    @CreationTimestamp
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    @UpdateTimestamp
    private LocalDateTime updatedAt;
lirugo
  • 41
  • 4
  • 1
    Useful to know, however as far as I can tell those or Hibernate rather than JPA annotations – Gavin Sep 23 '20 at 08:51
  • This solution looks like it has en issue when inserting the timestamp, because the last three digits of the timestamp, milliseconds digits are zero, example: 2021-07-09 12:09:10.820000 – Alter Sep 22 '21 at 03:00