Date
là bất khả tri về múi giờ trong Java. Nó luôn sử dụng UTC (theo mặc định và luôn luôn) nhưng khi Date
/ Timestamp
được chuyển thông qua trình điều khiển JDBC đến cơ sở dữ liệu, nó diễn giải ngày / giờ theo múi giờ JVM được mặc định lần lượt cho múi giờ hệ thống (múi giờ hệ điều hành gốc).
Do đó, trừ khi trình điều khiển MySQL JDBC bị buộc phải sử dụng vùng UTC hoặc bản thân JVM được đặt để sử dụng vùng đó, nó sẽ không lưu trữ Date
/ Timestamp
vào cơ sở dữ liệu đích bằng UTC mặc dù bản thân MySQL đã được định cấu hình để sử dụng UTC bằng default_time_zone='+00:00'
trong my.ini
hoặc my.cnf
trong [mysqld]
tiết diện. Một số cơ sở dữ liệu như Oracle có thể hỗ trợ tem thời gian với múi giờ và nó có thể là một ngoại lệ mà tôi không quen thuộc (chưa được kiểm tra vì hiện tại tôi không có môi trường đó).
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
Điều này có thể được làm rõ hơn bằng cách kiểm tra lệnh gọi của setTimestampInternal()
phương pháp triển khai trình điều khiển MySQL JDBC.
Xem hai
sau gọi đến setTimestampInternal()
từ trong hai phiên bản được nạp chồng của setTimestamp()
phương pháp.
Khi không có Calendar
phiên bản được chỉ định với PreparedStatement#setTimestamp()
, múi giờ mặc định sẽ được sử dụng (this.connection.getDefaultTimeZone()
).
Trong khi sử dụng nhóm kết nối trong máy chủ ứng dụng / vùng chứa Servlet được hỗ trợ bởi kết nối / JNDI truy cập hoặc hoạt động dựa trên các nguồn dữ liệu như,
-
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
(xa) -
com.mysql.jdbc.jdbc2.optional.MysqlDataSource
(non-xa)
trình điều khiển MySQL JDBC cần được buộc phải sử dụng múi giờ mong muốn mà chúng tôi quan tâm (UTC), hai tham số sau cần được cung cấp thông qua chuỗi truy vấn của URL kết nối.
Tôi không quen thuộc với lịch sử của trình điều khiển MySQL JDBC nhưng trong các phiên bản tương đối cũ hơn của trình điều khiển MySQL, tham số này useLegacyDatetimeCode
có thể không cần thiết. Do đó, một người có thể yêu cầu điều chỉnh bản thân trong trường hợp đó.
Trong trường hợp máy chủ ứng dụng, GlassFish, chẳng hạn, chúng có thể được đặt trong khi tạo vùng JDBC cùng với nhóm kết nối JDBC bên trong chính máy chủ cùng với các thuộc tính có thể định cấu hình bằng cách sử dụng công cụ GUI web quản trị hoặc trong domain.xml
trực tiếp. domain.xml
trông giống như sau (sử dụng nguồn dữ liệu 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>
Trong trường hợp WildFly, chúng có thể được định cấu hình trong standalone-xx.yy.xml
sử dụng lệnh CLI hoặc sử dụng công cụ GUI web quản trị (sử dụng nguồn dữ liệu 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>
Điều tương tự cũng áp dụng cho các nguồn dữ liệu không phải XA. Trong trường hợp đó, chúng có thể được nối trực tiếp vào chính URL kết nối.
Tất cả các thuộc tính được đề cập này sẽ được đặt thành lớp được đề cập có sẵn trong trình điều khiển JDBC, cụ thể là com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
sử dụng các phương thức setter tương ứng của chúng trong lớp này trong cả hai trường hợp.
Ví dụ:trong trường hợp sử dụng trực tiếp API JDBC cốt lõi hoặc tổng hợp kết nối trong Tomcat, chúng có thể được đặt trực tiếp thành URL kết nối (trong 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>
Bổ sung:
Nếu máy chủ cơ sở dữ liệu đích đang chạy trên vùng nhạy cảm với DST và Giờ tiết kiệm ánh sáng ban ngày (DST) không được tắt, nó sẽ gây ra sự cố. Cấu hình tốt hơn máy chủ cơ sở dữ liệu cũng để sử dụng múi giờ chuẩn không bị ảnh hưởng bởi DST như UTC hoặc GMT. UTC thường được ưu tiên hơn GMT nhưng cả hai đều giống nhau về mặt này. Trích dẫn trực tiếp từ liên kết này .
Nhân tiện, tôi đã bỏ trình chuyển đổi độc quyền của EclipseLink, kể từ JPA 2.1 cung cấp công cụ chuyển đổi tiêu chuẩn của riêng mình
có thể được chuyển đến một nhà cung cấp JPA khác khi được yêu cầu mà không có một chút hoặc không có sửa đổi nào. Bây giờ nó trông giống như sau, trong đó java.util.Date
cũng được thay thế bằng 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);
}
}
Sau đó, (các) máy khách ứng dụng được liên kết (Servlets / JSP / JSF / máy khách từ xa, v.v.) hoàn toàn có trách nhiệm chuyển đổi ngày / giờ theo múi giờ của người dùng thích hợp trong khi hiển thị hoặc trình bày ngày / giờ cho người dùng cuối. không được đề cập trong câu trả lời này vì tính ngắn gọn và lạc đề dựa trên bản chất của câu hỏi hiện tại.
Những kiểm tra rỗng đó trong trình chuyển đổi cũng không cần thiết vì nó cũng chỉ do (các) ứng dụng khách được liên kết chịu trách nhiệm trừ khi một số trường là tùy chọn.
Bây giờ mọi thứ đều ổn. Mọi đề xuất / khuyến nghị khác đều được hoan nghênh. Chúng tôi hoan nghênh nhất những lời chỉ trích đối với những người thiếu hiểu biết về tôi.