最近遇到一个问题,在用公司平台提供的save方法保存数据后,主键值会发生变化,导致程序出错。而这个错误是偶发的,只是在压力测试并发时偶尔会出现。

  • 场景如下:
Model model = new Model();
long id = IdGenerator.generateLongId();
model.setId(id);
model.setXxx(xxx);

doSomething();

//调用平台提供的保存方法
PlantformUtils.save(model);

//此方法中会用id去查询,但查不到数据
doOtherThings(id);

首先排查我自己程序中修改model.id的地方,通过日志分析没有发现异常,后来发现是在调用平台的save方法后,model的id发生了变化,导致数据库中插入的model的id并不是我当初设置的值。

平台的save方法中有一处判断,大概如下:

...
//取Model的主键对象,看是否为空,如果为空则重新设置
Object pkValue = getPKValue(model);
if(pkValue == null 
		|| (pkValue instanceof String && pkValue.toString().trim().length() == 0)
		|| (pkValue instanceof Number && ((Number)pkValue).intValue() == 0) ){
	//重新设置model的主键
	setPKValue(model);
}
...

通过断点调试,发现有少数的pkValue不为空也不为0时进入了此if条件中,将能进入if中的pkValue与正确的pkValue作了对比,结果如下:

//不正确的
Object pkValue = 293344286336876544L;
System.out.println(((Number)pkValue).intValue());//0
//正确的
pkValue = 293347735933812736L;
System.out.println(((Number)pkValue).intValue());//738197504

java.long.Long中intValue的代码如下:

public int intValue() {
	return (int)value;
}
  • 原因:

Java中long类型占8个字节,int类型占4个字节,将long转为int时,是将long类型值先转为二进制,然后从低位到高位截32位:

Long:293344286336876544  
对应的二进制为:10000010010001010110001001100000000000000000000000000000000  
从低位到高位截取32位:00000000000000000000000000000000  
Int:0
Long:293347735933812736  
对应的二进制为:10000010010001011100011011000101100000000000000000000000000 
从低位到高位截取32位:00101100000000000000000000000000
Int:738197504

也就是当Long类型数的低位4个字节每一位都为0时,它的intValue值就为0,所以才会在并发时出现此问题。