Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

Mongodb学习

2021/12/22 后端
Word count: 3,425 | Reading time: 14min

Mongodb学习

创建数据库-集合(表)

  • 使用指定数据库(没有会创建): use DATABASE_NAME, 如果数据库不存在,则创建数据库,否则切换到指定数据库。

    • 如: use douban, 此时db就变成了douban,之后db.xxxx()都默认在这个数据库下操作
  • 查看所有数据库,可以使用 show dbs

  • 创建集合:db.createCollection(name, options)

    • 随意创建集合, db.createCollection("top250");

    • options 可以是如下参数:

      字段 类型 描述
      capped 布尔 (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。
      autoIndexId 布尔 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。
      size 数值 (可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段。
      max 数值 (可选)指定固定集合中包含文档的最大数量。
    • 创建固定集合: db.createCollection("mycol", { capped : true, autoIndexId : true, size : 6142800, max : 10000 } )

  • 查看已有集合,可以使用 show collectionsshow tables 命令:

插入文档

  • db.COLLECTION_NAME.insert(document)db.COLLECTION_NAME.save(document)
    • save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne()db.collection.replaceOne() 来代替。
    • insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
    • 3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。

查询find:

在MongoDB中可以使用find0函数查询文档。
语法格式为:find(){查询条件(可选)},{指定投影的键(可选)})如果未给定参数则表示查询所有数据。 当查询所有,要制定投影键的时候得列出前面的大括号,如find({键: {操作符: 条件}}, {投影键名: 1(显示该列)| 0(不显示该列), ..}), 如果不想显示_id可以这么写: find({},{_id: 0})
prettyO函数可以使用格式化的方式来显示所有文档。

$and$or联合使用

查询title为test5并且size等于500,或者size小于400的文档。

db.dev.find({$or:[{$and:[{title:{$eq:"test5"}},{size:500}]},{size:{$Lt:409}}]})

聚合查询aggregate

SQL操作函数 mongodb聚合操作
where $match$groupby前侧
group $group
having $match$groupby后侧
select $project
order by $sort
limit $limit
join $lookup
1
2
3
4
5
// aggregate中一定是个数组格式
db.COLLECTION_NAME.aggregate([
{$group:{_id:"$分组键名”,$分组键名”……,别名:{聚合运算:"$运算列”}}}, // 分组处理
{条件筛选:{键名:{运算条件:运算值}}}
])

例子: 求和

查询dev集合中一共有多少个文档。
相当于sql语句:SELECT count(*)AS count FROM dev

db.dev.aggregate([{$group:{_id:null.count:($sum:1}}}])

  • $group:分组,代表聚合的分组条件
  • _id:分组的字段。相当于SQL分组语法group by column name中的column name部分。
    如果根据某字段的值分组,则定义为id:’$字段名’。所以此案例中的null代表一个固定的字面值null,表示对所有列即整表。
  • count:返回结果字段名。可以自定义,类似SQL中的字段别名。
  • $sum:求和表达式。相当于SQL中的sumO。
  • 1:累加值。如果是对某列如size求值和,则1改为""$size"

管道

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
管道操作符是按照书写的顺序依次执行的,每个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符(对于最后一个管道操作符,是将结果返回给客户端),称为流式工作方式。
管道操作符:$match$group$sort$limit$skip$unwind, 管道操作符,只能用于计算当前聚合管道的文档,不能处理其它的文档。

MongoDB 的 aggregate 是可以分为多个阶段的(pipeline)

  • 第一阶段,$match,这里可以参考《MongoDB:查询和投影操作符》这篇文章,根据条件去查找。

  • 第二阶段,$project,表示要哪个域,不要哪个域,或者投影成别的一个域,这里表示 value 域保留,status 域,根据条件来转换成 “confirmed” 或者 “unconfirmed”,_id 不要。

  • 第三阶段,groupidgroup,_id 域用前面的status来表示,balance 是对之前的$value进行求和。

