項目中一個 1 毫秒引起的問題

問題描述:java

1. 項目分爲兩個部分,一個後臺的數據採集程序,接收來自各個傳感器發送過來的數據,解碼後以特定的格式存儲在數據庫對應的表中,另外一個是web部分,用戶能夠經過web查看這些數據的狀況(歷史數據和實時數據);web

2. 在存儲檢測數據的表MonitorData中,以設備ID(區分不一樣的物理設備)、類型屬性ID(同一個設備能夠檢測多種數據,好比風速風向傳感器就可以檢測出風速和風向兩種數據,這兩種屬性就經過類型屬性ID來區分)、時間戳(記錄後臺收到數據的時間)來惟一標識一條數據;sql

3. 在web部分,有一個模塊能夠經過指定起始時間段來查詢歷史數據;數據庫

4. 好了如今問題來了,如今我經過這個模塊查看一段時間內的歷史數據,查詢結果以下:框架

  咦,爲何會出現這麼多0值呢?來看一下數據庫中的數據庫(select * from MonitorData):jsp

  在數據庫中對應的值根本不是0,而web端讀到的值竟然是0,這是爲何?函數

 

WHY?sqlserver

  帶着這個問題,首先找到了web端對應的頁面部分:ui

  對,就是這個listMonitorData.jsp,讓咱們來看一下它的源碼,在源碼中咱們主要是要找到在後臺這個數據請求是交給那一個類的方法處理的,因此咱們找到它的表單提交路徑:spa

  該web使用了struts2框架,咱們看到,該表單提交給了一個叫listMonitorDataDevice的action來處理,那咱們繼續去找該action

  直覺告訴我,我要的action頗有可能在struts-device.xml中,由於咱們查詢的設備的數據嘛。打開一看,果真有對應的action:

  看到class="com.water.struts2.action.device.DeviceAction" method="{1}",咱們知道接下來調用的方法是com.water.struts2.action.device.DeviceAction類中的listMonitorData方法。對method參數不熟的同窗能夠參考一下這篇博客

  好了,那讓咱們直接找到對應的方法中的抓取數據部分吧

  到這裏咱們抓取了數據庫中這段時間的所有數據的時間戳,下面是讀取數據的主要代碼,也是出問題的部分:

 1                             MonitorDataVo mvo = new MonitorDataVo();
 2                             if (list != null && list.size() > 0) {
 3                                 for (int m = 0; m < list.size(); m++) {
 4                                     String datatimestamp = StringUtil
 5                                             .getStringValue((list.get(m)));
 6 //                                    System.out.println("+++++++datatimestamp: " + datatimestamp);
 7                                     for (int i = 0; i < attributes.size(); i++) {
 8                                         DeviceAttributeVo attriVo = attributes.get(i);
 9                                         // 首先構建ID
10                                         MonitorDataId id = new MonitorDataId();
11                                         id.setDevice(device);
12                                         DeviceTypeAttribute attr = new DeviceTypeAttribute();
13                                         attr.setDeviceTypeAttributeId(attriVo.getDeviceTypeAttributeId());
14                                         id.setDeviceTypeAttribute(attr);
15                                         Date tmpDate = DateTimeUtil.parseDateTimeHMSs(datatimestamp);
16 //                                      System.out.println("=========tmpdate: " + tmpDate.getTime()%1000);
17                                         id.setDataTimestamp(tmpDate);
18                                         // 新建
19                                         MonitorData mdata = new MonitorDataDAO().findById(id);
20                                         if (mdata == null) {
21                                             System.out.println("==NULL");
22                                             mdata = new MonitorData();
23                                             mdata.setDataValue(0.0);
24                                         }
25                                         mvo.addMonitorData(mdata);
26                                     }
27                                     mvo.setDatatimestamp(DateTimeUtil
28                                             .parseDateTimeHMSs(datatimestamp));
29                                     mvolist.add(mvo);
30                                     mvo = new MonitorDataVo();
31                                 }
32                             }

  直觀上來看,根據上一步獲得的時間戳的list,知道了數據的條數,對於每條記錄咱們用Hibernate的get方法根據ID查詢出對應的value(在findById方法中調用Hibernate的get方法)

