Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como armazenar data e hora em UTC em um banco de dados usando EclipseLink e Joda-Time?


Date é agnóstico de fuso horário em Java. Sempre leva UTC (por padrão e sempre), mas quando Date / Timestamp é passado através de um driver JDBC para um banco de dados, ele interpreta a data/hora de acordo com o fuso horário da JVM que, por sua vez, assume o fuso horário do sistema (a zona nativa do sistema operacional).

Portanto, a menos que o driver MySQL JDBC tenha sido explicitamente forçado a usar a zona UTC ou a própria JVM esteja configurada para usar essa zona, ele não armazenaria Date / Timestamp no banco de dados de destino usando UTC mesmo que o próprio MySQL fosse configurado para usar UTC usando default_time_zone='+00:00' em my.ini ou my.cnf no [mysqld] seção. Alguns bancos de dados, como o Oracle, podem suportar carimbo de hora com fuso horário e pode ser uma exceção com a qual não estou familiarizado (não testado, pois não tenho esse ambiente no momento).

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

Isso pode ser esclarecido verificando a invocação de setTimestampInternal() método da implementação do driver MySQL JDBC.

Veja os seguintes dois chamadas para o setTimestampInternal() método de dentro das duas versões sobrecarregadas do setTimestamp() método.

Quando não há Calendar instância é especificada com o PreparedStatement#setTimestamp() método, o fuso horário padrão será usado (this.connection.getDefaultTimeZone() ).

Ao usar um pool de conexão em servidores de aplicativos/contêineres de Servlet apoiados por uma conexão/JNDI acessando ou operando em fontes de dados como,

o driver MySQL JDBC precisa ser forçado a usar o fuso horário desejado de nosso interesse (UTC), os dois parâmetros a seguir precisam ser fornecidos através da string de consulta da URL de conexão.

Não estou familiarizado com o histórico dos drivers MySQL JDBC, mas em versões relativamente mais antigas dos drivers MySQL, este parâmetro useLegacyDatetimeCode pode não ser necessário. Assim, pode ser necessário ajustar-se nesse caso.

No caso de servidores de aplicativos, GlassFish, por exemplo, eles podem ser configurados durante a criação de um domínio JDBC junto com um pool de conexão JDBC dentro do próprio servidor, juntamente com outras propriedades configuráveis ​​usando a ferramenta de GUI da Web de administração ou em domain.xml diretamente. domain.xml se parece com o seguinte (usando uma fonte de dados 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>

No caso do WildFly, eles podem ser configurados em standalone-xx.yy.xml usando comandos CLI ou usando a ferramenta admin web GUI (usando uma fonte de dados 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>

A mesma coisa se aplica a fontes de dados não XA. Eles podem ser anexados diretamente ao próprio URL de conexão nesse caso.

Todas essas propriedades mencionadas serão definidas para a classe mencionada disponível no driver JDBC, a saber, com.mysql.jdbc.jdbc2.optional.MysqlXADataSource usando seus respectivos métodos setter nesta classe em ambos os casos.

No caso de usar a API principal do JDBC diretamente ou o pool de conexões no Tomcat, por exemplo, eles podem ser configurados diretamente para a URL de conexão (em 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:

Se o servidor de banco de dados de destino estiver sendo executado em uma zona sensível ao horário de verão e o horário de verão (DST) não estiver desativado, isso causará problemas. Melhor configurar o servidor de banco de dados também para usar um fuso horário padrão que não seja afetado pelo horário de verão, como UTC ou GMT. O UTC geralmente é preferível ao GMT, mas ambos são semelhantes a esse respeito. Citando diretamente de este link .

A propósito, abandonei o conversor proprietário do EclipseLink, desde JPA 2.1 fornece seu próprio conversor padrão que pode ser portado para um provedor JPA diferente conforme e quando necessário, sem pouca ou nenhuma modificação. Agora se parece com o seguinte em que java.util.Date também foi substituído 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);
    }
}

É então de responsabilidade exclusiva do(s) cliente(s) de aplicação associado(s) (Servlets / JSP / JSF / clientes de desktop remoto etc.) não é abordado nesta resposta por brevidade e está fora do tópico com base na natureza da pergunta atual.

Essas verificações nulas no conversor também não são necessárias, pois também é de responsabilidade exclusiva do(s) cliente(s) do aplicativo associado, a menos que alguns campos sejam opcionais.

Tudo vai bem agora. Quaisquer outras sugestões/recomendações são bem-vindas. Críticas a qualquer um dos meus ignorantes são muito bem-vindas.