问题复现
-
使用参数占位符的形式执行SQL时,若传入的类型为时间类型则可能产生因时区导致的时间差,从而造成数据异常。
-
代码如下:
public static void main(String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); Properties prop = new Properties(); prop.load(Resources.getResourceAsStream("jdbc.properties")); Connection conn = DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("username"), prop.getProperty("password")); PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM date_test WHERE created <= ?"); pstmt.setObject(1, new Date()); // 此处传入的值会被MySQL驱动根据类型进行相应处理 System.out.println(pstmt); // 打印出执行的SQL pstmt.close(); conn.close(); }
问题原因
-
在调用setObject等方法时,驱动会根据传入的数据类型做出相应的处理。
-
传入日期类型时则会走到
ClientPreparedQueryBindings.java
源文件的setTimestamp()
方法里。 -
在方法里会有如下一行代码:
this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", this.session.getServerSession().getServerTimeZone());
-
getServerTimeZone()
方法则去获取数据库的默认时区,优先获取数据库连接信息上指定的,其次再去获取数据库服务器设置的时区,然后根据时区转换传入的时间,时间差也因此产生。
解决方案
方式一:增加数据库连接信息参数
- 在连接信息的url值增加参数:
serverTimezone=Asia/Shanghai
。 - 参数键值对之间使用
&
符号连接。 - 时区值也可以写为
GMT%2B8
,均表示中国的时区,具体根据实际情况设置时区。
方式二:改变数据库时区设置
-
进入MySQL命令行界面后进行如下操作
# 查看当前服务器的时区 show variables like '%time_zone%'; # 设置服务器时区为正八时区 set time_zone = '+8:00'; # 查询当前时间来验证修改是否生效 select now();
方式三:使用字符串类型作为时间参数
- 在使用参数占位符操作数据库时,传入日期类型的地方使用字符串的形式传入。
- 注意传入的时间格式要与数据库的格式保持一致否则也会产生异常。