常用命令:

  • $project:使用$project操作符做聚合投影操作从而修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。如db.dev.aggregate([{Sunwind:"Stags"},{Sproject:{id:0,Tags:"$tags",Title:"$title"}}])从而不显示id, 并且可以取别名

  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。

  • $limit:用来限制MongoDB聚合管道返回的文档数。

  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。

  • $lookup

    // Aggregation.lookup("student", "student_id", "_id", "student") 参数含义如下
    LookupOperation lookupOperation=LookupOperation.newLookup().
               from("grade").  //关联从表名
               localField("gradeId").     //主表关联字段
               foreignField("_id").//从表关联的字段
               as("GradeAndStu");   //查询结果名
    
  • $count

  • unwind:将文档中的某一个**数组类型字段拆分成多条**,每条包含数组中的一个值。如查询dev集合,将其中数组内的内容拆分显示(会生成多个id相同的查询结果),`db.dev.aggregate([{unwind:"$tags"}])`

  • $group:将集合中的文档分组,可用于统计结果。

操作内嵌文档

操作数组

对数组根据条件查询

$all、$size、$slice、$elemMatch

  • $all查找数组中包含指定的值的文档
  • $size 查找数组大小等于指定值的文档
  • slice,slice·查询数组中指定返回元素的个数, `slice·可以查询数组中第几个到第几个

对数组内嵌文档查询

  • $elemMatch文档包含有一个元素是数组,那么$elemMatch可以匹配内数组内的元素并返回文档数据db.orders.find({"items":{$elemMatch:{"quantity":2}}})
  • elemMatch可以带多个查询条件`db.orders.find({"items":{elemMatch:{“quantity”:4,“ino”:“002”}}}) `
  • $elemMatch同样可以用在find方法的第二个参数来限制返回数组内的元素,只返回我们需要的文档db.orders.find({"onumber":"001"},{"items":{$elemMatch:{"quantity":4,"ino":"002"}},"cname":1,"date":1,"onumber":1})

from;

完整demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public JobPositionDetailsVO getJobPositionDetails(String orgId, String jpId) {
Organization org = orgService.findByOrgId(orgId);
RootOrg root = orgService.getRootOrgInfo();
// 查询组织的岗位信息设置
List<SystemFieldListDTO> systemList;
List<Map<String, Object>> values;
List<JobPosition> allJobPosition = Lists.newArrayList();
JobPositionDetailsVO result = new JobPositionDetailsVO();
if (org == null && !orgId.equals(root.getOrgId())) {
throw new IllegalArgumentException("orgId:" + orgId + " 对应的组织信息不存在!");
} else if (orgId.equals(root.getOrgId())) {
// 根组织读取所有职务
allJobPosition = jobPositionService.list();
// 包装成List<Map>类型
values = getJobInfo(allJobPosition);
result.setOrgId(root.getOrgId());
} else {
// 非根组织取系统字段内岗位数据
systemList = org.getSystemFieldList();
// 校验下数据
SystemFieldListDTO dto = orgCommonService.getFirstRepeatFieldByCode(FieldConst.JOB_POSITION, systemList);
if (dto == null) {
throw new RuntimeException("组织内岗位信息不能为空!");
/*result.setPersonList(Lists.newArrayList());
result.setSettingList(Lists.newArrayList());
return result;*/
}
values = dto.getValues();
result.setOrgId(orgId);
}

// 获取组织配置中的所有职务id
Set<String> jpIds = Sets.newHashSet();
// 用来记录组织内岗位的编制数量
Map<String, Integer> numMap = Maps.newHashMap();
// 用来记录组织内岗位的数量范围
Map<String, Integer> rangeMap = Maps.newHashMap();
for (Map<String, Object> value : values) {
String curJpId = (String) ((Map<String, Object>) (value.get(FieldConst.VALUE))).get(FieldConst.JPID);
jpIds.add(curJpId);
numMap.put(curJpId, (Integer) ((Map<String, Object>)(value.get(FieldConst.VALUE))).get(FieldConst.NUM));
rangeMap.put(curJpId, (Integer) ((Map<String, Object>)(value.get(FieldConst.VALUE))).get(FieldConst.RANGE));
}

if (CollectionUtils.isEmpty(jpIds)) {
result.setPersonList(Lists.newArrayList());
result.setSettingList(Lists.newArrayList());
return result;
}

// 查询相关的所有职务信息
List<JobPosition> jobPositionList = jobPositionService.query()
.in(JobPosition::getJpId, jpIds).list();
// 职务列表排序
Collections.sort(jobPositionList);
// 如果对应的职务信息全部被删除了,而组织内设置的职务都没有对应删除,直接返回
if (CollectionUtils.isEmpty(jobPositionList)) {
result.setPersonList(Lists.newArrayList());
result.setSettingList(Lists.newArrayList());
return result;
}

// 查询在职信息中为 orgId 的人员
Criteria criteria = new Criteria();
criteria.andOperator(
Criteria.where("form.code").is(FieldConst.JOB_POSITION),
Criteria.where("form.values.orgId").is(orgId)
);
List<PersonInfo> personInfoList = mongoTemplate.find(Query.query(criteria), PersonInfo.class);
//排除离职人员
Set<String> userIds = personInfoList.stream().map(p -> p.getUserId()).collect(Collectors.toSet());
Set<String> resignUserIds = employmentInfoService.getResignationByUserIds(userIds);
personInfoList = personInfoList.stream().filter(p -> !resignUserIds.contains(p.getUserId())).collect(Collectors.toList());
// 获取 key 为 职务id, value 为 用户id 的 map
Map<String, Set<String>> reverseMap = orgService
.reverseMapping(personInfoList, FieldConst.JOB_POSITION, FieldConst.JPID, false, false);
// 获取 key 为 jpId, value 为 角色的 map
Map<String, String> roleMap;
if (orgId.equals(root.getOrgId())) {
// 根节点的话roleMap取在职表内的role信息
roleMap = allJobPosition.stream().collect(Collectors.toMap(JobPosition::getJpId, JobPosition::getRole));
} else {
// 非根节点取组织表内职务中的role信息
roleMap = orgService.getJobPositionRoleMap(orgId);
}
List<JobPositionSettingVO> settingList = constructSettingList(jobPositionList, numMap,
reverseMap, roleMap, rangeMap);
List<JobPositionPersonVO> personList = constructPersonList(jpId, jobPositionList,
reverseMap, orgId);
result.setSettingList(settingList);
result.setPersonList(personList);
return result;
}

使用MongoRepository完成CURD和复杂查询

与HibernateRepository类似,通过继承MongoRepository接口,我们可以非常方便地实现对一个对象的增删改查,要使用Repository的功能,先继承MongoRepository<T, TD>接口,其中T为仓库保存的bean类,TD为该bean的唯一标识的类型,一般为ObjectId。之后在service中注入该接口就可以使用,无需实现里面的方法,spring会根据定义的规则自动生成。

1
2
3
4
5
6
public interface PersonRepository extends 

interface DictionaryInfoDao : MongoRepository<DictionaryInfo?, String?> {
fun findByDictTypeCode(name: String): DictionaryInfo?
//这里可以添加需要用到的查询方法、以及自定义额外的查询方法
}

但是MongoRepository实现了的只是最基本的增删改查的功能,要想增加额外的查询方法,可以按照以下规则定义接口的方法。自定义查询方法,格式为findBy+字段名+方法后缀,方法传进的参数即字段的值,此外还支持分页查询,通过传进一个Pageable对象,返回Page集合。

1
2
3
4
5
6
public interface PersonRepository extends 

MongoRepository<Person, ObjectId>{
//查询大于age的数据
public Page<Product> findByAgeGreaterThan(int age,Pageable page) ;

from : MongoRepository基本方法

尽管以上查询功能已经很丰富,但如果还不能满足使用情况的话可以用一下方法——基于mongodb shell查询语句的查询方式,即在DAO接口中加入

1
2
@Query("{ 'name':{'$regex':?2,'$options':'i'}, sales': {'$gte':?1,'$lte':?2}}") 
public Page<Product> findByNameAndAgeRange(String name,double ageFrom,double ageTo,Pageable page);

注解Query里面的就是mongodb原来的查询语法,我们可以定义传进来的查询参数,通过坐标定义方法的参数。

还可以在后面指定要返回的数据字段,如上面的例子修改如下,则只通过person表里面的name和age字段构建person对象。

1
2
@Query(value="{ 'name':{'$regex':?2,'$options':'i'}, sales':{'$gte':?1,'$lte':?2}}",fields="{ 'name' : 1, 'age' : 1}") 
public Page<Product> findByNameAndAgeRange(String name,double ageFrom,double ageTo,Pageable page);

对比mongotemplate和mongoRepository

1MongoTemplate

MongoTemplate 遵循Spring中的标准模板模式,并为底层持久性引擎提供准备就绪的基本API。

1
2
3
4
5
6
7
// 1. 校验添加的物资类型是否存在
val exists = mongoTemplate.exists(
Query.query(
Criteria.where
("materialTypeCode").`is`(materialInfo.materialTypeCode)
), DictionaryInfo::class.java
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
val criteria = Criteria().andOperator(
Criteria.where("parentId").`is`(materialTypeId),
Criteria.where("unitId").`is`(unitId)
)
val query: Query = Query.query(criteria)
val list: MutableList<MaterialType> = mongoTemplate.find(query, MaterialType::class.java)
val list2: MutableList<MaterialInfo>? = mutableListOf()
list.forEach {
val query2: Query = Query.query(Criteria.where("id").`is`(it.itemId))
val value = mongoTemplate.findOne(query2, MaterialInfo::class.java)
if (value != null) {
list2.add(value)
}
}

2MongoRepository

以Spring Date为中心的方法,基于所有Spring数据项目中众所周知的访问模式,提供更加灵活和复杂的api操作。

mongoRepository,MongoTemplate

Spring boot集成mongodb使用MongoRepository完成CURD和复杂查询

Spring Data JPA方法名命名规则

  • findByOrderByDictTypeCodeDescAndSortNumDesc失败

  • findAllByOrderByIdDesc和findByOrderByIdDesc 效果一样

  • dictionaryInfoDao.findAllByOrderByIdDesc(Sort.by(Sort.Direction.DESC,"sortNum"));后面的排序无效

  • 1
    2
    3
    4
    5
    findAllByOrderByIdDescAndOrderBySortNumDesc  
    findAllByOrderByIdDescAndSortNumDesc(Sort sort)
    dictionaryInfoDao.findAll(Sort.by("dictTypeCode"), Sort.by("sortNum"));

    失效

MyBatis与JPA的区别是什么

JPA是JAVA持久层API的规范, JPA仅仅定义了一些接口,而接口是需要实现才能工作的。 所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。

Mybatis优势

  • MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
  • MyBatis容易掌握,而Hibernate门槛较高。

Hibernate优势

  • Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。

  • Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。

  • Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。

  • Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳

  • fun findAllByDictTypeCode(dictTypeCode: String): List<DictionaryInfo> 返回的是个List, 如果没有值则为空列表

  • 如果findById的结果是个Optional, 如果自定义findByXxxx方法时,返回值可以定义为Optional<XxxxDO>也可以直接定义成 XxxxDO?

Author: Mrli

Link: https://nymrli.top/2021/12/02/Mongodb学习/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
前端进行网络请求的三种方式
NextPost >
Vue-Router细节记录
CATALOG
  1. 1. Mongodb学习
    1. 1.1. 创建数据库-集合(表)
    2. 1.2. 插入文档
    3. 1.3. 查询find:
    4. 1.4. 聚合查询aggregate
      1. 1.4.1. 管道
    5. 1.5. 操作内嵌文档
    6. 1.6. 操作数组
      1. 1.6.1. 对数组根据条件查询
      2. 1.6.2. 对数组内嵌文档查询
    7. 1.7. 使用MongoRepository完成CURD和复杂查询
    8. 1.8. 对比mongotemplate和mongoRepository
      1. 1.8.0.1. 1MongoTemplate
      2. 1.8.0.2. 2MongoRepository
  2. 1.9. MyBatis与JPA的区别是什么
    1. 1.9.1. Mybatis优势
    2. 1.9.2. Hibernate优势