Date
jest niezależny od strefy czasowej w Javie. Zawsze przyjmuje czas UTC (domyślnie i zawsze), ale kiedy Date
/ Timestamp
jest przekazywany przez sterownik JDBC do bazy danych, interpretuje datę / godzinę zgodnie ze strefą czasową JVM, która domyślnie jest z kolei strefą czasową systemu (natywna strefa systemu operacyjnego).
W związku z tym, o ile sterownik MySQL JDBC nie został wyraźnie zmuszony do korzystania ze strefy UTC lub sama maszyna JVM nie jest ustawiona na korzystanie z tej strefy, nie zapisze Date
/ Timestamp
do docelowej bazy danych przy użyciu czasu UTC, mimo że sam MySQL miał być skonfigurowany do korzystania z czasu UTC przy użyciu default_time_zone='+00:00'
w my.ini
lub my.cnf
w [mysqld]
Sekcja. Niektóre bazy danych, takie jak Oracle, mogą obsługiwać znacznik czasu ze strefą czasową i może to być wyjątek, którego nie znam (nie przetestowano, ponieważ obecnie nie mam tego środowiska).
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
Można to dodatkowo wyjaśnić, sprawdzając wywołanie setTimestampInternal()
metoda implementacji sterownika MySQL JDBC.
Zobacz następujące dwa
wywołuje setTimestampInternal()
metoda z dwóch przeciążonych wersji setTimestamp()
metoda.
Gdy nie ma Calendar
instancja jest określona za pomocą PreparedStatement#setTimestamp()
metody, zostanie użyta domyślna strefa czasowa (this.connection.getDefaultTimeZone()
).
Podczas korzystania z puli połączeń w serwerach aplikacji / kontenerach serwletów wspieranych przez połączenie / JNDI uzyskujące dostęp lub działające na źródłach danych, takich jak,
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
(xa)com.mysql.jdbc.jdbc2.optional.MysqlDataSource
(nie xa)
sterownik MySQL JDBC musi być zmuszony do używania żądanej strefy czasowej naszego zainteresowania (UTC), następujące dwa parametry muszą być dostarczone przez ciąg zapytania adresu URL połączenia.
Nie znam historii sterowników MySQL JDBC, ale w stosunkowo starszych wersjach sterowników MySQL ten parametr useLegacyDatetimeCode
może nie być potrzebne. Dlatego w takim przypadku może być konieczne dostosowanie się.
W przypadku serwerów aplikacji, na przykład GlassFish, można je ustawić podczas tworzenia dziedziny JDBC wraz z pulą połączeń JDBC wewnątrz samego serwera wraz z innymi konfigurowalnymi właściwościami za pomocą administracyjnego narzędzia GUI web lub w domain.xml
bezpośrednio. domain.xml
wygląda następująco (przy użyciu źródła danych 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>
W przypadku WildFly można je skonfigurować w standalone-xx.yy.xml
za pomocą poleceń CLI lub za pomocą narzędzia GUI web administratora (za pomocą źródła danych 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>
To samo dotyczy źródeł danych innych niż XA. W takim przypadku można je dołączyć bezpośrednio do adresu URL połączenia.
Te wszystkie wymienione właściwości zostaną ustawione na wspomnianą klasę dostępną w sterowniku JDBC, a mianowicie com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
używając odpowiednich metod ustawiających w tej klasie w obu przypadkach.
W przypadku bezpośredniego korzystania z rdzenia JDBC API lub puli połączeń w Tomcat, można je ustawić bezpośrednio na adres URL połączenia (w 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>
Dodatkowe:
Jeśli docelowy serwer bazy danych działa w strefie wrażliwej na czas letni, a czas letni (DST) nie jest wyłączony, spowoduje to problemy. Lepiej skonfiguruj serwer bazy danych również tak, aby używał standardowej strefy czasowej, na którą nie ma wpływu czas letni, taki jak UTC lub GMT. UTC jest zwykle preferowany w stosunku do GMT, ale oba są podobne pod tym względem. Cytując bezpośrednio z ten link .
Przy okazji porzuciłem zastrzeżony konwerter EclipseLink, od JPA 2.1 zapewnia własny standardowy konwerter
które można przenieść do innego dostawcy JPA w razie potrzeby bez niewielkich lub żadnych modyfikacji. Teraz wygląda to tak, jak poniżej, w którym java.util.Date
został również zastąpiony przez 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);
}
}
Konwersja daty / czasu zgodnie z odpowiednią strefą czasową użytkownika podczas wyświetlania lub prezentowania daty / czasu użytkownikom końcowym jest wówczas wyłączną odpowiedzialnością powiązanych klientów aplikacji (serwletów / JSP / JSF / klientów zdalnego pulpitu itp.). nie jest uwzględniona w tej odpowiedzi ze względu na zwięzłość i jest nie na temat w oparciu o charakter bieżącego pytania.
Te sprawdzenia wartości NULL w konwerterze również nie są potrzebne, ponieważ jest to również wyłączna odpowiedzialność powiązanych klientów aplikacji, chyba że niektóre pola są opcjonalne.
Teraz wszystko idzie dobrze. Wszelkie inne sugestie / zalecenia są mile widziane. Mile widziana jest krytyka każdego z moich ignorantów.