Date 在 Java 中与时区无关。它总是采用 UTC(默认和始终),但是当 Date / Timestamp 通过 JDBC 驱动程序传递到数据库时,它会根据默认为系统时区的 JVM 时区来解释日期/时间(本机操作系统区域)。
因此,除非 MySQL JDBC 驱动程序被显式强制使用 UTC 区域或 JVM 本身设置为使用该区域,否则即使 MySQL 本身是配置为使用 UTC 在 my.ini 中使用 default_time_zone='+00:00' 或在 [mysqld] 部分中使用 my.cnf。像 Oracle 这样的一些数据库可能支持带时区的时间戳,这可能是我不熟悉的一个例外(未经测试,因为我目前没有那个环境)。
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
将指定参数设置为给定的java.sql.Timestamp 值,
使用给定的日历对象。驱动程序使用日历对象
构造一个 SQL TIMESTAMP 值,然后驱动程序将其发送到
数据库。使用 Calendar 对象,驱动程序可以计算
考虑到自定义时区的时间戳。 如果没有Calendar 对象
指定时,驱动程序使用默认时区,即
运行应用程序的虚拟机。
这可以通过检查 MySQL JDBC 驱动程序实现的setTimestampInternal() 方法的调用来进一步澄清。
请参阅setTimestamp() 方法的两个重载版本中对setTimestampInternal() 方法的以下two 调用。
/**
* Set a parameter to a java.sql.Timestamp value. The driver converts this
* to a SQL TIMESTAMP value when it sends it to the database.
*
* @param parameterIndex the first parameter is 1...
* @param x the parameter value
*
* @throws SQLException if a database access error occurs
*/
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
setTimestampInternal(parameterIndex, x, this.connection.getDefaultTimeZone());
}
/**
* Set a parameter to a java.sql.Timestamp value. The driver converts this
* to a SQL TIMESTAMP value when it sends it to the database.
*
* @param parameterIndex the first parameter is 1, the second is 2, ...
* @param x the parameter value
* @param cal the calendar specifying the timezone to use
*
* @throws SQLException if a database-access error occurs.
*/
public void setTimestamp(int parameterIndex, java.sql.Timestamp x,Calendar cal) throws SQLException {
setTimestampInternal(parameterIndex, x, cal.getTimeZone());
}
当没有使用PreparedStatement#setTimestamp() 方法指定Calendar 实例时,将使用默认时区(this.connection.getDefaultTimeZone())。
在应用服务器/Servlet 容器中使用连接池时,连接支持/JNDI 访问或操作数据源,例如,
MySQL JDBC 驱动程序需要强制使用我们感兴趣的所需时区 (UTC),以下两个参数需要通过连接 URL 的查询字符串提供。
我不熟悉MySQL JDBC驱动的历史,但是在比较老版本的MySQL驱动中,可能不需要useLegacyDatetimeCode这个参数。因此,在这种情况下可能需要调整自己。
在应用服务器的情况下,例如 GlassFish,可以在创建 JDBC 领域以及服务器本身内部的 JDBC 连接池以及其他可配置属性时进行设置,使用管理 Web GUI 工具或在domain.xml直接地。 domain.xml 如下所示(使用 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>
如果是 WildFly,可以使用 CLI 命令或使用管理 Web GUI 工具(使用 XA 数据源)在 standalone-xx.yy.xml 中配置它们。
<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>
同样的事情也适用于非 XA 数据源。在这种情况下,它们可以直接附加到连接 URL 本身。
在这两种情况下,所有提到的属性都将设置为 JDBC 驱动程序中可用的提到的类,即com.mysql.jdbc.jdbc2.optional.MysqlXADataSource,使用它们各自在此类中的设置方法。
如果直接使用核心 JDBC API,或者 Tomcat 中的连接池,可以直接设置为连接 URL(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>
补充:
如果目标数据库服务器在 DST 敏感区域上运行并且夏令时 (DST) 未关闭,则会导致问题。更好地配置数据库服务器以使用不受 DST 影响的标准时区,如 UTC 或 GMT。 UTC 通常优于 GMT,但两者在这方面是相似的。直接引用this link。
如果您真的更喜欢使用本地时区,我建议您至少
关闭夏令时,因为日期不明确
您的数据库可能是一场真正的噩梦。
例如,如果您正在构建电话服务并且正在使用
您的数据库服务器上的夏令时,然后您要求
麻烦:将没有办法判断一个客户是否打电话
从 "2008-10-26 02:30:00" 到 "2008-10-26 02:35:00" 实际调用
5 分钟或 1 小时 5 分钟(假设夏令时
发生在 10 月 26 日凌晨 3 点)!
顺便说一句,我放弃了 EclipseLink 的专有转换器since JPA 2.1 provides its own standard converter,它可以在需要时移植到不同的 JPA 提供程序,而无需进行少量修改或根本不需要修改。现在看起来像下面这样,其中java.util.Date 也被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);
}
}
然后,相关的应用程序客户端(Servlet / JSP / JSF / 远程桌面客户端等)全权负责根据适当的用户时区转换日期/时间,同时显示或显示结束日期/时间- 为简洁起见,此答案未涵盖且根据当前问题的性质偏离主题的用户。
也不需要转换器中的这些空检查,因为它也完全由关联的应用程序客户端负责,除非某些字段是可选的。
现在一切正常。欢迎任何其他建议/建议。欢迎批评我的任何无知者。