Part I
寫了一段代碼要批量導(dǎo)出數(shù)據(jù),格式如下,
<data>
<card_no>************</card_no>
<id>16111710001385</id>
<mobile_no>*********</mobile_no>
<pid_code>************</pid_code>
<pid_type>01</pid_type>
<real_name>測試</real_name>
<status>1</status>
<user_id>000000000000099</user_id>
</data>
然后經(jīng)過檢查發(fā)現(xiàn)大量數(shù)據(jù)<real_name>字段為空,首先懷疑是數(shù)據(jù)庫問題,執(zhí)行sql測試,發(fā)現(xiàn)可以取得正常結(jié)果,數(shù)據(jù)庫中有正常的密文存儲結(jié)果,
并且結(jié)果數(shù)量與xml文件中數(shù)量一致,說明提取的sql肯定沒問題,然后排查結(jié)果集映射,跟real_name有關(guān)的映射如下:
<result property="realName" column="user_name" />
經(jīng)過仔細驗證也沒問題(其實這里有點不自信,浪費了好久時間).
至此我們已經(jīng)基本排除了數(shù)據(jù)源和model層的問題(我以為),隨后進下一層進行排查.
Part II
我注意到除了大量為空的數(shù)據(jù)以外,還有少量亂碼,由于realName都是中文,所以懷疑是編碼問題,業(yè)務(wù)流程是從數(shù)據(jù)庫取值存到一個model的List里,再將List遍歷逐個轉(zhuǎn)化為XML元素,依次寫入文件流,最終將文件流保存到本地文件中.
那么可以看出最容易出問題的兩個環(huán)節(jié),一個是轉(zhuǎn)換為xml時候的編碼,一個是寫入文件流時候的編碼.(伏筆2)
檢查設(shè)置如下
m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
多次測試以后發(fā)現(xiàn)并沒有問題.
Part III
隨后進行單元測試,直接運行用例,發(fā)現(xiàn)遍歷列表輸出無誤,但是當遍歷列表后再進入xml轉(zhuǎn)換時出現(xiàn)問題.此時進入debug,徹底進入玄學(xué)
@Test
public void singleXMLTest(){
List<UserCardInf> list = backupDao.selectUserCardInf("20161116000000");
for(UserCardInf card:list){
System.out.println(card);
}
// BackupUtils.XMLIO(list,
// BackupUtils.getFileName(BackupUtils.USER_CARD_INF_NAME),
// BackupUtils.USER_CARD_INF_NAME);
//
}
進入debug后,點斷點進入遍歷,發(fā)現(xiàn)每個card的realName全部為空,這時在UserCardInf里面加入斷點
@XmlElement(name="real_name")
public String getRealName() {
realName=DecodeAndEncodeUtil.desDecrypt(realName);
return realName==null?"":realName;
}
發(fā)現(xiàn)進入get方法以后realName直接為空,百思不得其解,我決定去上個廁所.
這個決定讓我徹底走上了玄學(xué)的道路.
等我回來以后,隨手debug一次,一路F6,發(fā)現(xiàn)居然打印出了正確結(jié)果
201611171000811:|6214*********6575:|182******557:|測試:|01:|510*********7999:|1:|
我以為我的鍵盤成精,有個傳說中的鍵盤姑娘什么的,畢竟不能辜負我的飲料餅干經(jīng)常分它一部分,還經(jīng)常給它洗澡,而且鍵盤出身好,大戶人家(F廠),優(yōu)質(zhì)內(nèi)涵(原廠軸)成精必然是個美女,我想還是可以接受的...因此我單方面宣布其實鍵盤自動幫我改正了.
然而經(jīng)過之前跟別人討論時候的截圖仔細對比,發(fā)現(xiàn)代碼沒有變化過.
那么從科學(xué)的角度出發(fā),我們可以考慮其他變量,首先快速debug一遍,一路斷點直接F6,發(fā)現(xiàn)可以正常打印,這時我懷疑是在我斷點期間其他線程操作了我的變量,導(dǎo)致沒有正確結(jié)果.這時我很好奇?zhèn)魅氲闹?所以將鼠標指向card,這時第一個card已經(jīng)打印完畢,結(jié)果正確,但是第二個card進入后依然為null,這時我意識到可能是我的鼠標指向操作影響了屬性值,于是我在getRealName()方法上加了斷點,如果調(diào)用get方法,必然會跳入斷點,從而證實我的猜想.
而結(jié)果令我非常失望,結(jié)果依然為null,并且沒有跳入斷點.所以這時我進行了大量重復(fù)試驗,發(fā)現(xiàn)其他操作因素(時間/快慢/列表長度)都不影響結(jié)果,唯一影響結(jié)果的就是鼠標指向,那么我的鼠標又不是金手指可以直接擦寫內(nèi)存,我是如何影響realName屬性的呢,我再次在getRealName方法上做文章,這次我加入了System.out.println(realName)
,并且采用了之前是亂碼的那條數(shù)據(jù),發(fā)現(xiàn)其規(guī)律是 密文->明文->亂碼->亂碼->null,因此我判斷一定是我的鼠標指向操作調(diào)用了get方法,使得其多次調(diào)用解密操作,使得數(shù)據(jù)結(jié)果有誤,最終由于數(shù)據(jù)太短無法解密返回null,于是我加上調(diào)用解密方法的日志,發(fā)現(xiàn)我每次鼠標指向果然會調(diào)用解密操作.
最后我將get方法修改如下:
private int i=0;
@XmlElement(name="real_name")
public String getRealName() {
if(nameFlag){
realName = DecodeAndEncodeUtil.desDecrypt(realName);
nameFlag=false;
}
System.out.println("調(diào)用get@"+this.hashCode()+":"+i++);
return realName==null?"":realName;
}
我發(fā)現(xiàn)每次鼠標指向都是IDE在后臺自動調(diào)用了get方法,所以i的數(shù)量在依次變大,同時不會跳入get方法的斷點...
最終測試結(jié)果打印日志:
調(diào)用get@20477080:1
調(diào)用get@17485453:1
調(diào)用get@20477080:2
調(diào)用get@17485453:2
調(diào)用get@20477080:3
調(diào)用get@17485453:3
調(diào)用get@20477080:4
調(diào)用get@17485453:4
調(diào)用get@20477080:5
調(diào)用get@17485453:5
調(diào)用get@20477080:6
201611171000811:|621**************685:|182*******57:|測試:|01:|51***********99:|1:|
調(diào)用get@17485453:6
201611171000811:|621***********04:|1822*********57:|測試:|01:|5108****************99:|1:|
databackup結(jié)束
databackup執(zhí)行時間為:28718 ms-------------
經(jīng)過一下午的努力,終于用科學(xué)破解了玄學(xué)了秘密,其實有很多次提前發(fā)現(xiàn)問題的關(guān)鍵,但是一方面是對自己的代碼沒自信,浪費了大量時間復(fù)查,一方面是對別人的代碼太自信,導(dǎo)致了我一開始堅持認為調(diào)用解密方法不會出問題,更不可能返回null..
雖然玄不救非,氪不改命,但是我用科學(xué)的方法破解了玄學(xué),所以我經(jīng)過科學(xué)推理發(fā)現(xiàn)我遲早要出循環(huán)儀式護肩...
謝謝大家收看...我下班了...讓我們拭目以待