MongoDB更新修改内嵌文档操作
MongoDB是文档型的数据库系统,doc是MongoDB的数据单位,每个doc相当于关系型数据库的数据行(row),doc和row的区别在于field的原子性:row中的column是不和分割的原子对象,而doc中的field可以是原子对象,也可以是内嵌doc(embedded doc),数组等数据类型。内嵌doc中所有field的Key不允许重复。
{
name:"t1",
age:21,
contact: {
phone:123,
email:"[email protected]"
}
}
在引用内嵌doc中的field时,使用 dot notation,格式是:embedded_doc.field:value,如果内嵌doc的field也是内嵌文档,依次类推,embedded_doc1.embedded_doc2.field:value。
示例,查询contact 字段中phone是123的所有doc。
db.foo.find({"contact.phone":123})
示例,使用find的第二个参数Projection doc,只返回两个field:name 字段和内嵌doc的email字段。通过dot notation,将内嵌doc中的某些字段返回,格式是:embedded_doc.field:1,表示返回该字段,embedded_doc.field:0,表示不返回该字段。
db.foo.find({"contact.phone":123},{_id:0,"contact.email":1,name:1})
upsert 选项非常有用,如果当前的doc中不存在内嵌文档,通过 s e t m o d i f i e r 来增加;如果当前的 d o c 中存在内嵌文档,通过 set modifier 来增加;如果当前的doc中存在内嵌文档,通过 setmodifier来增加;如果当前的doc中存在内嵌文档,通过set modifier来修改内嵌文档的值。
1,修改doc,增加内嵌doc
示例,增加address字段,这是内嵌doc
db.foo.updateMany(
{name:"t1"},
{$set:{address:{province:"henan",city:"xinyang"}}},
{upsert:true}
)
2,修改内嵌doc中的字段
示例,修改内嵌doc中province 和 city 字段的内容,全部修改为"shanghai"
db.foo.updateMany(
{name:"t1"},
{$set:{address:{province:"shanghai",city:"shanghai"}}},
{upsert:true}
)
上面方法会将整个内嵌文档替换,如果只是修改指定字段则使用如下
db.foo.update(
{name: 't1'},
{'$set': {'address.province': "广东"}}
);
3,如果是数组
# 在爱好上追加,如果多次执行会重复
db.col.update({name:"龙猫不热"},{$push:{"hobby.movies":"平凡的世界"}})
# 多次执行不会重复添加
db.col.update({name:"龙猫不热"},{$addToSet:{"hobby.movies":"平凡的世界"}})
KaTeX parse error: Expected '}', got 'EOF' at end of input: …除doc中的字段,使用格式:{unset:{field1:“”, field2:“”}},将删除的字段放在$unset文档中。
1,如果要删除内嵌doc中的field或数组中的元素,可以使用dot notation。
示例,删除address内嵌doc中的province 字段
db.foo.updateMany(
{name:"t1"},
{$unset:{"address.province":""}},
{upsert:true}
)
2,如果不使用dot notation,那么删除的将是整个内嵌doc
示例,在$unset modifier中,使用address 内嵌doc 格式,那么将删除address field。
db.foo.updateMany(
{name:"t1"},
{$unset:{address:{province:"shanghai"}}},
{upsert:true}
)
3,数组
$pull: 数组中符合条件的值将被删除
> db.col.update({name:"龙猫不热"},{$pull:{"hobby.movies":"平凡的世界"}})))))
插入嵌入文档,我们采用map和JSONObject两种内嵌文档实现
@Data
@EqualsAndHashCode(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DeviceV2DTO extends BaseDTO {
/**
* 设备编码
* 该属性对应mongodb的字段的名字,如果一直,则无需要注解
*/
@NotEmpty(message = "设备编码不能为空")
@Schema(description = "设备编码")
private String deviceCode;
/**
* 设备类型:1:扬尘监测,2:塔吊,3:升降机,4:AI分析
*/
@NotNull(message = "设备类型不能为空")
@Schema(description = "设备类型")
private Integer deviceType;
/**
* 经度 WGS84
*/
@Schema(description = "经度 WGS84")
private Double lng;
/**
* 纬度 WGS84
*/
@Schema(description = "纬度 WGS84")
private Double lat;
private DeviceAttrDTO installation;
/* ********************扩展信息********************** */
private JSONObject extra;
private Map<String,Object> extraParams;
}
@Test
@SneakyThrows
public void insertDevice(){
DeviceV2DTO device = new DeviceV2DTO();
device.setDeviceCode("0001");
device.setLng(108.12);
device.setLat(22.33);
device.setActivated(1);
device.setActiveTime(DateUtil.date());
device.setHeartBeatInterval(60);
// 内嵌文档-安装信息
DeviceAttrDTO installation = new DeviceAttrDTO();
installation.setInstallationUnit("业");
installation.setInstallationUnitCode("001");
installation.setInstallationTime("2022-01-21");
device.setInstallation(installation);
// 内嵌文档-扩展属性
JSONObject extra = new JSONObject();
extra.put("sim1","13224521245");
extra.put("num1",123);
device.setExtra(extra);
// 内嵌文档-扩展属性2
Map<String,Object> extraParams = new TreeMap<>();
extraParams.put("name1","test");
extraParams.put("value1",12.3);
device.setExtraParams(extraParams);
BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, "device");
List<DeviceV2DTO> rows = new ArrayList<>();
rows.add(device);
operations.insert(rows);
BulkWriteResult result = operations.execute();
result.getInserts().forEach(i->{
String id = i.getId().asObjectId().getValue().toString();
System.out.println("id:"+id);
});
}
Query query = new Query(Criteria.where("id").is("63d6299f74acce5e8fb93457"));
DeviceV2DTO data = mongoTemplate.findOne(query, DeviceV2DTO.class,"iot_device");
System.out.println(JsonUtil.toJson(data));
assert data != null;
for(Map.Entry<String,Object> entry : data.getExtra().entrySet()){
System.out.println(entry.getKey() + "=" +entry.getValue());
}
if(data.getExtraParams() != null){
for(Map.Entry<String,Object> entry : data.getExtraParams().entrySet()){
System.out.println(entry.getKey() + "=" +entry.getValue());
}
}
实体:
@Data
@EqualsAndHashCode(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DeviceV3DTO extends BaseDTO {
/**
* 设备编码
* 该属性对应mongodb的字段的名字,如果一直,则无需要注解
*/
@NotEmpty(message = "设备编码不能为空")
@Schema(description = "设备编码")
private String deviceCode;
/**
* 经度 WGS84
*/
@Schema(description = "经度 WGS84")
private Double lng;
/**
* 纬度 WGS84
*/
@Schema(description = "纬度 WGS84")
private Double lat;
private DeviceAttrDTO installation;
/* ********************扩展信息********************** */
private DeviceExtraDTO extra;
private DeviceExtraParamsDTO extraParams;
DeviceV3DTO dataV3 = mongoTemplate.findOne(query, DeviceV3DTO.class,"iot_device");
System.out.println(JsonUtil.toJson(dataV3));
注意:如果将来需要序列化到redis,则请不要用map定义内嵌文档,会在redis中出现
@Type java.util.Map
,导致后期不好序列化
在查询时不要使用json字符串进行查询,在这种具有嵌套文档的集合中,可以使用点符号进行操作
db.testcollection.find({"extra.maxHeight":2,"extra.banana":3})
内嵌文档更新时需要指定内嵌字段,否则会整个文档替换,比如上面例子中,如果要更新扩展属性,则应该是extra.maxHeight=xxx
public DeviceDTO modifyDevice(DeviceDTO data) {
DeviceDTO entity = get(data.getId());
if (entity != null) {
DeviceDoc newEntity = new DeviceDoc();
BeanUtil.copyProperties(data, newEntity);
// 反射获取更新字段
Update update = getUpdate(newEntity);
// 更新字段
BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, DeviceDoc.class);
operations.updateOne(Query.query(Criteria.where("id").is(data.getId())), update);
operations.execute();
}
return null;
}
public Update getUpdate(DeviceDoc entity){
// 复制属性
entity.doBeforeUpdate();
StringBuilder updateSql = new StringBuilder();
Update update = new Update();
Class<? extends DeviceDoc> entityClass = entity.getClass();
Field[] fields = getAllFields(entityClass);
for (Field field : fields) {
// 判断是否忽略字段
UpdateIgnoreField property = field.getAnnotation(UpdateIgnoreField.class);
String fieldName = field.getName();
field.setAccessible(true);
Object value = field.get(entity);
if(property == null && value!= null && !fieldName.equals(IotConstant.FIELD_ID)){
// 判断是内嵌文档扩展属性
if(field.getType().equals(JSONObject.class) ){
JSONObject jsonObject = (JSONObject) value;
for(Map.Entry<String,Object> item: jsonObject.entrySet()){
String key = fieldName+SymbolConstants.DOT+item.getKey();
Object v = item.getValue();
update.set(key, v);
updateSql.append(SymbolConstants.C_COMMA).append(key).append("=").append(v);
}
}else{
update.set(fieldName, value);
updateSql.append(SymbolConstants.C_COMMA).append(fieldName).append("=").append(value);
}
}
}
log.info("修改数据id: {}, value:{}",entity.getId(), updateSql);
return update;
}