mongoDB调研

mongoDB和CouchDB的区别

特性 CouchDB MongoDB
数据模型 它遵循面向文档的模型,数据以JSON格式表示。 它遵循面向文档的模型,但数据以BSON格式表示
接口 CouchDB使用基于HTTP/ REST的接口。它非常直观,设计非常好。 MongoDB在TCP/IP上使用二进制协议和自定义协议。
对象存储 在CouchDB中,数据库包含文档。 在MongoDB中,数据库包含集合,而集合包含文档。
速度 它的读取速度是关键的数据库,MongoDB比CouchDB快 MongoDB提供了更快的读取速度。
手机支持 CouchDB可以运行在苹果iOS和Android设备上,为移动设备提供支持。 没有提供移动支援
大小 数据库可以随着CouchDB而增长;当结构从一开始就没有明确定义时,MongoDB更适合快速增长。 如果我们有一个快速增长的数据库,MongoDB是更好的选择。
查询方法 查询使用map-reduce函数。虽然它可能是一种优雅的解决方案,但对于具有传统SQL经验的人来说,学习它可能更加困难。 MongoDB采用Map/Reduce (JavaScript)创建基于集合+对象的查询语言。对于有SQL知识的用户,MongoDB更容易学习,因为它更接近语法。
复制 CouchDB支持使用自定义冲突解决功能的主-主复制。 MongoDB支持主从复制。
并发性 它遵循MVCC(多版本并发控制)。 就地更新。
首选项 CouchDB支持可用性。 MongoDB支持一致性
性能的一致性 CouchDB比MongoDB更安全
一致性 CouchDB最终是一致的。 MongoDB是强一致性的。
编写语言 Erlang C++.
分析 如果我们需要一个在移动设备上运行的数据库,需要主-主复制或单服务器持久性,那么CouchDB是一个很好的选择。 如果我们正在寻找最大的吞吐量,或者有一个快速增长的数据库,MongoDB是最好的选择。

mongodb的调研

mongodb是面向文档的nosql。

从总体上看

  1. mongodb是最亲和RDBMS的一个NoSQL,能解决大部分关系型数据库解决的问题。
  2. 跟面向列存储的HBase相比,面向文档存储的和面向行存储比较接近,比如在没有索引的情况下,扫描整个表内记录,同样是扫面全文档,及文档的每个字段。
  3. mongodb的索引同样也是B树,在一些索引的优化和设计上和MySQL比较相似。
  4. mongodb的查询中也有mysql的in,where,group等字段,而且向group那样的查询会比mysql更强大

mongodb的数据冗余机制

Mongodb的数据冗余机制分为两种,主从复制(Master-Slave)和副本集(ReplicaSet)的方式。这两种操作方式都是通过回放主节点操作日志的异步数据同步方式。

主节点会将操作日志记录到oplog中,从节点从主节点请求oplog日志,然后在节点上回放来构建数据;如果主节点和从节点数据超过oplog的范围,则需要从节点和主节点先进行sync后再反演oplog日志。

ReloicaSet是在Master-Salve同步机制的基础上加上了选举机制。一个ReplicaSet中的各个节点之间有心跳信息,当主节点失败时,从节点发起一轮主节点的选取过程,重新选出一个主节点。主节点的选取跟各个节点的配置优先级和数据的更新程度有关。选主节点先考虑优先级,再考虑拥有最新数据的节点,然后选出一个节点作为新的主节点。各个节点和主节点进行数据同步,旧的主节点重新加入ReplicaSet后,会作为从节点与新的主节点进行数据同步。

Mongodb通过这种操作日志回放进行数据同步的方式保证数据的最终一致性。主节点为了数据的安全性加入了journal机制,但是由于不同节点之间的数据是异步同步的,在主节点宕机或者网络故障时有可能丢失数据。

mongodb的数据分片机制

Nosql系统的水平拓展性依赖于其数据的分片机制和数据迁移机制。Mongodb集群通过自动分片机制来进行数据的分片和数据迁移。

