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>測(cè)試</real_name>
<status>1</status>
<user_id>000000000000099</user_id>
</data>
然后經(jīng)過檢查發(fā)現(xiàn)大量數(shù)據(jù)<real_name>字段為空,首先懷疑是數(shù)據(jù)庫(kù)問題,執(zhí)行sql測(cè)試,發(fā)現(xiàn)可以取得正常結(jié)果,數(shù)據(jù)庫(kù)中有正常的密文存儲(chǔ)結(jié)果,

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