Datum
är tidszon agnostiker i Java. Det tar alltid UTC (som standard och alltid) men när Datum
/ Tidsstämpel
skickas genom en JDBC-drivrutin till en databas, tolkar den datum/tid enligt JVM-tidszonen som i sin tur är förinställd på systemets tidszon (den ursprungliga operativsystemszonen).
Därför, såvida inte MySQL JDBC-drivrutinen uttryckligen tvingades använda UTC-zonen eller JVM själv är inställd att använda den zonen, skulle den inte lagra Datum
/ Tidsstämpel
in i måldatabasen med UTC även om MySQL själv skulle konfigureras för att använda UTC med default_time_zone='+00:00'
i my.ini
eller my.cnf
i [mysqld]
sektion. Vissa databaser som Oracle kan stödja tidsstämpel med tidszon och det kan vara ett undantag som jag inte är bekant med (otestad eftersom jag inte har den miljön för närvarande).
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) kastar SQLException
Detta kan ytterligare förtydligas genom att kontrollera anropet av setTimestampInternal() metod för implementeringen av MySQL JDBC-drivrutinen.
Se följande två
anrop till setTimestampInternal()
metod från de två överbelastade versionerna av setTimestamp()
metod.
När ingen Kalender
instans specificeras med PreparedStatement#setTimestamp()
metod kommer standardtidszonen att användas (this.connection.getDefaultTimeZone()
).
När du använder en anslutningspool i applikationsservrar / Servlet-behållare som backas upp av en anslutning / JNDI som får åtkomst till eller använder datakällor som,
com.mysql .jdbc.jdbc2.optional.MysqlXADataSource
(xa)com.mysql .jdbc.jdbc2.optional.MysqlDataSource
(icke-xa)
MySQL JDBC-drivrutinen måste tvingas använda den önskade tidszonen av vårt intresse (UTC), följande två parametrar måste tillhandahållas via frågesträngen för anslutnings-URL.
Jag är inte bekant med historien om MySQL JDBC-drivrutiner, men i relativt äldre versioner av MySQL-drivrutiner är denna parameter useLegacyDatetimeCode
kanske inte behövs. Således kan man behöva anpassa sig i så fall.
När det gäller applikationsservrar, till exempel GlassFish, kan de ställas in när man skapar en JDBC-sfär tillsammans med en JDBC-anslutningspool inuti själva servern tillsammans med andra konfigurerbara egenskaper antingen med hjälp av admin webb-GUI-verktyget eller i domain.xml
direkt. domain.xml
ser ut som följande (med en XA-datakälla).
<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>
När det gäller WildFly kan de konfigureras i standalone-xx.yy.xml
med CLI-kommandon eller med hjälp av admin webb-GUI-verktyget (med en XA-datakälla).
<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>
Samma sak gäller för icke-XA-datakällor. De kan i så fall läggas till direkt i själva anslutningsadressen.
Dessa alla nämnda egenskaper kommer att ställas in på den nämnda klassen som är tillgänglig i JDBC-drivrutinen, nämligen com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
använder sina respektive sättermetoder i denna klass i båda fallen.
Vid användning av kärnan JDBC API direkt, eller anslutningspoolning i Tomcat, till exempel, kan de ställas in direkt till anslutningsadressen (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>
Ytterligare :
Om måldatabasservern körs på en zon som är känslig för sommartid och sommartid (DST) inte är avstängd kommer det att orsaka problem. Bättre konfigurera databasservern även att använda en standardtidszon som inte påverkas av sommartid som UTC eller GMT. UTC är vanligtvis att föredra framför GMT men båda är lika i detta avseende. Citerar direkt från denna länk .
Förresten, jag tappade EclipseLinks proprietära omvandlare, sedan JPA 2.1 tillhandahåller sin egen standardomvandlare
som kan porteras till en annan JPA-leverantör vid behov utan några små eller inga ändringar alls. Det ser nu ut som följande där java.util.Date
ersattes också av 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 är då enbart ansvaret för den eller de associerade applikationsklienterna (Servlets / JSP / JSF / fjärrskrivbordsklienter etc) att konvertera datum / tid enligt en lämplig användares tidszon samtidigt som datum / tid visas för slutanvändare som tas inte upp i det här svaret för korthetens skull och är off-topic baserat på den aktuella frågans karaktär.
Dessa nollkontroller i omvandlaren behövs inte heller eftersom det också enbart är den tillhörande applikationsklientens ansvar om inte vissa fält är valfria.
Allt går bra nu. Alla andra förslag/rekommendationer är välkomna. Kritik till någon av mina okunniga är mycket välkommen.