Mysql
 sql >> Base de données >  >> RDS >> Mysql

Comment stocker la date et l'heure en UTC dans une base de données à l'aide d'EclipseLink et de Joda-Time ?

Date est indépendant du fuseau horaire en Java. Il prend toujours UTC (par défaut et toujours) mais quand Date / Timestamp est transmis via un pilote JDBC à une base de données, il interprète la date/l'heure en fonction du fuseau horaire de la JVM, qui est par défaut le fuseau horaire du système (le fuseau du système d'exploitation natif).

Par conséquent, à moins que le pilote MySQL JDBC n'ait été explicitement forcé d'utiliser la zone UTC ou que la JVM elle-même ne soit configurée pour utiliser cette zone, elle ne stockerait pas Date / Timestamp dans la base de données cible en utilisant UTC même si MySQL lui-même devait être configuré pour utiliser UTC en utilisant default_time_zone='+00:00' dans my.ini ou my.cnf dans le [mysqld] section. Certaines bases de données comme Oracle peuvent prendre en charge l'horodatage avec fuseau horaire et il peut s'agir d'une exception que je ne connais pas (non testée car je n'ai pas cet environnement pour le moment).

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

Cela peut être clarifié en vérifiant l'invocation de setTimestampInternal() méthode d'implémentation du pilote MySQL JDBC.

Voir les deux appels à setTimestampInternal() méthode à partir des deux versions surchargées de setTimestamp() méthode.

Lorsqu'il n'y a pas de Calendar l'instance est spécifiée avec le PreparedStatement#setTimestamp() , le fuseau horaire par défaut sera utilisé (this.connection.getDefaultTimeZone() ).

Lors de l'utilisation d'un pool de connexions dans des serveurs d'applications / conteneurs de servlets soutenus par une connexion / JNDI accédant ou opérant sur des sources de données telles que,

le pilote MySQL JDBC doit être forcé d'utiliser le fuseau horaire souhaité de notre intérêt (UTC), les deux paramètres suivants doivent être fournis via la chaîne de requête de l'URL de connexion.

Je ne connais pas l'historique des pilotes MySQL JDBC mais dans les versions relativement anciennes des pilotes MySQL, ce paramètre useLegacyDatetimeCode peut ne pas être nécessaire. Ainsi, on peut avoir besoin de s'adapter dans ce cas.

Dans le cas des serveurs d'applications, GlassFish, par exemple, ils peuvent être définis lors de la création d'un domaine JDBC avec un pool de connexions JDBC à l'intérieur du serveur lui-même ainsi que d'autres propriétés configurables à l'aide de l'outil d'interface graphique Web d'administration ou dans domain.xml directement. domain.xml ressemble à ce qui suit (en utilisant une source de données 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>

Dans le cas de WildFly, ils peuvent être configurés dans standalone-xx.yy.xml à l'aide de commandes CLI ou à l'aide de l'outil d'interface graphique Web d'administration (à l'aide d'une source de données 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 même chose s'applique aux sources de données non-XA. Ils peuvent être directement ajoutés à l'URL de connexion elle-même dans ce cas.

Toutes ces propriétés mentionnées seront définies sur la classe mentionnée disponible dans le pilote JDBC, à savoir com.mysql.jdbc.jdbc2.optional.MysqlXADataSource en utilisant leurs méthodes de définition respectives dans cette classe dans les deux cas.

En cas d'utilisation directe de l'API JDBC principale ou de regroupement de connexions dans Tomcat, par exemple, ils peuvent être définis directement sur l'URL de connexion (dans 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>

Supplémentaire :

Si le serveur de base de données cible s'exécute dans une zone sensible à l'heure d'été et que l'heure d'été (DST) n'est pas désactivée, cela causera des problèmes. Mieux vaut configurer le serveur de base de données également pour utiliser un fuseau horaire standard qui n'est pas affecté par l'heure d'été comme UTC ou GMT. UTC est généralement préféré à GMT, mais les deux sont similaires à cet égard. Citant directement de ce lien .

Au fait, j'ai abandonné le convertisseur propriétaire d'EclipseLink, depuis JPA 2.1 fournit son propre convertisseur standard qui peut être porté vers un autre fournisseur JPA au fur et à mesure des besoins sans aucune ou peu de modifications. Il ressemble maintenant à ce qui suit dans lequel java.util.Date a également été remplacé par 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);
    }
}

Il est alors de la seule responsabilité du ou des clients d'application associés (servlets / JSP / JSF / clients de bureau à distance, etc.) de convertir la date / l'heure en fonction du fuseau horaire d'un utilisateur approprié tout en affichant ou en présentant la date / l'heure aux utilisateurs finaux qui n'est pas couvert dans cette réponse par souci de brièveté et est hors sujet en fonction de la nature de la question actuelle.

Ces vérifications nulles dans le convertisseur ne sont pas non plus nécessaires car elles relèvent également de la seule responsabilité du ou des clients d'application associés, sauf si certains champs sont facultatifs.

Tout va bien maintenant. Toutes autres suggestions/recommandations sont les bienvenues. Les critiques envers l'un de mes ignorants sont les bienvenues.