jpa-spec
  1. jpa-spec
  2. JPA_SPEC-63

JPA next should support Java 8 Date and Time types

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: 2.2
    • Labels:
      None

      Description

      Currently, JPA temporal fields are supported for the following data types: java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, and java.sql.Timestamp. java.sql.Date properties are always mapped to the JDBC methods getDate and setDate, and it is an error to specify the @javax.persistence.Temporal annotation for these types. The same is true of Time (mapped to getTIme and setTime) and Timestamp (mapped to getTimestamp and setTimestamp). Properties of type java.util.Date and Calendar must be annotated with @Temporal to specify the javax.persistence.TemporalType enum indicating which JDBC methods should be used for those properties.

      Some vendors support other temporal types, such as Joda Time, but this is non-standard and should probably remain so since Joda Time isn't guaranteed to stay around (and, in fact, is likely to start ramping down with the release of Java 8).

      JSR-310 as part of Java 8 specifies a new Date & Time API in the java.time package and sub-packages that supplants java.util.Date, Calendar, java.sql.Date, Time, Timestamp, and Joda Time. It is based off of the Joda Time API, but with enhancements and certain redesigns that the Joda Time founder/creator has said makes it superior to Joda Time.

      JPA's existing rules for the currently-supported temporal types should remain largely unchanged. However, the specification should be added to in order to specify support for JSR-310. These are the proposed new rules I believe should be present in the JPA.next specification:

      • Properties of type java.time.Duration are treated as @javax.persistence.Basic fields. They automatically map to;
        • DURATION fields if the database vendor supports duration types;
        • DECIMAL-type fields storing the seconds before the decimal point and the nanoseconds after the decimal point;
        • INTEGER-type fields storing the seconds; and,
        • CHAR/VARCHAR-type fields storing the value in its ISO-8601 format (Duration#toString() and Duration#parse(CharSequence)).
      • Properties of type java.time.Period are treated as @Basic fields. They automatically map to:
        • PERIOD or DURATION fields if the database vendor supports period or duration types;
        • DECIMAL-type fields storing the seconds before the decimal point and the nanoseconds after the decimal point;
        • INTEGER-type fields storing the seconds; and,
        • CHAR/VARCHAR-type fields storing the value in its ISO-8601 format (Period#toString() and Period#parse(CharSequence)).
      • Properties of type java.time.Year are treated as @Basic fields. They automatically map to:
        • YEAR fields if the database vendor supports year types; and,
        • INTEGER/CHAR/VARCHAR-type fields storing the literal number/string value.
      • Properties of enum type java.time.Month are treated as special-case enum fields.
        • If the database field is a MONTH field (assuming the database vendor supports such types), it maps to this field.
        • If @javax.persistence.Enumerated is not present and the database field is an INTEGER-type field, it maps as the month number (NOT the ordinal) using int Month#getValue() and Month Month#of(int).
        • Otherwise, it falls back to standard enum mapping rules.
        • It is an error to annotate a Month property with @Enumerated if the database field is of type MONTH.
      • Properties of enum type java.time.DayOfWeek are treated as special-case enum fields.
        • If the database field is a DAY_OF_WEEK field (assuming the database vendor supports such types), it maps to this field.
        • If @Enumerated is not present and the database field is an INTEGER-type field, it maps as the day number (NOT the ordinal) using int DayOfWeek#getValue() and DayOfWeek DayOfWeek#of(int).
        • Otherwise, it falls back to standard enum mapping rules.
        • It is an error to annotate a DayOfWeek property with @Enumerated if the database field is of type DAY_OF_WEEK.
      • Properties of type java.time.YearMonth are treated as @Basic fields.
        • By default, they automatically map to:
          • YEARMONTH fields if the database vendor supports year-month types;
          • DATE and DATETIME fields storing the lowest day number that the database vendor supports and zero-time if applicable; and,
          • CHAR/VARCHAR-type fields storing the value in its ISO-8601 format (YearMonth#toString() and YearMonth#parse(CharSequence)).
        • The new @javax.persistence.YearMonthColumns annotation can map a YearMonth property to two database fields. A property annotated with this overrides the default mapping behavior. It is an error to mark properties of any other type with this annotation. The required @javax.persistence.Column-typed year attribute specifies the column that the year is stored in while the required @Column-typed month attribute specifies the column that the month is stored in. The year column follows the same default mapping rules as for Year types and the month column as for the Month enum. It is an error to specify @Column and @YearMonthColumns on the same property.
      • Properties of type java.time.MonthDay are treated as @Basic fields.
        • By default they automatically map to:
          • MONTHDAY fields if the database vendor supports month-day types;
          • DATE and DATETIME fields storing the lowest year number that the database vendor supports and zero-time if applicable; and,
          • CHAR/VARCHAR-type fields storing the value in its ISO-8601 format (MonthDay#toString() and MonthDay#parse(CharSequence).
        • The new @javax.persistence.MonthDayColumns annotation can map a MonthDay property to two database fields. A property annotated with this overrides the default mapping behavior. It is an error to mark properties of any other type with this annotation. The required @Column-typed month attribute specifies the column that the month is stored in while the required @Column-typed day attribute specifies the column that the day is stored in. The month column follows the same default mapping rules as for the Month enum and the day column automatically maps to INTEGER/CHAR/VARCHAR-type fields. It is an error to specify @Column and @MonthDayColumns on the same property.
      • Properties of type java.time.ZoneId are treated as @Basic fields. They automatically map to:
        • TIMEZONE fields if the database vendor supports time zone types (they never map to offset fields); and,
        • CHAR/VARCHAR-type fields storing the value in its ISO-8601 format (ZoneId#toString() and ZoneId#of(String)).
      • Properties of type java.time.ZoneOffset are treated as @Basic fields. They automatically map to:
        • OFFSET fields if the database vendor supports offset types (they never map to time zone fields); and,
        • CHAR/VARCHAR-type fields storing the value in its ISO-8601 format (ZoneOffset#toString() and ZoneOffset#of(String)).
      • Properties of types java.time.Instant, java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime, java.time.OffsetTime, java.time.OffsetDateTime, and java.time.ZonedDateTime are treated as temporal @Basic types that are mapped using the following rules:
        • LocalDate always maps as a date-only value. It is an error to mark a LocalDate property with the @Temporal annotation.
        • LocalTime and OffsetTime always map as time-only values. It is an error to mark a LocalTime or OffsetTime property with the @Temporal annotation.
        • Instant, LocalDateTime, OffsetDateTime, and ZonedDateTime map as timestamp values by default. You may mark a property of one of these types with @Temporal to specify a different strategy for persisting that property.
        • The new @javax.persistence.TemporalIncludeTimeZone annotation indicates that the offset in the OffsetTime or OffsetDateTime property or the time zone in the ZonedDateTime or Calendar property will be persisted with the value. Otherwise (if this is absent) the value is converted to the database server offset or time zone for persistence.
        • The new @javax.persistence.TemporalTimeZoneColumn(@Column value) annotation indicates a different column in which the time zone value is stored. It implies @TemporalIncludeTimeZone. It is required if @TemporalIncludeTimeZone is present but the database vendor does not support storing the time zone with the field data type. It is also required if @TemporalIncludeTimeZone is present but the JDBC driver in use is less than version 4.2 (a JDBC 4.2 driver is necessary to persist time zones and offsets with time/date-time values). The persistence rules for this column are the same as for ZoneId and ZoneOffset properties.
        • Properties of these types invoke the following special handling for JDBC driver versions before and after 4.2.
          • A JDBC driver is considered version 4.2 or better if java.sql.Driver#getMajorVersion() returns a number greater than 4, or it returns 4 and Driver#getMinorVersion() returns a number greater than 1. In the absence of a testable Driver instance, implementations may assume that the driver version is less than 4.2 if PreparedStatement#setObject(int, Object, SQLType) throws a SQLFeatureNotSupportedException.
          • If the JDBC driver is version 4.2 or newer, these seven types are persisted and retrieved as follows:
            • They are persisted with PreparedStatement#setObject(int, Object, SQLType) and retrieved with ResultSet#getObject(int, Class<?>) or ResultSet#getObject(String, Class<?>).
            • Time-only properties or TemporalType.TIME properties use a java.sql.SQLType of java.sql.JDBCType.TIME in the absence of @TemporalIncludeTimeZone or presence of @TemporalTimeZoneColumn. They use JDBCType.TIME_WITH_TIMEZONE in the presence of @TemporalIncludeTimeZone and absence of @TemporalTimeZoneColumn.
            • Date-only properties or TemporalType.DATE properties use a SQLType of JDBCType.DATE.
            • Date-and-time properties use a SQLType of JDBCType.TIMESTAMP in the absence of @TemporalIncludeTimeZone or presence of @TemporalTimeZoneColumn. They use JDBCType.TIMESTAMP_WITH_TIMEZONE in the presence of @TemporalIncludeTimeZone and absence of @TemporalTimeZoneColumn.
          • If the JDBC driver is version 4.1 or older, these seven types are persisted and retrieved as follows:
            • Time-only properties or TemporalType.TIME properties are automatically converted to and from Time and use the traditional setTime and getTime methods.
            • Date-only properties or TemporalType.DATE properties are automatically converted to and from java.sql.Date and use the traditional setDate and getDate methods.
            • Date-and-time properties are automatically converted to and from Timestamp and use the traditional setTimestamp and getTimestamp methods.
            • @TemporalTimeZoneColumn is required if @TemporalIncludeTimeZone is present.

        Activity

        Hide
        richardwalker added a comment -

        Please consider including support for a "pure UTC" data flow at least in the following specific sense:

        • Java default time zone can be set to anything (to be specific, not UTC)
        • Some of the attributes of an entity class are specified as LocalDateTime, where the values are considered to be in UTC
        • Schema generation makes database columns of a "TIMESTAMP-like" type (actually, for MySQL I would override and use DATETIME in order to get a wider range of values)
        • Values are sent back/forth to the database "as is"
        • At no point do the values get converted to/from the default time zone (or any other time zone), so as to avoid the issue with confusion of values during daylight saving changeover

        I don't see how to achieve this with JPA as is.

        Until yesterday I have been using an AttributeConverter like this, based on code that can be found on several blogs:

        @Converter(autoApply = true)
        public class LocalDateTimeConverter implements
            AttributeConverter<LocalDateTime, Timestamp> {
        
            @Override
            public Timestamp convertToDatabaseColumn(
                    final LocalDateTime attribute) {
                if (attribute == null) {
                    return null;
                }
                return Timestamp.valueOf(attribute);
            }
        
            @Override
            public LocalDateTime convertToEntityAttribute(
                    final Timestamp dbData) {
                if (dbData == null) {
                    return null;
                }
                return dbData.toLocalDateTime();
            }
        }
        

        I now realise that although this seems to work, it is wrong, because both Timestamp.valueOf() and Timestamp.toLocalDateTime() interpret the value as though it were in the default time zone, so the code breaks during daylight saving changeover when there's an hour of time that doesn't "exist" in the default time zone.

        For example, consider the test value 2016-10-02 02:02:01, which is a perfectly valid time when interpreted as UTC, but which does not exist in my local time zone (Australia/Sydney). If I (with default time zone Australia/Sydney) use the above converter to persist the LocalDateTime value LocalDateTime.of(2016, 10, 2, 2, 1, 0), the value is sent to the database, and stored, as '2016-10-02 03:01:00.0', and when it is read back into a LocalDateTime, its value is now one hour out! (I've done exactly this test to confirm.)

        My converter is now like this:

        @Converter(autoApply = true)
        public class LocalDateTimeConverter implements
            AttributeConverter<LocalDateTime, Timestamp> {
        
            @Override
            public Timestamp convertToDatabaseColumn(
                    final LocalDateTime attribute) {
                if (attribute == null) {
                    return null;
                }
                return Timestamp.from(attribute.toInstant(ZoneOffset.UTC));
            }
        
            @Override
            public LocalDateTime convertToEntityAttribute(
                    final Timestamp dbData) {
                if (dbData == null) {
                    return null;
                }
                return LocalDateTime.ofInstant(dbData.toInstant(), ZoneOffset.UTC);
            }
        }
        

        but this doesn't work as is; it requires the recently-added Hibernate-specific configuration (explained at http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/) to specify using UTC when sending Timestamp values through JDBC:

        hibernate.jdbc.time_zone=UTC
        

        So, although LocalDateTime values are "in theory" separate from concerns about time zones, it seems that when it comes to persisting them as Timestamp values, the values must be interpreted as being in some time zone.

        So, please consider providing some way of specifying that LocalDateTime values are persisted in a "transparent" way (avoiding all internal time zone conversions along the way), or, if that is not possible, specifying the time zone in which LocalDateTime values are to be interpreted.

        Show
        richardwalker added a comment - Please consider including support for a "pure UTC" data flow at least in the following specific sense: Java default time zone can be set to anything (to be specific, not UTC) Some of the attributes of an entity class are specified as LocalDateTime , where the values are considered to be in UTC Schema generation makes database columns of a "TIMESTAMP-like" type (actually, for MySQL I would override and use DATETIME in order to get a wider range of values) Values are sent back/forth to the database "as is" At no point do the values get converted to/from the default time zone (or any other time zone), so as to avoid the issue with confusion of values during daylight saving changeover I don't see how to achieve this with JPA as is. Until yesterday I have been using an AttributeConverter like this, based on code that can be found on several blogs: @Converter(autoApply = true ) public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Timestamp> { @Override public Timestamp convertToDatabaseColumn( final LocalDateTime attribute) { if (attribute == null ) { return null ; } return Timestamp.valueOf(attribute); } @Override public LocalDateTime convertToEntityAttribute( final Timestamp dbData) { if (dbData == null ) { return null ; } return dbData.toLocalDateTime(); } } I now realise that although this seems to work, it is wrong, because both Timestamp.valueOf() and Timestamp.toLocalDateTime() interpret the value as though it were in the default time zone, so the code breaks during daylight saving changeover when there's an hour of time that doesn't "exist" in the default time zone. For example, consider the test value 2016-10-02 02:02:01, which is a perfectly valid time when interpreted as UTC, but which does not exist in my local time zone (Australia/Sydney). If I (with default time zone Australia/Sydney) use the above converter to persist the LocalDateTime value LocalDateTime.of(2016, 10, 2, 2, 1, 0) , the value is sent to the database, and stored, as '2016-10-02 03:01:00.0', and when it is read back into a LocalDateTime , its value is now one hour out! (I've done exactly this test to confirm.) My converter is now like this: @Converter(autoApply = true ) public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Timestamp> { @Override public Timestamp convertToDatabaseColumn( final LocalDateTime attribute) { if (attribute == null ) { return null ; } return Timestamp.from(attribute.toInstant(ZoneOffset.UTC)); } @Override public LocalDateTime convertToEntityAttribute( final Timestamp dbData) { if (dbData == null ) { return null ; } return LocalDateTime.ofInstant(dbData.toInstant(), ZoneOffset.UTC); } } but this doesn't work as is; it requires the recently-added Hibernate-specific configuration (explained at http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/ ) to specify using UTC when sending Timestamp values through JDBC: hibernate.jdbc.time_zone=UTC So, although LocalDateTime values are "in theory" separate from concerns about time zones, it seems that when it comes to persisting them as Timestamp values, the values must be interpreted as being in some time zone. So, please consider providing some way of specifying that LocalDateTime values are persisted in a "transparent" way (avoiding all internal time zone conversions along the way), or, if that is not possible, specifying the time zone in which LocalDateTime values are to be interpreted.
        Hide
        neilstockton added a comment -

        The description for Period is wrong in that it implies that this type stores seconds+nanos, whilst it actually is YEAR+MONTH+DAY ("A date-based amount of time in the ISO-8601 calendar system"). Consequently it is not possible to store it in DECIMAL, and likely not INTEGER also.

        Show
        neilstockton added a comment - The description for Period is wrong in that it implies that this type stores seconds+nanos, whilst it actually is YEAR+MONTH+DAY ("A date-based amount of time in the ISO-8601 calendar system"). Consequently it is not possible to store it in DECIMAL, and likely not INTEGER also.
        Hide
        Lukas Jungmann added a comment -

        will try to address this in 2.2

        Show
        Lukas Jungmann added a comment - will try to address this in 2.2
        Hide
        braghest added a comment -

        there actually is no real need for it anymore, thanks to the acceptance of the adapter API proposal

        I'm not sure. Firstly currently the RI explodes with a ClassCastException when trying to write an attribute converter mapping a java.util.Calendar database value. Secondly the spec would have to say that when mapping a java.util.Calendar the time zone of the value returned from the database is the time zone of the value on the database instead of the time zone of the Java virtual machine (like java.sql.Date). JDBC only allows to access the time zone of a value using the Java 8 Date and Time API.
        If you currently want to access the timezone of a database value you need to use vendor specific extensions.

        Show
        braghest added a comment - there actually is no real need for it anymore, thanks to the acceptance of the adapter API proposal I'm not sure. Firstly currently the RI explodes with a ClassCastException when trying to write an attribute converter mapping a java.util.Calendar database value. Secondly the spec would have to say that when mapping a java.util.Calendar the time zone of the value returned from the database is the time zone of the value on the database instead of the time zone of the Java virtual machine (like java.sql.Date ). JDBC only allows to access the time zone of a value using the Java 8 Date and Time API. If you currently want to access the timezone of a database value you need to use vendor specific extensions.
        Hide
        mkarg added a comment -

        While that is absolutely correct, the technical answer is a bit more complex: What is the final predicate that makes a data type eligible for inclusion in the set of mandatory type mappings?

        One could say, that predicate is "being essential" or "being of common use", but who defines what "essential" or "common use" is? See, for some applications, support for java.awt.Image and java.net.URL might be much more essential than support for LocalDate or ZonedDateTime. On the other hand, other applications might be full of LocalDate but never uses Instant. So where exactly to make the cut? This becomes particularly complex when looking at the sheer amount of types found in the JRE, and it is obvious there has to be a cut somewhere. Even JavaFX, which is bundled with the JRE, does not support Instant still in v8, so why should JPA? And looking at the current progress of Project Jigsaw, possibly the qualifying predicate might simply be answered by "all types in a particular jigsaw module"?

        Anyways, it is not up to me to decide. I do support your request, and would love to see support for rather all Java Time API times, particularly for Instant and Duration, and your request has prominent supporters like for example Java Champion Arun Gupa as I learned recently. But I doubt the final answer will be as simple an satisfying as we would love to have it.

        Maybe it would be better to simply set up another JSR, like "Common Data Type Conversions for the Java Platform", which provides much more mappings than just date and time, but also would not be bound to JPA but also could be used by JAXB, JAX-RS, and possibly more API that deal which the problem of transforming "<A> to <B>"? Having such a vehicle would really reduce boilerplate a lot.

        Show
        mkarg added a comment - While that is absolutely correct, the technical answer is a bit more complex: What is the final predicate that makes a data type eligible for inclusion in the set of mandatory type mappings? One could say, that predicate is "being essential" or "being of common use", but who defines what "essential" or "common use" is? See, for some applications, support for java.awt.Image and java.net.URL might be much more essential than support for LocalDate or ZonedDateTime. On the other hand, other applications might be full of LocalDate but never uses Instant. So where exactly to make the cut? This becomes particularly complex when looking at the sheer amount of types found in the JRE, and it is obvious there has to be a cut somewhere. Even JavaFX, which is bundled with the JRE, does not support Instant still in v8, so why should JPA? And looking at the current progress of Project Jigsaw, possibly the qualifying predicate might simply be answered by "all types in a particular jigsaw module"? Anyways, it is not up to me to decide. I do support your request, and would love to see support for rather all Java Time API times, particularly for Instant and Duration, and your request has prominent supporters like for example Java Champion Arun Gupa as I learned recently. But I doubt the final answer will be as simple an satisfying as we would love to have it. Maybe it would be better to simply set up another JSR, like "Common Data Type Conversions for the Java Platform", which provides much more mappings than just date and time, but also would not be bound to JPA but also could be used by JAXB, JAX-RS, and possibly more API that deal which the problem of transforming "<A> to <B>"? Having such a vehicle would really reduce boilerplate a lot.

          People

          • Assignee:
            Unassigned
            Reporter:
            Nick Williams
          • Votes:
            56 Vote for this issue
            Watchers:
            41 Start watching this issue

            Dates

            • Created:
              Updated: