Mysql
 sql >> Database >  >> RDS >> Mysql

Come memorizzare la data e l'ora in UTC in un database utilizzando EclipseLink e Joda-Time?

Date è indipendente dal fuso orario in Java. Prende sempre UTC (per impostazione predefinita e sempre) ma quando Date / Timestamp viene passato attraverso un driver JDBC a un database, interpreta la data / ora in base al fuso orario della JVM che a sua volta viene impostato sul fuso orario del sistema (il fuso orario nativo del sistema operativo).

Pertanto, a meno che il driver MySQL JDBC non sia stato esplicitamente forzato a utilizzare la zona UTC o la JVM stessa non sia impostata per utilizzare quella zona, non memorizzerebbe Date / Timestamp nel database di destinazione utilizzando UTC anche se MySQL stesso doveva essere configurato per utilizzare UTC utilizzando default_time_zone='+00:00' in my.ini o my.cnf nel [mysqld] sezione. Alcuni database come Oracle possono supportare il timestamp con fuso orario e potrebbe essere un'eccezione con cui non ho familiarità (non testato poiché al momento non dispongo di quell'ambiente).

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

Questo può essere ulteriormente chiarito controllando l'invocazione di setTimestampInternal() metodo di implementazione del driver MySQL JDBC.

Vedi i seguenti due chiamate a setTimestampInternal() metodo dalle due versioni sovraccaricate di setTimestamp() metodo.

Quando nessun Calendar l'istanza è specificata con PreparedStatement#setTimestamp() metodo, verrà utilizzato il fuso orario predefinito (this.connection.getDefaultTimeZone() ).

Durante l'utilizzo di un pool di connessioni in server applicazioni/contenitori servlet supportati da una connessione/accesso JNDI o operazioni su origini dati come,

il driver MySQL JDBC deve essere forzato per utilizzare il fuso orario di nostro interesse (UTC) desiderato, i due parametri seguenti devono essere forniti tramite la stringa di query dell'URL di connessione.

Non ho familiarità con la cronologia dei driver JDBC MySQL, ma in versioni relativamente precedenti dei driver MySQL, questo parametro useLegacyDatetimeCode potrebbe non essere necessario. Pertanto, in tal caso, potrebbe essere necessario adeguarsi.

Nel caso di server delle applicazioni, ad esempio GlassFish, possono essere impostati durante la creazione di un dominio JDBC insieme a un pool di connessioni JDBC all'interno del server stesso insieme ad altre proprietà configurabili utilizzando lo strumento della GUI web di amministrazione o in domain.xml direttamente. domain.xml è simile al seguente (usando un'origine dati 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>

In caso di WildFly, possono essere configurati in standalone-xx.yy.xml utilizzando i comandi CLI o utilizzando lo strumento GUI Web di amministrazione (utilizzando un'origine dati 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>

La stessa cosa è applicabile alle origini dati non XA. In questo caso possono essere aggiunti direttamente all'URL di connessione stesso.

Tutte queste proprietà menzionate verranno impostate sulla classe menzionata disponibile nel driver JDBC, ovvero com.mysql.jdbc.jdbc2.optional.MysqlXADataSource utilizzando i rispettivi metodi setter in questa classe in entrambi i casi.

In caso di utilizzo diretto dell'API JDBC principale o del pool di connessioni in Tomcat, ad esempio, è possibile impostarli direttamente sull'URL di connessione (in 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>

Aggiuntivo :

Se il server di database di destinazione è in esecuzione in una zona sensibile all'ora legale e l'ora legale (DST) non è disattivata, causeranno problemi. È meglio configurare il server del database anche per utilizzare un fuso orario standard che non è influenzato dall'ora legale come UTC o GMT. L'UTC è solitamente preferito al GMT, ma entrambi sono simili in questo senso. Citando direttamente da questo link .

A proposito, ho abbandonato il convertitore proprietario di EclipseLink, da JPA 2.1 fornisce il proprio convertitore standard che può essere trasferito a un diverso provider JPA come e quando richiesto senza piccole o nessuna modifica. Ora appare come il seguente in cui java.util.Date è stato anche sostituito da 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);
    }
}

È quindi esclusiva responsabilità dei client dell'applicazione associati (servlet / JSP / JSF / client desktop remoti ecc.) convertire la data / ora in base al fuso orario dell'utente appropriato durante la visualizzazione o la presentazione di data / ora agli utenti finali che non è trattato in questa risposta per brevità ed è fuori tema in base alla natura della domanda attuale.

Anche questi controlli nulli nel convertitore non sono necessari in quanto è anche responsabilità esclusiva dei client dell'applicazione associati, a meno che alcuni campi non siano facoltativi.

Adesso va tutto bene. Eventuali altri suggerimenti/raccomandazioni sono ben accetti. Le critiche a qualcuno dei miei ignoranti sono ben accette.