mongodb的存储结构

Mongodb将数据按照数据库(database),集合(collection)和文档(document)的方式进行存储,分别对应RDBMS中的数据库,表和记录的概念。Mongodb为每个数据库建立一个namespace(.ns结尾)文件,存储该数据库的元信息,比如该数据都有那些collections,各个collcetions的数据文件,索引文件信息等。

MongoDB的记录是个文档,它是由字段对值的数据结构,类似JSON,同时字段的值可以包括其他文档,数组和文档数据,即文档的嵌套。

Mongodb每个db中有多个collection,相当于table,每个collection内是许许多多的document,Mongdb对数据格式要求宽松,因此document的schemaless的。

同时mongodb的所有数据都存放在文件中,Mongodb的文件采用预分配的方式来避免磁盘碎片化和保证操作性能。第一次分配数据文件是64M,后续分配文件的数据指数级递增,知道预分配的数据文件大小达到2G。

Mongodb通过mmap来操作所有磁盘文件,Mongodb启动时将所ns文件和所有数据文件通过mmap的方式全部映射到进程逻辑地址空间,后续通过内存的操作来操作物理文件。

Mongodb存储引擎的优缺点

Mongodb采用mmap将存储文件全部映射到进程逻辑地址空间,通过操作系统的page cache 和 缺页中断机制来实现数据在内存和磁盘之间的迁移。

这种实现结构是将系统的内存作为存储系统的缓存,而缓存的管理完全由操作系统的界面缓存机制来管理,大大简化了Mongodb的存储引擎设计和实现。

这样做的优点是在系统内存充足的时候,会将所有的数据和索引文件都放在内存中,读写速度会非常快。另外,由于将内存管理任务交给操作系统的界面缓存来完成,使得Mongodb的存储引擎实现简单明了。

缺点:

  1. 通过mmap的形式将索引和数据文件全部映射到进程地址空间,使用页缓存来管理内存,是的mongodb会占用大量的内存,随着数据的增加,系统内存都会被mongodb占用。
  2. mongodb再数据和索引的量超过系统内存时,会造成大量的缺页中断,导致数据在磁盘和内存之间频繁换入换出,增加磁盘的IO压力,增加访问延迟。

对mongodb单节点的性能测试

测试机器为:VMWare虚拟机CentOS6.4 CPU:4核 内存:8G

当数据量达到千万级的时候最大访问时间小于1ms,访问性能很好;

当数据量达到4亿级的时,多客户端访问时间最大达到6s,在数据集过大的时候,mongodb会造成大量的磁盘IO和频繁的内存换入换出,导致mongodb的性能下降。

考虑到使用mongodb的数据量不会很大,超过上亿级别,加上mongodb在千万级数据量的时候访问速度很好,所以选择使用mongodb。

MongoDB安装

windows 下载地址

一路直接安装即可,自行修改下载路径,下的时候选择自定义安装,安装完毕之后需要在server/bin/data目录下创建一个db文件夹

添加环境变量

找到下载mongodb的bin目录 E:\env\mongodb\bin将该目录添加到系统变量的path目录中。

image-20220610110905817

运行mongoDB

打开一个cmd窗口

1
2
mongod -dbpath "E:\env\mongodb\data\db"
# 运行此行命令运用一直开启mongoDB服务,但是命令行窗口不能关闭

image-20220610111228148

之后在浏览器上运行 localhost:27017
image.png

运行之后在重新打开一个cmd指令窗口

1
输入 mongo  即可对mongodb数据库进行操作

image-20220610111203834

安装mongoDB compass 可视化界面

可视化界面

是一个压缩包,解压后,直接运行里面的exe即可打开界面,然后点击connect ,mongoDB默认的端口是:https://localhost:27017/ ,连接之后是如下界面
img

mongoDB的基本命令

