How to store date-time in UTC into a database using EclipseLink and Joda-Time?

后端 未结 2 1924
遇见更好的自我
遇见更好的自我 2021-01-15 01:15

I have been fumbling with the following EclipseLink Joda-Time converter for a long time to store date-time in UTC into MySQL database with no success at all.



        
2条回答
  •  情话喂你
    2021-01-15 01:20

    Date is timezone agnostic in Java. It always takes UTC (by default and always) but when Date / Timestamp is passed through a JDBC driver to a database, it interprets date / time according to the JVM time zone which defaults to the system time zone in turn (the native operating system zone).

    Therefore, unless the MySQL JDBC driver was explicitly forced to use the UTC zone or JVM itself is set to use that zone, it would not store Date / Timestamp into the target database using UTC even though MySQL itself were to be configured to use UTC using default_time_zone='+00:00' in my.ini or my.cnf in the [mysqld] section. Some databases like Oracle may support time stamp with time zone and it may be an exception which I am not familiar with (untested as I do not have that environment at present).

    void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException

    Sets the designated parameter to the given java.sql.Timestamp value, using the given Calendar object. The driver uses the Calendar object to construct an SQL TIMESTAMP value, which the driver then sends to the database. With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone. If no Calendar object is specified, the driver uses the default timezone, which is that of the virtual machine running the application.

    This can further be clarified by checking the invocation of setTimestampInternal() method of the MySQL JDBC driver implementation.

    See the following two calls to the setTimestampInternal() method from within the two overloaded versions of the setTimestamp() method.

    /**
     * Set a parameter to a java.sql.Timestamp value. The driver converts this
     * to a SQL TIMESTAMP value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1...
     * @param x the parameter value
     *
     * @throws SQLException if a database access error occurs
     */
    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
        setTimestampInternal(parameterIndex, x, this.connection.getDefaultTimeZone());
    }
    
    /**
     * Set a parameter to a java.sql.Timestamp value. The driver converts this
     * to a SQL TIMESTAMP value when it sends it to the database.
     *
     * @param parameterIndex the first parameter is 1, the second is 2, ...
     * @param x the parameter value
     * @param cal the calendar specifying the timezone to use
     *
     * @throws SQLException if a database-access error occurs.
     */
    public void setTimestamp(int parameterIndex, java.sql.Timestamp x,Calendar cal) throws SQLException {
        setTimestampInternal(parameterIndex, x, cal.getTimeZone());
    }
    

    When no Calendar instance is specified with the PreparedStatement#setTimestamp() method, the default time zone will be used (this.connection.getDefaultTimeZone()).


    While using a connection pool in application servers / Servlet containers backed by a connection / JNDI accessing or operating upon datasources like,

    • com.mysql.jdbc.jdbc2.optional.MysqlXADataSource (xa)
    • com.mysql.jdbc.jdbc2.optional.MysqlDataSource (non-xa)

    the MySQL JDBC driver needs to be forced to use the desired time zone of our interest (UTC), the following two parameters need to be supplied through the query string of the connection URL.

    • useLegacyDatetimeCode=false
    • serverTimezone=UTC

    I am not familiar with the history of MySQL JDBC drivers but in relatively older versions of MySQL drivers, this parameter useLegacyDatetimeCode may not be needed. Thus, one may require to adjust oneself in that case.

    In case of application servers, GlassFish, for example, they can be set while creating a JDBC realm along with a JDBC connection pool inside the server itself along with other configurable properties either using the admin web GUI tool or in domain.xml directly. domain.xml looks like the following (using an XA datasource).

    
    
      
      
      
      
      
      
      
      
      
      
      
      
    
    
    
    
    

    In case of WildFly, they can be configured in standalone-xx.yy.xml using CLI commands or using the admin web GUI tool (using an XA datasource).

    
    
        database_name
        localhost
        3306
        true
        UTF-8
        
        false
        UTC
    
        com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
        mysql
        TRANSACTION_READ_COMMITTED
    
        
            5
            15
        
    
        
            root
            password
        
    
        
            
            true
            
        
    
        
            true
        
    
    
    
        
            com.mysql.jdbc.Driver
        
    
    

    The same thing is applicable to non-XA datasources. They can directly be appended to the connection URL itself in that case.

    These all mentioned properties will be set to the mentioned class available in the JDBC driver namely com.mysql.jdbc.jdbc2.optional.MysqlXADataSource using their respective setter methods in this class in both the cases.

    In case of using the core JDBC API directly, or connection pooling in Tomcat, for example, they can be set directly to the connection URL (in context.xml)

    
        
    
    

    Additional :

    If the target database server is running on a DST-sensible zone and Daylight Saving time (DST) is not turned off, it will cause problems. Better configure the database server also to use a standard time zone which is not affected by DST like UTC or GMT. UTC is usually preferred over GMT but both are similar in this regard. Quoting directly from this link.

    If you really prefer to use a local timezone, I recommend at least turning off Daylight Saving Time, because having ambiguous dates in your database can be a real nightmare.

    For example, if you are building a telephony service and you are using Daylight Saving Time on your database server then you are asking for trouble: there will be no way to tell whether a customer who called from "2008-10-26 02:30:00" to "2008-10-26 02:35:00" actually called for 5 minutes or for 1 hour and 5 minutes (supposing Daylight Saving occurred on Oct. 26th at 3am)!

    By the way, I dropped the proprietary converter of EclipseLink, since JPA 2.1 provides its own standard converter which can be ported to a different JPA provider as and when required without a little or no modifications at all. It now looks like the following in which java.util.Date was also replaced by java.sql.Timestamp.

    import java.sql.Timestamp;
    import javax.persistence.AttributeConverter;
    import javax.persistence.Converter;
    import org.joda.time.DateTime;
    import org.joda.time.DateTimeZone;
    
    @Converter(autoApply = true)
    public final class JodaDateTimeConverter implements AttributeConverter {
    
        @Override
        public Timestamp convertToDatabaseColumn(DateTime dateTime) {
            return dateTime == null ? null : new Timestamp(dateTime.withZone(DateTimeZone.UTC).getMillis());
        }
    
        @Override
        public DateTime convertToEntityAttribute(Timestamp timestamp) {
            return timestamp == null ? null : new DateTime(timestamp, DateTimeZone.UTC);
        }
    }
    

    It is then solely the responsibility of the associated application client(s) (Servlets / JSP / JSF / remote desktop clients etc) to convert date / time according to an appropriate user's time zone while displaying or presenting date / time to end-users which is not covered in this answer for brevity and is off-topic based on the nature of the current question.

    Those null checks in the converter are also not needed as it is also solely the responsibility of the associated application client(s) unless some fields are optional.

    Everything goes fine now. Any other suggestions / recommendations are welcome. Criticism to any of ignorant of mine is most welcome.

提交回复
热议问题