Date
Java 中与时区无关。它总是采用 UTC(默认情况下并且总是)但是当Date
/ Timestamp
通过 JDBC 驱动程序传递到数据库,它根据 JVM 时区解释日期/时间,而 JVM 时区又默认为系统时区(本机操作系统时区)。
因此,除非显式强制 MySQL JDBC 驱动程序使用 UTC 区域或 JVM 本身设置为使用该区域,否则它不会存储Date
/ Timestamp
使用 UTC 进入目标数据库,即使 MySQL 本身配置为使用 UTCdefault_time_zone='+00:00'
in my.ini
or my.cnf
in the [mysqld]
部分。像Oracle这样的一些数据库可能支持带时区的时间戳,这可能是我不熟悉的例外(未经测试,因为我目前没有该环境)。
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
将指定参数设置为给定值java.sql.Timestamp
价值,
使用给定的 Calendar 对象。驱动程序使用 Calendar 对象
构造一个 SQLTIMESTAMP
值,然后驱动程序发送到
数据库。通过 Calendar 对象,驱动程序可以计算
考虑到自定义时区的时间戳。If no Calendar
目的
指定时,驱动程序使用默认时区,即
运行应用程序的虚拟机.
这可以通过检查调用来进一步澄清setTimestampInternal()MySQL JDBC 驱动程序实现的方法。
请参阅以下内容two致电setTimestampInternal()
两个重载版本中的方法setTimestamp()
method.
/**
* 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());
}
When no Calendar
实例指定为PreparedStatement#setTimestamp()
方法,将使用默认时区(this.connection.getDefaultTimeZone()
).
在由连接/JNDI 支持的应用程序服务器/Servlet 容器中使用连接池时,访问或操作数据源,例如,
- com.mysql.jdbc.jdbc2.optional.MysqlXADataSource (xa)
- com.mysql.jdbc.jdbc2.optional.MysqlDataSource(非 XA)
需要强制 MySQL JDBC 驱动程序使用我们感兴趣的所需时区 (UTC),需要通过连接 URL 的查询字符串提供以下两个参数。
- useLegacyDatetimeCode=false
- serverTimezone=UTC
I am not familiar with the history of MySQL JDBC drivers but in relatively older versions of MySQL drivers, this parameter useLegacyDatetimeCode
may not be needed. Thus, one may require to adjust oneself in that case.
对于应用程序服务器(例如 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,它们可以在以下位置进行配置:standalone-xx.yy.xml
使用 CLI 命令或使用管理 Web GUI 工具(使用 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>
The same thing is applicable to non-XA datasources. They can directly be appended to the connection URL itself in that case.
所有提到的这些属性将被设置为 JDBC 驱动程序中可用的提到的类,即com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
在这两种情况下,都在此类中使用各自的 setter 方法。
例如,如果直接使用核心 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,但两者在这方面是相似的。直接引用自这个链接.
如果您确实更喜欢使用当地时区,我建议至少
关闭夏令时,因为日期不明确
您的数据库可能是一场真正的噩梦。
例如,如果您正在构建电话服务并且正在使用
您的数据库服务器上的夏令时,然后您要求
麻烦:无法判断是否有顾客打来电话
从“2008-10-26 02:30:00”到“2008-10-26 02:35:00”实际调用
5 分钟或 1 小时 5 分钟(假设夏令时
发生在 10 月 26 日凌晨 3 点)!
顺便说一句,我放弃了 EclipseLink 的专有转换器,因为 JPA 2.1 提供了自己的标准转换器可以根据需要将其移植到不同的 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 /远程桌面客户端等)独自负责根据适当的用户时区转换日期/时间,同时向最终用户显示或呈现日期/时间为简洁起见,本答案未涵盖此内容,并且根据当前问题的性质,该内容属于题外话。
Those null checks in the converter are also not needed as it is also solely the responsibility of the associated application client(s) unless some fields are optional.
现在一切都很顺利。欢迎任何其他建议/推荐。对于任何对我无知的人的批评是非常受欢迎的。