Date
je v Javě agnostik časového pásma. Vždy to trvá UTC (ve výchozím nastavení a vždy), ale když Date
/ Timestamp
je předán přes ovladač JDBC do databáze, interpretuje datum/čas podle časového pásma JVM, které je implicitně nastaveno na systémové časové pásmo (zóna nativního operačního systému).
Pokud by tedy ovladač MySQL JDBC nebyl výslovně nucen používat zónu UTC nebo pokud by samotné JVM nebylo nastaveno na používání této zóny, neuloží Date
/ Timestamp
do cílové databáze pomocí UTC, i když samotná MySQL měla být nakonfigurována tak, aby používala UTC pomocí default_time_zone='+00:00'
v my.ini
nebo my.cnf
v [mysqld]
sekce. Některé databáze jako Oracle mohou podporovat časové razítko s časovou zónou a může to být výjimka, kterou neznám (netestováno, protože toto prostředí momentálně nemám).
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
To lze dále objasnit kontrolou vyvolání setTimestampInternal()
metoda implementace ovladače MySQL JDBC.
Viz následující dva
volání setTimestampInternal()
metoda ze dvou přetížených verzí setTimestamp()
metoda.
Když žádný Calendar
instance je určena pomocí PreparedStatement#setTimestamp()
metoda, použije se výchozí časové pásmo (this.connection.getDefaultTimeZone()
).
Při používání fondu připojení v aplikačních serverech / kontejnerech servletů podporovaných připojením / JNDI přistupuje nebo pracuje s datovými zdroji jako,
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
(xa)com.mysql.jdbc.jdbc2.optional.MysqlDataSource
(jiné než xa)
ovladač MySQL JDBC musí být nucen používat požadované časové pásmo našeho zájmu (UTC), následující dva parametry je třeba zadat prostřednictvím řetězce dotazu adresy URL připojení.
Neznám historii ovladačů MySQL JDBC, ale v relativně starších verzích ovladačů MySQL tento parametr useLegacyDatetimeCode
nemusí být potřeba. V takovém případě může být vyžadováno přizpůsobení.
V případě aplikačních serverů, například GlassFish, je lze nastavit při vytváření sféry JDBC spolu s fondem připojení JDBC uvnitř samotného serveru spolu s dalšími konfigurovatelnými vlastnostmi buď pomocí nástroje webového GUI pro správu nebo v domain.xml
přímo. domain.xml
vypadá následovně (pomocí zdroje dat 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>
V případě WildFly je lze nakonfigurovat v standalone-xx.yy.xml
pomocí příkazů CLI nebo pomocí nástroje webového grafického rozhraní pro správu (pomocí zdroje dat 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>
Totéž platí pro jiné zdroje dat než XA. V takovém případě je lze přímo připojit k samotné adrese URL připojení.
Tyto všechny zmíněné vlastnosti budou nastaveny na zmíněnou třídu dostupnou v ovladači JDBC konkrétně com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
pomocí jejich příslušných metod setter v této třídě v obou případech.
V případě přímého použití základního JDBC API nebo například sdružování připojení v Tomcatu je lze nastavit přímo na adresu URL připojení (v 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>
Další:
Pokud cílový databázový server běží v zóně citlivé na letní čas a letní čas (DST) není vypnutý, způsobí to problémy. Je lepší nakonfigurovat databázový server také tak, aby používal standardní časové pásmo, které není ovlivněno letním časem, jako je UTC nebo GMT. UTC je obvykle preferováno před GMT, ale oba jsou v tomto ohledu podobné. Citace přímo z tento odkaz .
Mimochodem, upustil jsem od proprietárního převodníku EclipseLink, od JPA 2.1 poskytuje svůj vlastní standardní převodník
které lze podle potřeby přenést na jiného poskytovatele JPA bez malých nebo vůbec žádných úprav. Nyní to vypadá takto, ve kterém java.util.Date
byl také nahrazen 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);
}
}
Je pak výhradně odpovědností přidružených aplikačních klientů (servletů / JSP / JSF / klienti vzdálené plochy atd.) za převod data/času podle příslušného časového pásma uživatele při zobrazování nebo prezentaci data/času koncovým uživatelům, kteří není v této odpovědi zahrnuta pro stručnost a je mimo téma na základě povahy aktuální otázky.
Tyto kontroly nuly v převodníku také nejsou potřeba, protože za ně také odpovídají výhradně přidružení aplikační klienti, pokud některá pole nejsou volitelná.
Všechno teď jde dobře. Jakékoli další návrhy / doporučení jsou vítány. Kritika pro kohokoli z mých ignorantů je velmi vítána.