1
2
3
## 创建数据库
use DATABASE_NAME // 如果数据库不存在,就创建数据库,否则进入到指定的数据库
## 但是刚刚创建的数据库没有显示在所有数据库中,因为还没有插入数据,插入数据后即可显示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
删除数据库 
db.dropDatabase() #需要先进入数据库中在进行删除

删除数据库中的集合
db.collection.drop()

对数据库进行插入文档
db.COLLEECTION_NAME.insert(document

COLLECTION_NAME 是文档的名字。

查看已经插入的文档
db.COLLECTION_NAME.find()
db.COLLECTION_NAME.find().pretty() #出现的文档信息更有条理

还可以将数据变成一个变量再进行插入

1
2
3
4
5
6
7
> document=({title: 'MongoDB 教程', 
description: 'MongoDB 是一个 Nosql 数据库',
by: 'w3cschool',
url: 'http://www.w3cschool.cn',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
});

执行后的结果是

1
2
3
4
5
6
7
8
9
10
11
12
{
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "w3cschool",
"url" : "http://www.w3cschool.cn",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}

进行插入操作

1
2
3
> db.col.insert(document)
WriteResult({ "nInserted" : 1 })
>

数据库的备份导出

新打开一个cmd窗口

1
2
3
4
5
6
7
8
9
10
11
12
mongodump -h dbhost -d dbname -o dbdirectory

## 备份整个数据库

- -h:
MongoDB 所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017

- -d:
需要备份的数据库实例,例如:test

- -o:
备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。

数据导出可能遇到的问题

uncaught exception: SyntaxError: unexpected token: numeric literal : @(shell):1:13

原因:下载mongodb的时候没有下载对应的mongodump的exe,所以会报错

解决办法:

下载文件的bin目录中所有文件, 复制到mongodb的bin目录, 就可以用命令导入了
https://www.mongodb.com/try/download/database-tools

springboot 整合 Mongodb

  1. 导入pom依赖
  2. 修改配置文件 properties
  3. 编写实体类 pojo
  4. 编写 dao层和dao的实现类
  5. 编写service层和service的实现
  6. 编写controller
  7. 测试

导入pom依赖

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
<dependencies>
<!--mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--json转换-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.2</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

修改配置文件

1
2
# mongodb配置
spring.data.mongodb.uri=mongodb://localhost:27017/Test

编写实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "test1")
public class test1 {

@Id
private String _id;
private int id;
private String name;
private int age;

private List<test2> test2List;

}
1
2
3
4
5
6
7
8
9
10
11
12
@Document(collection = "test2")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class test2 {

@Id
private String _id;
private int id;
private String name;

}

编写dao层

dao层接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface SiteFlowDao {

//增
public void add(test1 s1);

//删
public void remove(int id);

//根据名字查找
public test1 findByName(String name);

//更新
public int update(test1 s1);

//多表联查
public List<test1> findListByTest();

}

dao层接口实现

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
@Component
public class SiteFlowDaoImpl implements SiteFlowDao{

@Autowired
MongoTemplate mongoTemplate;


@Override
public void add(test1 s1) {
mongoTemplate.save(s1);
}

@Override
public void remove(int id) {
Query query = new Query(Criteria.where("id").is(id));
mongoTemplate.remove(query,test1.class);
}

@Override
public test1 findByName(String name) {
Query query = new Query(Criteria.where("name").is(name));
test1 one = mongoTemplate.findOne(query, test1.class);
return one;
}

@Override
public int update(test1 s1) {
Query query = new Query(Criteria.where("id").is(s1.getId()));
Update update = new Update();
update.set("name",s1.getName()).set("age",s1.getAge());
UpdateResult updateResult = mongoTemplate.updateMulti(query, update, test1.class);
if(updateResult.getModifiedCount()>0) return 1;
return 0;
}

@Override
public List<test1> findListByTest() {

LookupOperation lookupOperation = LookupOperation.newLookup()
.from("test2") //关联从表名
.localField("id") //主表关联字段
.foreignField("id")//从表关联的字段
.as("test2List"); //查询结果名
// 获取指定字段的数据 ProjectionOperation project = Aggregation.project("id","name","age");

Criteria criteria = Criteria.where("test2List").not().size(0); // 为输出的list 添加条件
AggregationOperation match = Aggregation.match(criteria);
//UnwindOperation unwind = Aggregation.unwind("test2List"); // 分解字段 , 例如 将数组大小为n的元素,分解为n个文档
Aggregation aggregation = Aggregation.newAggregation(lookupOperation,match);
List<test1> deto = mongoTemplate.aggregate(aggregation, "test1",test1.class).getMappedResults();
//// 上面 test1 必须是 主表的名字
//for (test1 test1 : deto) {
// System.out.println(test1);
//}
return deto;
}
}

