Part I
写了一段代码要批量导出数据,格式如下,
<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>
然后经过检查发现大量数据<real_name>字段为空,首先怀疑是数据库问题,执行sql测试,发现可以取得正常结果,数据库中有正常的密文存储结果,
并且结果数量与xml文件中数量一致,说明提取的sql肯定没问题,然后排查结果集映射,跟real_name有关的映射如下:
<result property="realName" column="user_name" />
经过仔细验证也没问题(其实这里有点不自信,浪费了好久时间).
至此我们已经基本排除了数据源和model层的问题(我以为),随后进下一层进行排查.
Part II
我注意到除了大量为空的数据以外,还有少量乱码,由于realName都是中文,所以怀疑是编码问题,业务流程是从数据库取值存到一个model的List里,再将List遍历逐个转化为XML元素,依次写入文件流,最终将文件流保存到本地文件中.
那么可以看出最容易出问题的两个环节,一个是转换为xml时候的编码,一个是写入文件流时候的编码.(伏笔2)
检查设置如下
m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
多次测试以后发现并没有问题.
Part III
随后进行单元测试,直接运行用例,发现遍历列表输出无误,但是当遍历列表后再进入xml转换时出现问题.此时进入debug,彻底进入玄学
@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后,点断点进入遍历,发现每个card的realName全部为空,这时在UserCardInf里面加入断点
@XmlElement(name="real_name")
public String getRealName() {
realName=DecodeAndEncodeUtil.desDecrypt(realName);
return realName==null?"":realName;
}
发现进入get方法以后realName直接为空,百思不得其解,我决定去上个厕所.
这个决定让我彻底走上了玄学的道路.
等我回来以后,随手debug一次,一路F6,发现居然打印出了正确结果
201611171000811:|6214*********6575:|182******557:|测试:|01:|510*********7999:|1:|
我以为我的键盘成精,有个传说中的键盘姑娘什么的,毕竟不能辜负我的饮料饼干经常分它一部分,还经常给它洗澡,而且键盘出身好,大户人家(F厂),优质内涵(原厂轴)成精必然是个美女,我想还是可以接受的...因此我单方面宣布其实键盘自动帮我改正了.
然而经过之前跟别人讨论时候的截图仔细对比,发现代码没有变化过.
那么从科学的角度出发,我们可以考虑其他变量,首先快速debug一遍,一路断点直接F6,发现可以正常打印,这时我怀疑是在我断点期间其他线程操作了我的变量,导致没有正确结果.这时我很好奇传入的值,所以将鼠标指向card,这时第一个card已经打印完毕,结果正确,但是第二个card进入后依然为null,这时我意识到可能是我的鼠标指向操作影响了属性值,于是我在getRealName()方法上加了断点,如果调用get方法,必然会跳入断点,从而证实我的猜想.
而结果令我非常失望,结果依然为null,并且没有跳入断点.所以这时我进行了大量重复试验,发现其他操作因素(时间/快慢/列表长度)都不影响结果,唯一影响结果的就是鼠标指向,那么我的鼠标又不是金手指可以直接擦写内存,我是如何影响realName属性的呢,我再次在getRealName方法上做文章,这次我加入了System.out.println(realName)
,并且采用了之前是乱码的那条数据,发现其规律是 密文->明文->乱码->乱码->null,因此我判断一定是我的鼠标指向操作调用了get方法,使得其多次调用解密操作,使得数据结果有误,最终由于数据太短无法解密返回null,于是我加上调用解密方法的日志,发现我每次鼠标指向果然会调用解密操作.
最后我将get方法修改如下:
private int i=0;
@XmlElement(name="real_name")
public String getRealName() {
if(nameFlag){
realName = DecodeAndEncodeUtil.desDecrypt(realName);
nameFlag=false;
}
System.out.println("调用get@"+this.hashCode()+":"+i++);
return realName==null?"":realName;
}
我发现每次鼠标指向都是IDE在后台自动调用了get方法,所以i的数量在依次变大,同时不会跳入get方法的断点...
最终测试结果打印日志:
调用get@20477080:1
调用get@17485453:1
调用get@20477080:2
调用get@17485453:2
调用get@20477080:3
调用get@17485453:3
调用get@20477080:4
调用get@17485453:4
调用get@20477080:5
调用get@17485453:5
调用get@20477080:6
201611171000811:|621**************685:|182*******57:|测试:|01:|51***********99:|1:|
调用get@17485453:6
201611171000811:|621***********04:|1822*********57:|测试:|01:|5108****************99:|1:|
databackup结束
databackup执行时间为:28718 ms-------------
经过一下午的努力,终于用科学破解了玄学了秘密,其实有很多次提前发现问题的关键,但是一方面是对自己的代码没自信,浪费了大量时间复查,一方面是对别人的代码太自信,导致了我一开始坚持认为调用解密方法不会出问题,更不可能返回null..
虽然玄不救非,氪不改命,但是我用科学的方法破解了玄学,所以我经过科学推理发现我迟早要出循环仪式护肩...
谢谢大家收看...我下班了...让我们拭目以待