如何使用Java更改数据库连接中的MySQL时区?

54

MySQL是在时区"GMT+8"下运行的,而Tomcat则是在"GMT"下运行。当我将datetime保存到数据库中时,一切似乎都没问题,但是当我检查数据库中datetime的值时,我看到了"GMT"的值。

另外,当我尝试从数据库获取值时,该值被更改,似乎数据库中的值被视为"GMT+8",因此Java将其更改为"GMT"。

我已经像这样设置了连接URL:

useTimezone=true&serverTimezone=GMT

但是它没有起作用。


你只是得到了“错误”的时区,还是值本身就是错误的? - Thilo
我将配置更改为useGmtMillisForDatetimes=true&serverTimezone=GMT。看起来这是我想要的行为,但仍然感到困惑。 - Xilang
请参阅 https://dev59.com/Oqzla4cB1Zd3GeqPAa06#64031088。 - Caio Dornelles Antunes
6个回答

92

useTimezone是一种较旧的解决方法。MySQL团队最近重写了setTimestamp/getTimestamp代码,但只有在设置连接参数useLegacyDatetimeCode=false并且使用最新版本的mysql JDBC连接器时才会启用该功能。例如:

String url =
 "jdbc:mysql://localhost/mydb?useLegacyDatetimeCode=false

如果你下载mysql-connector的源代码并查看setTimestamp方法,你会很容易地看到发生了什么:

如果使用旧日期时间代码= false,则调用newSetTimestampInternal(...)。然后,如果传递给newSetTimestampInternal的日历为NULL,则会在数据库的时区中格式化你的日期对象:

this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US);
this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ());
timestampString = this.tsdf.format(x);

Calendar对象非常重要,因此请确保它为空 - 请确保使用:

setTimestamp(int,Timestamp).

不要使用setTimestamp(int,Timestamp,Calendar)方法。

现在应该很明显这是如何工作的了。如果你使用java.util.Calendar构造一个日期(例如:2011年1月5日早上3点),并设置为America/Los_Angeles时区(或任何你想要的时区),然后调用setTimestamp(1, myDate),它将采用SimpleDateFormat将你的日期按照数据库时区进行格式化。因此,如果你的数据库处于America/New_York时区,它将构造字符串“2011-01-05 6:00:00”以插入数据(因为纽约比洛杉矶快3个小时)。

要检索日期,请使用getTimestamp(int)(不需要Calendar参数)。再次,它将使用数据库时区来构建日期。

注意:现在Web服务器的时区完全无关紧要! 如果你不将useLegacyDatetimecode设置为false,则Web服务器时区将用于格式化 - 这会增加许多混乱。


注意:

可能MySQL会抱怨服务器时区是不明确的。例如,如果你的数据库设置为使用EST时区,在Java中可能有几种可能的EST时区,因此你可以通过告诉mysql-connector确切的数据库时区来澄清这一点:

String url =
 "jdbc:mysql://localhost/mydb?useLegacyDatetimeCode=false&serverTimezone=America/New_York";

只有在它报错时你才需要这样做。


结果证明,这正是我需要解决生产问题的设置。在本地我们无法复制它。但是在生产环境中在驱动程序中尝试这个操作后,消除了我们在处理10-30秒后看到的停顿。 - Robert Casto
@djsumdog - 时区信息并未存储在 Timestamp 类中。因此,当您将日期对象打印到控制台时,它将以 JVM 时区格式化。 - nogridbag
1
尝试使用以下代码:String url = "jdbc:mysql://localhost?tz=useLegacyDatetimeCode=false&serverTimezone=America/New_York";。对于我的应用程序,这个示例链接有效:url: jdbc:mysql://localhost:3306/trip_planner?tz=useLegacyDatetimeCode=false&serverTimezone=Europe/Berlin - mzober
我喜欢你使用“如果你下载源代码并查看方法,很容易看出发生了什么”和“现在应该很明显这是如何工作的”的方式。 - sumek
[DEBUG] 2017-09-20 12:27:55.066 [http-nio-8182-exec-1] [12709eqId] SqlExceptionHelper logExceptions [1] [RELOAD] - 无法获取JDBC连接[n/a] java.sql.SQLException: 无法创建PoolableConnectionFactory(由于底层异常而无法加载连接类:com.mysql.cj.core.exceptions.WrongArgumentException:格式错误的数据库URL,在“=America/New_York;”附近解析连接字符串失败。) - user2478236
显示剩余6条评论

14

JDBC使用所谓的“连接URL”,因此可以通过“%2B”来转义“+”符号,即

useTimezone=true&serverTimezone=GMT%2B8

目前mysql-connector-java 5.1.33-37的投诉总是由一个错误引起的,因为在大多数情况下,serverTimeZone参数不应该是必需的。请参考此帖子: https://dev59.com/vV8d5IYBdhLWcg3wt0Gy#33893008 - Aníbal

1
对于像Squirrel SQL Client (http://squirrel-sql.sourceforge.net/) 4版本这样的应用程序,您可以在“驱动程序属性”下设置“serverTimezone”为GMT+1(例如时区为“Europe/Vienna”)。

谢谢,那个有效!我还想指出IST没有改变任何东西,而GMT+1解决了问题。 - grinderX19
谢谢!我从未想过我的回答会有任何相关性... :-) - mtjmohr

0

更改数据库名称、用户名和密码

 connection = DriverManager.getConnection("jdbc:mysql://localhost/databasename?user=username&password=userpassword"
                                    + "&userUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetime=false&serverTimezone=UTC");
        

1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - lemon

0

对我来说没问题

spring.datasource.url= jdbc:mysql://localhost:3306/catalog?serverTimezone=UTC


0

有没有办法从MySQL获取支持的时区列表? 例如 - serverTimezone=America/New_York。这可以解决许多类似问题。我相信每次您需要指定正确的时区,无论是从应用程序还是数据库时区。


你可以使用TimeZone.getAvailableIDs()方法,它基本上返回一个字符串数组。你可以将它打印出来,检查可用时区的列表。 - ThivankaW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接