编写service层

service层接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface SiteFlowService {

public void add(test1 s1);

public void remove(int id);

public test1 findByName(String name);

public int update(test1 s1);

public List<test1> findListByTest();

}

service层接口实现

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
@Service
public class SiteFlowServiceImpl implements SiteFlowService{

@Autowired
SiteFlowDao siteFlowDao;

@Override
public void add(test1 s1) {
siteFlowDao.add(s1);
}

@Override
public void remove(int id) {
siteFlowDao.remove(id);
}

@Override
public test1 findByName(String name) {
return siteFlowDao.findByName(name);
}

@Override
public int update(test1 s1) {
return siteFlowDao.update(s1);
}

@Override
public List<test1> findListByTest() {
return siteFlowDao.findListByTest();
}
}

controller层

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
@RestController
public class testController {
@Autowired
SiteFlowService siteFlowService;

@RequestMapping("/add")
public String add(test1 ss1){
siteFlowService.add(ss1);
return "add";
}

@RequestMapping("/delete")
public String delete(int id){
siteFlowService.remove(id);
return "delete";
}

@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") int id){
siteFlowService.remove(id);
return "delete";
}

@RequestMapping("/update")
public String update(test1 ss1){
siteFlowService.update(ss1);
return "update";
}

@RequestMapping("/TwoList")
public String test(){

List<test1> listByTest = siteFlowService.findListByTest();
System.out.println("======================="+listByTest+"============================");
return "list";

}
}

springboot使用Mongodb进行CRUD

增加

1
2
3
4
5
6
7
8
9
10
11
12
void add(){
test1 s1 = new test1();
s1.setId(3);
s1.setName("ff");
s1.setAge(18);
mongoTemplate.save(s1);

List<test1> all = mongoTemplate.findAll(test1.class);
for (test1 ss1 : all) {
System.out.println(ss1);
}
}

删除

1
2
3
4
5
6
void delete(){

Query query = new Query(Criteria.where("siteflow_author").is("阿斯兰"));
mongoTemplate.remove(query,siteflow.class);

}

更新

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
void update(){
//更新
/**
* updateFirst 只更新满足条件的第一条数据
* updateMulti 更新所有满足条件的数据
* upsert 没有符合条件的记录就插入数据
*
* getMatchedCount 匹配的记录数
* getModifiedCount 修改的记录数
*
*/
Update update = new Update();
update.set("siteflow_author","李亚辉");
Query query4 = new Query(Criteria.where("id").gte(154));
UpdateResult updateResult = mongoTemplate.updateMulti(query4, update, siteflow.class);
System.out.println("=====>"+updateResult.getMatchedCount()); // 匹配的记录数
System.out.println("=====>"+updateResult.getModifiedCount()); // 修改的记录数

// 查询所有
List<siteflow> all = mongoTemplate.findAll(siteflow.class);
for (siteflow siteflow : all) {
System.out.println(siteflow);
}

}

