That is a $ 1M question.
Why it wasn't supported in the past, I don't know. It is indeed odd. However, it seems it might become part of upcoming JPA 3.2 Spec. In my opinion Instant
should have been the first JSR-310 type to support - not the last coming along 10 years later.
Here is some background:
Almost all RDBMSes can indeed only store an Instant. Some, like PostgreSQL timestamptz
, will give you the illusion that they can do more, but it is really a hoax as pointed out by many others.
If your entity class looks like this
@Entity
public class MyEntity {
...
@Column(name="created_at")
private ZonedDateTime createdAt;
public ZonedDateTime getCreatedAt() {
return this.createdAt;
}
public void setCreatedAt(ZonedDateTime ts) {
this.createdAt = ts;
}
}
you are most likely doing it wrong: With this, your persistence layer code is giving you the illusion that it can store a ZonedDateTime
for you while in reality it can't.
(the notable exception is Oracle Database which can actually store a ZonedDateTime without loss of information, but I must admit I've never seen it used in real life).
I've found this comment:
Instant is not covered by JDBC, so is not by JPA. At least not yet.
which may explain why the JPA maintainers did not (until recently) acknowledge why it should be mentioned in the JPA Spec. But the above statement is wrong. Support for Instant
in JDBC has been there all along. Let me explain:
Reading and writing Instant values using JDBC
The JDBC java.sql.Timestamp
and Instant
is essentially the same thing. Except for some edge cases caused by the fact that Instant
can store dates further in the past or into the future than can Date
(which Timestamp
extends from) there is a lossless conversion between the two. (edge cases: we are talking about dates in future year 292,469,238 and beyond which would get you into trouble and similar nonsense on the past side, so yes, for all practical purpose there is lossless conversion between the two).
So all that is left for us is to explain to the JDBC driver that we provide and expect values in UTC.
Suppose we have
private static final Calendar UTC_CAL = Calendar.getInstance(TimeZone.getTimeZone(ZoneOffset.UTC));
then reading an Instant
from a database of column type TIMESTAMP
can be done like this:
Instant myInstant = resultSet.getTimestamp(colIdx, UTC_CAL).toInstant();
while writing can be done like this:
preparedStatement.setTimestamp(colIdx, Timestamp.from(myInstant), UTC_CAL);
It should be noted that the above methodology is "safe", meaning it will work consistently regardless of the database server's setting for default timezone or your own JVM's timezone setting.
ORM support
As you can imagine, those ORMs which indeed supports Instant
behind the scenes do exactly as above. Hibernate and Nucleus support Instant
, EclipseLink not yet.
Your entity class should simply look like this:
@Entity
public class MyEntity {
...
@Column(name="created_at")
private Instant createdAt;
// getters and setters
}
When using Hibernate, you can find many tales on the Internet of having to set hibernate.jdbc.time_zone
to UTC
. This is not necessary with the above, at least not with Hibernate 6. The reason is that Hibernate can see your intent (you specify Instant
, not any of the other JSR-310 types), so it knows it has to use a static UTC_CAL
when reading or writing the values from/to the database.
Should you be using Instant
in JPA code ?
As stated, Instant
is not currently in the JPA Spec but seems to be finally coming up. There are two reasons why I would gladly use it anyway:
- Hibernate is Spring's default. Yes, you can probably use something else, but I bet very few do. So using something Hibernate specific doesn't bother me too much.
- When
Instant
finally comes to the JPA Spec, I bet it will simply work just like Hibernate already do. So no change.
java.time.Instant
to represent a moment, such as a SQL-standardTIMESTAMP WITH TIME ZONE
column. And yes,Instant
replaces bothjava.util.Date
&java.sql.Timestamp
classes. (b) No, “not depending by timezone” is incorrect. There is a time zone involved on anInstant
: UTC. As for the database, that behavior varies. For example, in Postgres, the input to aTIMESTAMP WITH TIME ZONE
column uses any provided zone/offset info to adjust into UTC for storage, then discarding that provided zone/offset info after the adjustment is complete. – Liewjava.time.LocalDateTime
. That class does not represent a moment as it purposely lacks any concept of zone/offset. That class is indefinite, and represents a rough idea about potential moments along a range of about 26-27 hours (the range of time zones around the globe). – Liew