Dato
er tidszoneagnostisk i Java. Det tager altid UTC (som standard og altid), men når Dato
/ Tidsstempel
sendes gennem en JDBC-driver til en database, fortolker den dato/klokkeslæt i henhold til JVM-tidszonen, som igen er standard til systemets tidszone (den oprindelige operativsystemzone).
Medmindre MySQL JDBC-driveren eksplicit blev tvunget til at bruge UTC-zonen, eller JVM selv er indstillet til at bruge denne zone, vil den derfor ikke gemme Dato
/ Tidsstempel
ind i måldatabasen ved hjælp af UTC, selvom MySQL selv skulle konfigureres til at bruge UTC ved hjælp af default_time_zone='+00:00'
i my.ini
eller my.cnf
i [mysqld]
afsnit. Nogle databaser som Oracle understøtter muligvis tidsstempel med tidszone, og det kan være en undtagelse, som jeg ikke er bekendt med (utestet, da jeg ikke har det miljø på nuværende tidspunkt).
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) kaster SQLException
Dette kan yderligere afklares ved at kontrollere påkaldelsen af setTimestampInternal() metode til implementering af MySQL JDBC-driveren.
Se følgende to
kalder til setTimestampInternal()
metode fra de to overbelastede versioner af setTimestamp()
metode.
Når ingen Kalender
instans er angivet med PreparedStatement#setTimestamp()
metode, vil standardtidszonen blive brugt (this.connection.getDefaultTimeZone()
).
Mens du bruger en forbindelsespulje i applikationsservere / Servlet-containere understøttet af en forbindelse / JNDI, der får adgang til eller opererer på datakilder som,
com.mysql .jdbc.jdbc2.optional.MysqlXADataSource
(xa)com.mysql .jdbc.jdbc2.optional.MysqlDataSource
(ikke-xa)
MySQL JDBC-driveren skal tvinges til at bruge den ønskede tidszone af vores interesse (UTC), de følgende to parametre skal leveres gennem forespørgselsstrengen i forbindelses-URL'en.
Jeg er ikke bekendt med historien om MySQL JDBC-drivere, men i relativt ældre versioner af MySQL-drivere er denne parameter useLegacyDatetimeCode
er muligvis ikke nødvendig. Således kan man kræve at justere sig selv i så fald.
I tilfælde af applikationsservere, GlassFish, for eksempel, kan de indstilles, mens de opretter et JDBC-rige sammen med en JDBC-forbindelsespulje inde i selve serveren sammen med andre konfigurerbare egenskaber enten ved hjælp af admin web-GUI-værktøjet eller i domain.xml
direkte. domæne.xml
ser ud som følgende (ved hjælp af en XA-datakilde).
<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>
I tilfælde af WildFly kan de konfigureres i standalone-xx.yy.xml
ved at bruge CLI-kommandoer eller ved at bruge admin web-GUI-værktøjet (ved at bruge en XA-datakilde).
<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>
Det samme gælder for ikke-XA-datakilder. De kan i så fald føjes direkte til selve forbindelses-URL'en.
Disse alle nævnte egenskaber vil blive sat til den nævnte klasse, der er tilgængelig i JDBC-driveren, nemlig com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
ved at bruge deres respektive setter-metoder i denne klasse i begge tilfælde.
I tilfælde af at bruge kerne-JDBC API direkte eller forbindelsespooling i Tomcat, for eksempel, kan de indstilles direkte til forbindelses-URL'en (i 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>
Yderligere:
Hvis måldatabaseserveren kører på en DST-følsom zone, og sommertid (DST) ikke er slået fra, vil det forårsage problemer. Konfigurer hellere databaseserveren til også at bruge en standard tidszone, som ikke er påvirket af sommertid som UTC eller GMT. UTC foretrækkes normalt frem for GMT, men begge er ens i denne henseende. Citerer direkte fra dette link .
I øvrigt droppede jeg den proprietære konverter af EclipseLink, siden JPA 2.1 leverer sin egen standardkonverter
som kan overføres til en anden JPA-udbyder efter behov uden små eller ingen ændringer overhovedet. Det ser nu ud som følgende, hvor java.util.Date
blev også erstattet af 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);
}
}
Det er derefter udelukkende ansvaret for den eller de tilknyttede applikationsklienter (Servlets / JSP / JSF / remote desktop-klienter osv.) at konvertere dato / klokkeslæt i henhold til en passende brugers tidszone, mens du viser eller præsenterer dato / tid for slutbrugere, som er ikke dækket i dette svar for kortheds skyld og er off-topic baseret på arten af det aktuelle spørgsmål.
Disse nul-tjek i konverteren er heller ikke nødvendige, da det også udelukkende er de tilknyttede applikationsklienters ansvar, medmindre nogle felter er valgfrie.
Alt går fint nu. Andre forslag/anbefalinger er velkomne. Kritik til enhver af mine uvidende er meget velkommen.