查找

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
void contextLoads() {

// 查询所有
List<siteflow> all = mongoTemplate.findAll(siteflow.class);
for (siteflow siteflow : all) {
System.out.println(siteflow);
}

// 根据具体内容查询
/**
* gte:大于等于 gt:大于 is 等于 lte 小于等于 lt 小于
* 正则查询 regex 模糊查询
*
* 排序:
* query.with(Sort.by(Sort.Order.desc("id")));
* 分页
* query.skip(0) // 跳过指定记录数
* .limit(4); // 每页显示记录数
*/

Query query1 = new Query(Criteria.where("id").gte(155).lte(155));
Query query2 = new Query(Criteria.where("siteflow_author").is("lks"));
Query query3 = new Query(Criteria.where("park").regex("廊"));

Criteria criteria = new Criteria();
Query query = new Query(criteria);
query.with(Sort.by(Sort.Order.desc("id")));

query.skip(0) // 跳过指定记录数
.limit(4); // 每页显示记录数


List<siteflow> siteflows = mongoTemplate.find(query3, siteflow.class);
for (siteflow siteflow : siteflows) {
System.out.println("=======>"+siteflow);
}
}

模糊查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test3(){
String siteflow_name = "北京站点456867";
String siteflow_author = "李亚辉";
String reges = String.format("%s%s%s","^.*",siteflow_name,".*$");
String reges2 = String.format("%s%s%s","^.*",siteflow_author,".*$");
Pattern pattern = Pattern.compile(reges,Pattern.CASE_INSENSITIVE);//正则匹配
Pattern pattern2 = Pattern.compile(reges2,Pattern.CASE_INSENSITIVE);
Criteria cr = new Criteria();
Query query = new Query();
if(siteflow_author!=null&&siteflow_author!=""){
cr.and("siteflow_author").regex(pattern2);
}
if(siteflow_name!=null&&siteflow_name!=""){
cr.and("siteflow_name").regex(pattern);
}
query.addCriteria(cr);
List<SiteFlow> siteflow = mongoTemplate.find(query, SiteFlow.class, "siteflow");
System.out.println(siteflow);
}

多表联查

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
void select(){
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("test2") //关联从表名
.localField("id") //主表关联字段
.foreignField("id")//从表关联的字段
.as("test2List"); //查询结果名
// 获取指定字段的数据 ProjectionOperation project = Aggregation.project("id","name","age");

Criteria criteria = Criteria.where("test2List").not().size(0); // 为输出的list 添加条件
AggregationOperation match = Aggregation.match(criteria);
UnwindOperation unwind = Aggregation.unwind("test2List"); // 分解字段 , 例如 将数组大小为n的元素,分解为n个文档
Aggregation aggregation = Aggregation.newAggregation(lookupOperation,match);
Aggregation aggregation1 = Aggregation.newAggregation(lookupOperation,match,unwind);
List<test1> deto = mongoTemplate.aggregate(aggregation, "test1",test1.class).getMappedResults();
List<Map> deto1 = mongoTemplate.aggregate(aggregation1, "test1",Map.class).getMappedResults();
// 上面 test1 必须是 主表的名字
for (test1 test1 : deto) {
System.out.println(test1);
}

System.out.println("====================================");

for (Map map : deto1) {
System.out.println(map);
}
}

输出结果:

image-20220709213220673

