信创-为什么ORACLE使用JDBC查询SYSDATE时,RS.getDate能获取到时间部分?
1. 这是个什问题?
在某些版本ORACLE对应的JDBC驱动中,使用查询语句select sysdate from dual,使用rs.getDate获取到日期包含时间部分.而其他大部分数据库返回都不会包含时间部分.间接造成在做信创迁移时,造成时间部分丢失.
由于测试代码简单,这里不提供代码部分,直接测试Oracle mysql两种数据的情况,同时经过测试返现,对应的kingbase guassdb同样有一下情况
txt
✅ [oracle] 数据库连接已建立
当前查询SQL:select sysdate from dual
Date类型值=>93
getDate_FMT => 2026-05-03 21:30:34
getDate=> 2026-05-03
getTimestamp=>2026-05-03 21:30:34.0
✅ [oracle] 数据库连接已关闭
✅ [mysql] 数据库连接已建立
当前查询SQL:select sysdate()
Date类型值=>93
getDate_FMT => 2026-05-03 00:00:00
getDate=> 2026-05-03
getTimestamp=>2026-05-03 21:30:34.0
✅ [mysql] 数据库连接已关闭
针对以上输出结果可以反应了以下信息:
- ORACLE 和 MYSQL日期列对应的TYPE值都是 93
- 使用格式
yyyy-MM-dd HH:mm:ss格式化,可以发现ORACL包含时间部分21:30:34 - 当前的查询sql,复制到数据执行,不管是oracle还是mysql查询接口都包含了时间部分
以下是直接在mysql数据库执行的情况
sysdate() |
-------------------|
2026-05-03 21:36:24|
通过这些输出信息可以得到一个结论:数据库底层查询sysdate都是包含时间部分的,只是有些JDBC的驱动去掉了时间部分
2. Oracle的JDBC驱动是如何处理的呢?
带着上边的疑问,我分析了ojdbc8-19.7.0.0.jar的源码,找到对应的处理逻辑.由此发现,Oracle针对这种情况,还提供了专门的配置oracle.jdbc.DateZeroTime,让用户确定是否保留日期类型的时间部分
那这部分代码到底在哪里呢?以下是我定位到堆栈信息
txt
T4CDateAccessor(DateTimeCommonAccessor).getDate(int) line: 135
T4CStatement(GeneratedStatement).getDate(long, int) line: 163
ForwardOnlyResultSet(GeneratedScrollableResultSet).getDate(int) line: 186
Multdbtest.testSysdate() line: 138
由此我们可以知道,Oralce处理日期的类是 DateTimeCommonAccessor
我们看下这部分代码
java
Date getDate(int paramInt, Calendar paramCalendar) throws SQLException {
Calendar calendar;
if (isNull(paramInt)) return null;
if (paramCalendar == null) {
calendar = this.statement.getDefaultCalendar();
} else {
calendar = (Calendar)paramCalendar.clone();
}
getBytesInternal(paramInt, this.tmpBytes);
int i = oracleYear(this.tmpBytes);
calendar.clear();
calendar.set(1, i);
calendar.set(2, oracleMonth(this.tmpBytes));
calendar.set(5, oracleDay(this.tmpBytes));
if (OracleDriver.getSystemPropertyDateZeroTime()) {
calendar.set(11, 0);
calendar.set(12, 0);
calendar.set(13, 0);
} else {
calendar.set(11, oracleHour(this.tmpBytes));
calendar.set(12, oracleMin(this.tmpBytes));
calendar.set(13, oracleSec(this.tmpBytes));
}
calendar.set(14, 0);
if (i > 0 && calendar.isSet(0)) {
calendar.set(0, 1);
}
return new Date(calendar.getTimeInMillis());
}
其中有一部分关键代码 if (OracleDriver.getSystemPropertyDateZeroTime()) 如果这个值为true,就直接把时间部分值设置成0了,否则,就取数据返回的时间部分的信息.
那 OracleDriver.getSystemPropertyDateZeroTime 这个函数,从函数名基本可以知道它的意图,应该是获取 SystemProperty ,这些信息可以通过启动时注入.那我们推测是否是正确的呢?
java
public static boolean getSystemPropertyDateZeroTime() {
String str = PhysicalConnection.getSystemPropertyDateZeroTime("false");
return str.equalsIgnoreCase("true");
}
从这里代码只知道,可以大概推测到,这方法默认值返回的是false,就是要获取数据库返回的时间部分,我们继续往下追代码
java
static String getSystemPropertyDateZeroTime(String defaultValue) {
return getSystemProperty("oracle.jdbc.DateZeroTime", defaultValue);
}
private static String getSystemProperty(String configKey, String defaultValue) {
if (configKey != null) {
final String fstr = configKey;
final String fdefaultValue = defaultValue;
final String[] rets = { defaultValue };
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
rets[0] = System.getProperty(fstr, fdefaultValue);
return null;
}
});
return rets[0];
}
return defaultValue;
}
3. 知道原因,那如何使用呢?
目前知道时间部分存在的原因,如果目前希望rs.getDate返回的日期,跟其他数据库保一样不返回时间部分,就可以设置oracle.jdbc.DateZeroTime=true,对应测试代码如下:
java
String exesql = "select sysdate ";
if (dbName == "oracle") {
exesql += " from dual";
System.setProperty("oracle.jdbc.DateZeroTime", "true");
}
4. MYSQL是如何处理的呢?
通过跟踪代码,数据也是返回了日期类型的,但是rs.getDate使用的是SqlDateValueFactory做了值转换,可以找到这个类,跟踪一下方法.
java
public Date localCreateFromDate(InternalDate idate) {
synchronized (this.cal) {
try {
if (idate.isZero()) {
throw new DataReadException(Messages.getString("ResultSet.InvalidZeroDate"));
}
this.cal.clear();
this.cal.set(idate.getYear(), idate.getMonth() - 1, idate.getDay());
long ms = this.cal.getTimeInMillis();
return new Date(ms);
} catch (IllegalArgumentException e) {
throw ExceptionFactory.createException(WrongArgumentException.class, e.getMessage(), e);
}
}
}
从以上代码可以知道,mysql只赋值了了年月日,对应调试的堆栈信息如下
java
SqlDateValueFactory.localCreateFromDate(InternalDate) line: 80
SqlDateValueFactory.localCreateFromDate(InternalDate) line: 50
SqlDateValueFactory(AbstractDateTimeValueFactory<T>).createFromDate(InternalDate) line: 67
SqlDateValueFactory.localCreateFromTimestamp(InternalTimestamp) line: 120
SqlDateValueFactory.localCreateFromTimestamp(InternalTimestamp) line: 50
SqlDateValueFactory(AbstractDateTimeValueFactory<T>).createFromTimestamp(InternalTimestamp) line: 87
MysqlTextValueDecoder.decodeTimestamp(byte[], int, int, ValueFactory<T>) line: 79
ByteArrayRow(AbstractResultsetRow).decodeAndCreateReturnValue(int, byte[], int, int, ValueFactory<T>) line: 87
ByteArrayRow(AbstractResultsetRow).getValueFromBytes(int, byte[], int, int, ValueFactory<T>) line: 241
ByteArrayRow.getValue(int, ValueFactory<T>) line: 91
ResultSetImpl.getDate(int) line: 740
5. 信创总结
在适配ORACLE数据信创时,由于oracle返回了日期时间的部分.导致迁移到其他数据库时,返回前端的JSON日期串,时间部分丢失了.此时要做信创适配,有一下三种途径
- 修改业务代码,需要显示时间的代码,统一调整成
rs.getTimestamp - 查看对应数据库驱动,是否有类似
oracle.jdbc.DateZeroTime的配置(目前我接触的数据都没有发现) - 修改驱动代码,同样返回时间部分
原文地址:https://mp.weixin.qq.com/s/0M5nKQSA114doJLC1AyZYw
往期信创ORACEL迁移推荐:
信创-ORACLE迁移到DM8
信创-ORACLE迁移到KingbaseV9