MonitorData instance = (MonitorData) getSession().get("com.water.hibernate.MonitorData", id);

  這裏咱們用了一個MonitorData的類來表示一個數據。attributes屬性是一個action類中的全局數據,表示當前設備具備的屬性,如風速風向。到這裏咱們初步明白了爲何有些web頁面上有些值是0了,是由於get方法沒有根據ID匹配到對象,因此返回了null,繼而findById返回了null,繼而mdata的value值爲0.0,可是爲何get方法會查詢不到對應的對象呢?咱們所使用的ID是這樣的:

private Device device;
private DeviceTypeAttribute deviceTypeAttribute;
private Date dataTimestamp;

  它包含了三個成員變量,device是根據deviceID來查詢到的

Device device = new DeviceDAO().findById(deviceId);

  而deviceId是這個action類的String型的成員變量,它的賦值是經過反射找到set方法實現的,而deviceTypeAttribute是根據attributes來的,attributes是根據deviceID查詢數據庫獲得的,deviceID一樣是這個action類的String型的成員變量,所以這兩個變量應該都沒有什麼問題。datatimestamp是根據前面的時間戳list獲得的String,再轉化成Date類型而獲得的,按理說也沒什麼問題。

Date tmpDate = DateTimeUtil.parseDateTimeHMSs(datatimestamp);

 

從現象概括問題的特色,從而發現問題的根源

  經過查看源碼找問題貌似遇到了點問題,因而咱們回過頭來再去看看問題自己。首先咱們觀察是具體哪些時間出現了問題,在數據庫中對應時間點是否是有什麼特徵:

  咱們發現,在數據庫中的時間戳是精確到毫秒的,而出現問題的時間點(也就是經過ByID的get方法找不到的記錄)有一個共同的特色,那就是毫秒位爲0,或者說按正常書寫小數點後末尾的0是能夠省去的,也就是達不到三位,好比14.890,能夠寫成14.89。

根據問題特色猜想引發問題的可能緣由:

  出現問題主要是由於findById(id)這個函數不能獲得id對應的對象,進一步來講是Hibernate中的get方法返回了null,而get方法基於的ID中包含了一個datatimestamp的Date對象,凡是datatimestamp中毫秒位爲0的ID都沒法正常get到數據。而Hibernate的get方法最終會轉化成一條SQL語句對數據庫進行查詢(固然首先是在Session中查找)

Hibernate: select monitordat0_.DeviceID as DeviceID10_0_, monitordat0_.DeviceTypeAttributeID as DeviceTy2_10_0_, monitordat0_.DataTimestamp as DataTime3_10_0_, monitordat0_.DataUpdateTime as DataUpda4_10_0_, monitordat0_.DataValue as DataValue10_0_ from water.dbo.MonitorData monitordat0_ where monitordat0_.DeviceID=? and monitordat0_.DeviceTypeAttributeID=? and monitordat0_.DataTimestamp= ?

 

因此猜想:

  SQL語句在作查詢的時候條件對比是如何實現的?尤爲是datetime類型的數據對比,最終是轉化成格式化的時間字符串進行對比的,仍是轉化成毫秒數進行對比,根據問題的特徵,有可能最終是轉化成格式化時間的字符串進行的對比,也就是說數據庫中的時間保留了毫秒位最後的‘0’,而java類中的Date類型最終是簡化了寫法,把最後的‘0’省去了,所以二者出現了不一致,因此該條記錄查詢不到。

 

臨時解決方案:

  1. 因爲採集的傳感器數據是每一個幾秒鐘發送一次,因此毫秒級別的偏差根本就不痛不癢,所以在數據寫入的時候作點「手腳」不是皆大歡喜嗎。
  2. 既然問題出現的時間點是毫秒位爲0的數據,那咱們主要讓其毫秒位不出現0不就能夠了嗎。
  3. 須要注意的一點是:sqlserver中全部的datetime類型的值在顯示、處理時有所調整。即會圓整到幾個特殊的毫秒個位值:0、三、7:如:(九、0、1) 會引發進0調整;(五、六、七、8)引發7調整;(二、三、4)引發3調整。所以咱們在作手腳的時候不能值偏移一個毫秒,所以咱們偏移三毫秒。
Date dt = new Date();
System.out.println("當前時間" + dt);
String tmpDate = String.valueOf(dt.getTime()%10);
long tmpDt = dt.getTime();
if(tmpDate.equalsIgnoreCase("0") || 
            tmpDate.equalsIgnoreCase("9") ||
            tmpDate.equalsIgnoreCase("1")) {
     tmpDt += 3;
}
Date newDate = new Date(tmpDt);
id.setDataTimestamp(newDate);
相關文章
相關標籤/搜索