ArrayList加载对象逻辑错误:多对象数据重复致数据库仅存一条
嘿,兄弟,我前阵子刚踩过这个一模一样的坑!你的问题核心原因主要有两个:要么是你没真正创建新对象实例,只是复用了同一个引用;要么是克隆对象时只做了浅拷贝,导致所有元素共享可变属性,再加上日期格式的小错误,就出现了现在的情况。
先揪出最容易忽略的小问题:日期格式错误
你代码里的SimpleDateFormat格式字符串写的是"yyyy-mm-dd HH:MM:SS",这里的大小写完全搞错了:
mm代表的是分钟,不是月份!月份应该用MMSS代表的是毫秒,秒应该用ss
正确的格式应该是:"yyyy-MM-dd HH:mm:ss",这个小错误可能会导致你解析出来的日期全是错的,先把它改了!
核心问题:对象复用/浅拷贝导致所有元素指向同一个对象
你说“加载第一个对象,之后克隆其余对象”,大概率你的“克隆”根本没创建新对象,只是把同一个对象的引用重复加到列表里了。比如错误代码可能是这样的:
SimpleDateFormat f = new SimpleDateFormat("yyyy-mm-dd HH:MM:SS"); ArrayList<MyObject> myObjectList = new ArrayList<>(); // 加载第一个对象 MyObject firstObj = loadFirstObject(); myObjectList.add(firstObj); // 错误的克隆方式:只是引用赋值 for (int i = 1; i < totalCount; i++) { MyObject clonedObj = firstObj; // 这里根本没克隆,只是指向同一个对象! // 修改clonedObj的属性 clonedObj.setCreateTime(f.parse(someDateStr)); myObjectList.add(clonedObj); }
这种情况下,ArrayList里的所有元素其实都是指向firstObj的引用,你每次修改的都是同一个对象,最后列表里所有对象的数据自然完全相同。而且如果这些对象的主键(比如id)相同,数据库持久化时会认为是同一个对象,只会插入/更新一次,就出现了“仅一个对象被持久化”的情况。
正确的解决方法
方法1:每次创建全新的对象实例(最稳妥)
不要依赖克隆,直接在循环里new新的MyObject,然后把原对象的属性逐个复制过去,注意可变属性(比如Date、自定义对象)要复制新实例,避免共享引用:
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ArrayList<MyObject> myObjectList = new ArrayList<>(); MyObject firstObj = loadFirstObject(); myObjectList.add(firstObj); for (int i = 1; i < totalCount; i++) { // 创建全新的对象实例 MyObject newObj = new MyObject(); // 复制原对象的属性 newObj.setName(firstObj.getName()); newObj.setType(firstObj.getType()); // 对于Date类型,要创建新的Date实例,避免引用同一个对象 newObj.setCreateTime(new Date(firstObj.getCreateTime().getTime())); // 设置当前对象的唯一属性(比如主键、不同的日期) newObj.setId(generateNewId()); // 必须保证主键唯一! newObj.setCreateTime(f.parse(getDifferentDateStr(i))); myObjectList.add(newObj); }
方法2:实现深拷贝的clone方法
如果一定要用克隆,让MyObject实现Cloneable接口,并重写clone()方法,对可变属性做深拷贝:
public class MyObject implements Cloneable { private Long id; private String name; private Date createTime; // 重写clone方法,实现深拷贝 @Override protected Object clone() throws CloneNotSupportedException { MyObject cloned = (MyObject) super.clone(); // 对Date这种可变对象单独克隆,避免共享引用 cloned.createTime = (Date) this.createTime.clone(); return cloned; } // getter和setter省略 }
然后使用克隆的时候,记得修改主键(必须唯一):
for (int i = 1; i < totalCount; i++) { MyObject clonedObj = (MyObject) firstObj.clone(); // 必须设置新的主键,否则数据库会认为是同一个对象 clonedObj.setId(generateNewId()); // 修改其他需要变化的属性 clonedObj.setCreateTime(f.parse(getDifferentDateStr(i))); myObjectList.add(clonedObj); }
最后提醒:主键必须唯一
不管用哪种方法,每个要持久化的对象的主键(比如id)必须是唯一的!如果所有对象的主键相同,ORM框架(比如Hibernate、MyBatis)会把它们当成同一个对象,只会执行一次插入或者更新,这也是导致“仅一个对象被持久化”的关键原因之一!
内容的提问来源于stack exchange,提问作者Roland