Mongodb两表联查

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
@Test // 两表联查
/**
* mongodb 不能像 mysql一样,能在实体类中直接引用别的实体类,并查询到(或者我还没发现怎么搞)
* 例如 : private User user; 这样在多表联查中mongodb会报错
* 如果想要显示 联查后其他实体类的数据 ,可以 private List<User> user; 联查后数据会注入在 user 的数组中,但是如何取出来里面的具体数值很麻烦
*
* 所以使用了创建第三个实体类作为中介
* 例如 : test1 和 test2 联查,则将test1 和test2 整合(不同的元素,相同的元素只取一个)到一个新建的类 sum中,在mongodb联查的时候,直接将数据注入在 sum类中
* 注意: 新建的sum整合类中,int 类型 要换为 Integer 类型,不然会报错
*
* 把要在sum中显示的元素 取出来, test2List是查询的结果名 后面的后缀是 从表中的要显示的数据 不写 前缀 test2List 。结果会为空查不出来
* ProjectionOperation project = Aggregation.project(
"id","name",
"test2List.siflow_id","test2List.external_port","test2List.ternal_port");
*/
void select(){
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("test6") //关联从表名
.localField("id") //主表关联字段
.foreignField("id")//从表关联的字段
.as("test2List"); //查询结果名

// 把要在 sum类中显示的数据
ProjectionOperation project = Aggregation.project("id","name","test2List.siflow_id","test2List.external_port","test2List.ternal_port");

Aggregation aggregation = Aggregation.newAggregation(lookupOperation,project);
Aggregation aggregation1 = Aggregation.newAggregation(lookupOperation,project);
List<Sum2> deto = mongoTemplate.aggregate(aggregation, "test1",Sum2.class).getMappedResults();
List<Map> deto1 = mongoTemplate.aggregate(aggregation1, "test1",Map.class).getMappedResults();
// 上面 test1 必须是 主表的名字
for (Sum2 test1 : deto) {
System.out.println(test1);
}

System.out.println("====================================");

for (Map map : deto1) {
System.out.println(map.get("siflow_id"));
}
}

结果

image-20220711133201615

三表联查

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
void test2(){
Criteria criteria = new Criteria();
criteria.orOperator(Criteria.where("siteflow_name").is("北京站点456867"));
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("server_configure") // 被关联表
.localField("id") // 主表关联字段
.foreignField("siteflow_id") // 被关联字段
.as("ss"); // 别名
LookupOperation lookupOperation2 = LookupOperation.newLookup()
.from("system_number") // 被关联表
.localField("id") // 主表关联字段
.foreignField("id") // 被关联字段
.as("sa"); // 别名

ProjectionOperation project = Aggregation.project("id");
ProjectionOperation project2 = Aggregation.project("id","sa.system_name","siteflow_id");

Map2Bean<SiteFlowSum> sumMap2Bean = new Map2Bean<>();
Aggregation aggregation = Aggregation.newAggregation(lookupOperation,Aggregation.unwind("ss")
,Aggregation.match(criteria),project);
Aggregation aggregation2 = Aggregation.newAggregation(lookupOperation2,Aggregation.unwind("sa")
,Aggregation.match(Criteria.where("sa").ne(null)),project2);
// siteflow 和 server_configure 联查 得到 siteflow 的id === siteflow_id
List<Map> siteflow1 = mongoTemplate.aggregate(aggregation, "siteflow", Map.class).getMappedResults();
// 得到 siteflow_id 和 system_name
List<Map> siteflow2 = mongoTemplate.aggregate(aggregation2, "server_configure", Map.class).getMappedResults();
// 合成的新数组
List<Map> test = new ArrayList<>();

for (Map map : siteflow1) {
String i = map.get("id").toString();
Integer h = Integer.valueOf(i);
Query query = new Query(Criteria.where("siteflow_id").is(h));
List<SiteServerConfigure> siteServerConfigures = mongoTemplate.find(query, SiteServerConfigure.class);

// 根据 siteserver 和 system_number 之间的关系 来进行 判断
for (SiteServerConfigure siteServerConfigure : siteServerConfigures) {
for (Map map1 : siteflow2) {
String ih = map1.get("id").toString();
if(siteServerConfigure.getId() == Integer.valueOf(ih)){
Map<String,Object> map2 = new HashMap<>();
map2.put("id",map.get("id"));
map2.put("system_name",map1.get("system_name"));
test.add(map2);
}
}
}
}

SiteFlowSum convert = sumMap2Bean.convert(test.get(0), SiteFlowSum.class,true,true);
System.out.println(convert);
System.out.println("=======================");
//
//System.out.println(siteflow1);
//System.out.println("==============================");
//System.out.println(siteflow2);
//System.out.println("===============================");
System.out.println(test);
}

结果

image-20220711132914156