这个例子是这样的,我们有三种数据库表需要做data migration,两种表Deliverable和Local_Deliverable存储了常规数据,另一张表叫做Deliverable_Object。后一张表是前两张表里的java bean对象序列化以后的存储,这样做主要是为了效率考虑,不需要跨表查询得到数据对象。真正显示在用户界面上的就是这张Deliverable_Object表里的java bean对象。
Java bean对象里有个数据字段叫做GADate,这个在数据库里 (DB2)是一个timestamp对象,目前的现象是data migration完以后,我们发现显示的GADate和用户的原始输入差一天,更具体一点,Deliverable_Object和Deliverable(Local_Deliverable)对象的GAdata差一天。
让我们看一下数据的产生和录入的过程:
1.用户前台输入(dojo控件),javascript转数据,通过json object传入到后台(long)
var ega = Util.getDateLongValue(dijit.byId("fp_GADate").attr("value"));
if (ega != null) {
datawrapper["ega"] = ega;
}
getDateLongValue: function(date){
return date != null ? this.toGMTTimestamp(date) : null;
},
toGMTTimestamp: function(date){
if (date == null) {
return null;
}
var millsecond = date.getTime();
var offset = date.getTimezoneOffset();
return millsecond - offset * 60000;
}
2.后台new Timestamp(value), 存入数据库:
Local_Deliverable: setTimestamp(time)
Deliverable: setTimestamp()
Deliverable_Cache setDeliverableObject()
3.后台从数据库读Timestamp,转成long
Local_Deliverable: getTimestamp(time)
Deliverable: getTimestamp()
Deliverable_Cache getDeliverableObject()
让我们看一个具体的例子:
步骤 | 值 | 注解 |
---|---|---|
用户输入 | Tue Feb 15 2011 00:00:00 GMT+8 | 前台取得的时间和用户TimeZone有关 |
经JS转换 | Tue Feb 15 2011 00:00:00 GMT | 这里将用户TimeZone信息抹去得到GMT时间 |
后台得到 | New Timestamp(Tue Feb 15 2011 00:00:00 GMT) | Java Code |
存储数据库 | Deliverable (2011-2-15:00:00:00:00000 JVM-timezone ) Local_Deliverable(2011-2-15:00:00:00:00000 JVM-timezone) Deliverable_Object(2011-2-15:00:00:00:00000 JVM-timezone) |
Java.sql.Timestamp和 SQL timestamp(string)之间转换 CachResultsetImpl.setTimestamp 系统默认用JVM 时区转换 |
注意到,这里从Java数据类型java.sql.Timestamp向数据库timestamp类型字段写值时,需要使用JVM时区进行转换,其中注意用到的方法为:
CachResultsetImpl.setTimestamp(TimeStamp)
CachResultsetImpl.setTimestamp(TimeStamp,Calendar)
CachResultsetImpl.getTimestamp(TimeStamp)
CachResultsetImpl.getTimestamp(TimeStamp,Calendar)
针对我们开始的问题,最终的结论为:
1. 数据库中timestamp的存储为2011-2-15:00:00:00:00000这样的字符串,本身没有任何时区概念
2. 从数据库中读取timestamp初始化一个java.sql.Timestamp对象时,需要对数据库中的字符串进行时区的解释,即
2011-2-15:00:00:00:00000 = 2011-2-15:00:00:00:00000 GMT+8 还是2011-2-15:00:00:00:00000GMT+0
这时候,默认用JVM的timezone来转换,也可以指定Calendar(TimeZone)。
反之,将一个java.sql.Timestamp对象写进数据库时,也要对其进行转换。
这里有一个典型的例子,对同一个java.sql.Timestamp按不同的Timezone写进数据库我们可以看到如下结果:
Asian/Shang Hai GMT +8 2011-03-09 14:04:51.21
GMT 2011-03-09 06:04:51.21
EST GMT -5 2011-03-09 01:04:51.21
4. 对TimeStamp做data migration本身不会有任何问题,因为读了一次,又写了一次,两次就将转换抵消最终写进数据库的Timestamp值还是本身的;问题是我们用了blob存了java bean,也就是读了一次的那个java.sql.Timestamp,这个时候做data migration的JVM的timezone设置就成了问题的关键。由于我们做data migration时候的机器time zone设置成GMT+8,所以会有问题发生。
步骤 | 值 | 注解 |
---|---|---|
migration前 | Tue Feb 15 2011 00:00:00 | |
migration后 | Deliverable (2011-2-15:00:00:00:00000 GMT) Local_Deliverable(2011-2-15:00:00:00:00000 GMT) Deliverable_Object(2011-2-15:00:00:00:00000 JVM-timezone) |
cachresultsetImpl.getTimestamp cachresultsetImpl.setTimestamp 系统默认用JVM 时区转换 对于Deliverable转换2次所以没变; 对于Object存入的是读取的值 |
那么我们在自己的机器上如何设置JVM timezone呢?
方法1:
在control panel里如下设置(注意:如果选中”夏令时”那么对于4-9月的时间会有一个偏移量,此时相当于GMT+1。所以最好不选中“夏令时”)
方法2:
JVM 启动选项
-Duser.timezone=GMT