sql >> Base de Datos >  >> RDS >> Mysql

¿Cómo almacenar fecha y hora en UTC en una base de datos usando EclipseLink y Joda-Time?

Date es independiente de la zona horaria en Java. Siempre toma UTC (por defecto y siempre) pero cuando Date / Timestamp se pasa a través de un controlador JDBC a una base de datos, interpreta la fecha/hora de acuerdo con la zona horaria de JVM, que por defecto es la zona horaria del sistema a su vez (la zona del sistema operativo nativo).

Por lo tanto, a menos que el controlador JDBC de MySQL se haya forzado explícitamente a usar la zona UTC o que la propia JVM esté configurada para usar esa zona, no almacenaría Date / Timestamp en la base de datos de destino usando UTC aunque MySQL mismo se configurara para usar UTC usando default_time_zone='+00:00' en my.ini o my.cnf en el [mysqld] sección. Algunas bases de datos como Oracle pueden admitir la marca de tiempo con la zona horaria y puede ser una excepción con la que no estoy familiarizado (no probado ya que no tengo ese entorno en este momento).

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

Esto se puede aclarar aún más comprobando la invocación de setTimestampInternal() método de implementación del controlador MySQL JDBC.

Consulte los siguientes dos llamadas a setTimestampInternal() desde dentro de las dos versiones sobrecargadas de setTimestamp() método.

Cuando no hay Calendar la instancia se especifica con PreparedStatement#setTimestamp() método, se utilizará la zona horaria predeterminada (this.connection.getDefaultTimeZone() ).

Al usar un grupo de conexiones en servidores de aplicaciones/contenedores de Servlet respaldados por una conexión/JNDI accediendo u operando sobre fuentes de datos como,

el controlador MySQL JDBC debe ser forzado a usar la zona horaria deseada de nuestro interés (UTC), los siguientes dos parámetros deben proporcionarse a través de la cadena de consulta de la URL de conexión.

No estoy familiarizado con la historia de los controladores MySQL JDBC, pero en versiones relativamente antiguas de controladores MySQL, este parámetro useLegacyDatetimeCode puede no ser necesario. Por lo tanto, uno puede necesitar ajustarse en ese caso.

En el caso de los servidores de aplicaciones, GlassFish, por ejemplo, se pueden configurar al crear un dominio JDBC junto con un conjunto de conexiones JDBC dentro del propio servidor, junto con otras propiedades configurables, ya sea mediante la herramienta GUI web de administración o en domain.xml directamente. domain.xml se parece a lo siguiente (usando una fuente de datos XA).

<jdbc-connection-pool datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"
                      name="jdbc_pool"
                      res-type="javax.sql.XADataSource">

  <property name="password" value="password"></property>
  <property name="databaseName" value="database_name"></property>
  <property name="serverName" value="localhost"></property>
  <property name="user" value="root"></property>
  <property name="portNumber" value="3306"></property>
  <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  <property name="characterEncoding" value="UTF-8"></property>
  <property name="useUnicode" value="true"></property>
  <property name="characterSetResults" value="UTF-8"></property>
  <!-- The following two of our interest -->
  <property name="serverTimezone" value="UTC"></property>
  <property name="useLegacyDatetimeCode" value="false"></property>
</jdbc-connection-pool>

<jdbc-resource pool-name="jdbc_pool" 
               description="description"
               jndi-name="jdbc/pool">
</jdbc-resource>

En el caso de WildFly, se pueden configurar en standalone-xx.yy.xml usando comandos CLI o usando la herramienta GUI web de administración (usando una fuente de datos XA).

<xa-datasource jndi-name="java:jboss/datasources/datasource_name"
               pool-name="pool_name"
               enabled="true"
               use-ccm="true">

    <xa-datasource-property name="DatabaseName">database_name</xa-datasource-property>
    <xa-datasource-property name="ServerName">localhost</xa-datasource-property>
    <xa-datasource-property name="PortNumber">3306</xa-datasource-property>
    <xa-datasource-property name="UseUnicode">true</xa-datasource-property>
    <xa-datasource-property name="CharacterEncoding">UTF-8</xa-datasource-property>
    <!-- The following two of our interest -->
    <xa-datasource-property name="UseLegacyDatetimeCode">false</xa-datasource-property>
    <xa-datasource-property name="ServerTimezone">UTC</xa-datasource-property>

    <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
    <driver>mysql</driver>
    <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>

    <xa-pool>
        <min-pool-size>5</min-pool-size>
        <max-pool-size>15</max-pool-size>
    </xa-pool>

    <security>
        <user-name>root</user-name>
        <password>password</password>
    </security>

    <validation>
        <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
        <background-validation>true</background-validation>
        <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/>
    </validation>

    <statement>
        <share-prepared-statements>true</share-prepared-statements>
    </statement>
</xa-datasource>

<drivers>
    <driver name="mysql" module="com.mysql">
        <driver-class>com.mysql.jdbc.Driver</driver-class>
    </driver>
</drivers>

Lo mismo se aplica a las fuentes de datos que no son XA. En ese caso, se pueden agregar directamente a la propia URL de conexión.

Todas estas propiedades mencionadas se establecerán en la clase mencionada disponible en el controlador JDBC, a saber, com.mysql.jdbc.jdbc2.optional.MysqlXADataSource utilizando sus respectivos métodos de establecimiento en esta clase en ambos casos.

En caso de utilizar directamente la API de JDBC principal o la agrupación de conexiones en Tomcat, por ejemplo, se pueden establecer directamente en la URL de conexión (en context.xml )

<Context antiJARLocking="true" path="/path">
    <Resource name="jdbc/pool" 
              auth="Container"
              type="javax.sql.DataSource"
              maxActive="100"
              maxIdle="30"
              maxWait="10000"
              username="root"
              password="password"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/database_name?useEncoding=true&amp;characterEncoding=UTF-8&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC"/>
</Context>

Adicional:

Si el servidor de la base de datos de destino se ejecuta en una zona sensible al horario de verano y el horario de verano (DST) no está desactivado, se producirán problemas. Configure mejor el servidor de la base de datos también para usar una zona horaria estándar que no se vea afectada por DST como UTC o GMT. Generalmente se prefiere UTC sobre GMT, pero ambos son similares en este sentido. Citando directamente de este enlace .

Por cierto, eliminé el convertidor propietario de EclipseLink, desde JPA 2.1 proporciona su propio convertidor estándar que se puede transferir a un proveedor de JPA diferente cuando sea necesario sin modificaciones mínimas o nulas. Ahora se parece a lo siguiente en el que java.util.Date también fue reemplazado por 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<DateTime, Timestamp> {

    @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);
    }
}

Entonces, es responsabilidad exclusiva de los clientes de la aplicación asociada (Servlets/JSP/JSF/clientes de escritorio remoto, etc.) convertir la fecha/hora de acuerdo con la zona horaria de un usuario apropiado mientras se muestra o presenta la fecha/hora a los usuarios finales que no está cubierto en esta respuesta por brevedad y está fuera de tema según la naturaleza de la pregunta actual.

Esas verificaciones nulas en el convertidor tampoco son necesarias, ya que también es responsabilidad exclusiva de los clientes de la aplicación asociada, a menos que algunos campos sean opcionales.

Todo va bien ahora. Cualquier otra sugerencia/recomendación es bienvenida. Las críticas a cualquiera de mis ignorantes son bienvenidas.