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,
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
(x)com.mysql.jdbc.jdbc2.optional.MysqlDataSource
(não-xa)
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&characterEncoding=UTF-8&useLegacyDatetimeCode=false&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.