在一个产品管理系统中,产品信息需要封装一份同步业务订单系统,封装同步信息的时候需要对产品信息做一些修改,同步完信息再将产品信息进行入库等操作。开发中就是使用的Map对象封装信息,但是总是发现入库信息和创建信息不一致的情况。
操作步骤伪代码如下:
//创建产品信息
createProdInfo();
//执行订单同步信息封装
packageProdInfo();
//执行订单同步服务操作
synProdInfo();
//上面操作都成功了,执行入库操作
insertProdInfo();
使用Map封装产品信息,产品中value都是字符串类型,并没有其他Object对象的情况下
@Test
public void putAllTest1() throws InterruptedException {
String crtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());//产品创建时间
Map<String, Object> prodInfo = new HashMap<>();//产品
prodInfo.put("prodId", "1");//产品ID
prodInfo.put("prodName", "AI产品");//产品名称
prodInfo.put("prodDesc", "这是个AI产品,智能连接未来");//产品描述
prodInfo.put("isMain", "1");//是否是主产品
prodInfo.put("crtTime", crtTime);//创建时间
System.out.println("原产品信息:" + JSON.toJSONString(prodInfo));
Map<String, Object> synProdInfo = new HashMap<>();//同步产品信息
TimeUnit.SECONDS.sleep(3);//模拟同步产品信息耗时3秒
String synCrtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
synProdInfo.putAll(prodInfo);
synProdInfo.put("systemId", "order");//同步系统ID
synProdInfo.put("systemName", "订单系统");//同步系统名称
synProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
System.out.println("同步信息内容:" + JSON.toJSONString(synProdInfo));
System.out.println("产品入库信息:" + JSON.toJSONString(prodInfo));
}
测试结果:
原产品信息:{
"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:45:59","prodId":"1","prodDesc":"这是个AI产品,智能连接未来"}
同步信息内容:{
"systemId":"order","systemName":"订单系统","isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:46:02","prodId":"1","prodDesc":"这是个AI产品,智能连接未来"}
产品入库信息:{
"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:45:59","prodId":"1","prodDesc":"这是个AI产品,智能连接未来"}
上面这种情况是符合要求的情况,同步信息crtTime
的修改并没有影响产品入库时间。但是出问题的场景是比这种更为复杂的情况,就是产品下面带有子产品信息的情况。
使用Map封装产品信息,产品中包含Map对象的的情况下,测试代码如下:
@Test
public void putAllTest2() throws InterruptedException {
String crtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());//产品创建时间
Map<String, Object> prodInfo = new HashMap<>();//产品
prodInfo.put("prodId", "1");//产品ID
prodInfo.put("prodName", "AI产品");//产品名称
prodInfo.put("prodDesc", "这是个AI产品,智能连接未来");//产品描述
prodInfo.put("isMain", "1");//是否是主产品,1是0不是
prodInfo.put("crtTime", crtTime);//创建时间
Map<String, Object> childProdInfo = new HashMap<>();
childProdInfo.put("prodId", "2");//产品ID
childProdInfo.put("prodName", "5G产品");//产品名称
childProdInfo.put("prodDesc", "这是个5G产品");//产品描述
childProdInfo.put("isMain", "0");//是否是主产品,1是0不是
childProdInfo.put("crtTime", crtTime);//创建时间
prodInfo.put("childProdInfo", childProdInfo);//子产品信息
System.out.println("原产品信息:" + JSON.toJSONString(prodInfo));
Map<String, Object> synProdInfo = new HashMap<>();//同步产品信息
TimeUnit.SECONDS.sleep(3);//模拟同步产品信息耗时3秒
String synCrtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
synProdInfo.putAll(prodInfo);
synProdInfo.put("systemId", "order");//同步系统ID
synProdInfo.put("systemName", "订单系统");//同步系统名称
synProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
Map<String,Object> synChildProdInfo = (Map<String, Object>) synProdInfo.get("childProdInfo");//获取子产品信息
synChildProdInfo.put("systemId", "order");//同步系统ID
synChildProdInfo.put("systemName", "订单系统");//同步系统名称
synChildProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
System.out.println("原同步信息内容:" + JSON.toJSONString(synProdInfo));
System.out.println("产品入库信息:" + JSON.toJSONString(prodInfo));
}
日志输出如下:
原产品信息:{
"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:57:07","prodId":"1","childProdInfo":{
"isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 21:57:07","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
原同步信息内容:{
"systemId":"order","systemName":"订单系统","isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:57:10","prodId":"1","childProdInfo":{
"systemId":"order","systemName":"订单系统","isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 21:57:10","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
产品入库信息:{
"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:57:07","prodId":"1","childProdInfo":{
"systemId":"order","systemName":"订单系统","isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 21:57:10","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
这里发现主产品的创建时间和入库的时候一致但是子产品的创建时间和其入库时间不一致了,但是和同步创建时间一致了。
这说明,同步信息封装的时候修改了子产品的信息,这并不是我们想要的结果。这里其实就涉及到对象的浅拷贝和深拷贝的问题。
简单来说就是:
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
序列化的方式可以实现对象的深拷贝,但是对象必须是实现了Serializable接口才可以,Map本身没有实现 Serializable 这个接口,不能实现深拷贝,但是HashMap实现了Serializable,可以进行深拷贝。
首先,附上深拷贝的方法
/**
* 使用对象的序列化进而实现深拷贝
* @param obj
* @param
* @return
*/
private <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
ByteOutputStream bos = new ByteOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
其次,对我们的代码稍作修改
@Test
public void putAllTest3() throws InterruptedException {
String crtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());//产品创建时间
HashMap<String, Object> prodInfo = new HashMap<>();//产品,此处修改为HashMap
prodInfo.put("prodId", "1");//产品ID
prodInfo.put("prodName", "AI产品");//产品名称
prodInfo.put("prodDesc", "这是个AI产品,智能连接未来");//产品描述
prodInfo.put("isMain", "1");//是否是主产品,1是0不是
prodInfo.put("crtTime", crtTime);//创建时间
Map<String, Object> childProdInfo = new HashMap<>();
childProdInfo.put("prodId", "2");//产品ID
childProdInfo.put("prodName", "5G产品");//产品名称
childProdInfo.put("prodDesc", "这是个5G产品");//产品描述
childProdInfo.put("isMain", "0");//是否是主产品,1是0不是
childProdInfo.put("crtTime", crtTime);//创建时间
prodInfo.put("childProdInfo", childProdInfo);//子产品信息
System.out.println("原产品信息:" + JSON.toJSONString(prodInfo));
Map<String, Object> synProdInfo = new HashMap<>();//同步产品信息
TimeUnit.SECONDS.sleep(3);//模拟同步产品信息耗时3秒
String synCrtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
synProdInfo = clone(prodInfo);//此处使用深拷贝赋值
synProdInfo.put("systemId", "order");//同步系统ID
synProdInfo.put("systemName", "订单系统");//同步系统名称
synProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
Map<String, Object> synChildProdInfo = (Map<String, Object>) synProdInfo.get("childProdInfo");//获取子产品信息
synChildProdInfo.put("systemId", "order");//同步系统ID
synChildProdInfo.put("systemName", "订单系统");//同步系统名称
synChildProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
System.out.println("原同步信息内容:" + JSON.toJSONString(synProdInfo));
System.out.println("产品入库信息:" + JSON.toJSONString(prodInfo));
}
测试结果如下:
原产品信息:{
"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 22:53:26","prodId":"1","childProdInfo":{
"isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 22:53:26","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
原同步信息内容:{
"systemId":"order","systemName":"订单系统","isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 22:53:29","prodId":"1","childProdInfo":{
"systemId":"order","systemName":"订单系统","isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 22:53:29","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
产品入库信息:{
"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 22:53:26","prodId":"1","childProdInfo":{
"isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 22:53:26","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
这里的产品创建时间、子产品创建时间都和入库时间一致了
至此,解决了由于putAll引起的问题,这个在使用的时候需要注意。