由于在实际开发项目中,突然涉及到了需要Mongodb 进行连表操作的业务,所以我在私下进行了一波学习。
服务器中 mongodb数据库安装以及密码设置请见:Centos7 使用Yum源安装MongoDB4.2版本数据库(补:密码配置)
springboot-mongodb 单数据源的的CRUD 以及批量操作请见:SpringBoot整合MongoDB(一)
使用多数据源以及Aggregation 管道对mongo 进行聚合函数操作(分组,统计,分页等等)请见:SpringBoot整合MongoDB(二)多数据源配置,Aggregation管道使用
使用MongoTemplate连表查询主要是使用LookupOperation 确定主表 从表 主表关联字段以及从表关联字段等。
话不多说,直接开战。
本文中暂时学习了 一对一 (两张表/多张表) 一对多(多对一) ,我呢,总共是准备了4张表
学生表
@Data
@Document(collation = "student")
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {
/*** 自定义mongo主键 加此注解可自定义主键类型以及自定义自增规则
* 若不加 插入数据数会默认生成 ObjectId 类型的_id 字段
* org.springframework.data.annotation.Id 包下
* mongo库主键字段还是为_id 。不必细究(本文实体类中为id)
*/
@Id
private Long id;
private String username;
/**
* 关联班级ID
*/
private Long classId;
}
班级表
@Data
@Document(collation = "studentClass")
@NoArgsConstructor
@AllArgsConstructor
public class StudentClass implements Serializable {
@Id
private Long id;
private String className;
/**关联学校*/
private Long schoolId;
}
学校表
@Data
@Document(collation = "school")
@NoArgsConstructor
@AllArgsConstructor
public class School implements Serializable {
@Id
private Long id;
private String schoolName;
/**关联城市ID*/
private Long cityId;
}
城市表
@Data
@Document(collation = "city")
@NoArgsConstructor
@AllArgsConstructor
public class City implements Serializable {
@Id
private Long id;
private String cityName;
}
无关数据真实性以及业务逻辑性设计
一个学生对应一个班级(多个学生对应一个班级)无所谓,代码主要是进行连表测试。
添加测试数据
public JsonReturn addData() {
List<Student> students = Arrays.asList(
new Student(1L, "小明", 1L),
new Student(2L, "小红", 2L),
new Student(3L, "小菜", 2L));
List<StudentClass> studentClasses = Arrays.asList(
new StudentClass(1L, "三年级一班", 1L),
new StudentClass(2L, "三年级二班", 2L));
List<School> schools = Arrays.asList(
new School(1L, "旺仔小学", 1L),
new School(2L, "蒙牛小学", 1L));
City city = new City();
city.setId(1L);
city.setCityName("希望市");
try {
mongoTemplate.insertAll(students);
mongoTemplate.insertAll(studentClasses);
mongoTemplate.insertAll(schools);
mongoTemplate.save(city);
HashMap<String, Object> map = new HashMap<>(4);
map.put("student", students);
map.put("studentClass", studentClasses);
map.put("schools", schools);
map.put("city", city);
return JsonReturn.buildSuccess(map);
} catch (Exception e) {
e.printStackTrace();
return JsonReturn.buildFailure("error");
}
}
学生表与班级表进行联查 以血学生为主表(即查询第一视角) 多对一查询
public JsonReturn MoreToOne() {
LookupOperation lookup = LookupOperation.newLookup()
//从表(关联的表)
.from("studentClass")
//主表中与从表相关联的字段
.localField("classId")
//从表与主表相关联的字段
.foreignField("_id")
//查询出的从表集合 命名
.as("class");
Aggregation agg = Aggregation.newAggregation(lookup);
try {
AggregationResults<Map> studentAggregation = mongoTemplate.aggregate(agg, "student", Map.class);
return JsonReturn.buildSuccess(studentAggregation.getMappedResults());
} catch (Exception e) {
e.printStackTrace();
return JsonReturn.buildFailure("error");
}
}
需要注意的是: .as是查询出的从表数据结果集的名字 ,类似于mysql 中 在一个实体类中要设置一个关联对象类型字段 那么我们获取其相关关联对象信息的时候只需要.关联对象.字段 即可 我们mongodb 获取关联对象信息则是.as取得名字.字段名
mongoTemplate.aggregat()中 表选择必须是主表(以谁为第一视角则谁当主表),我这里以student为主表 那么我查询后信息的第一视角则为Student
那么这只是普通的查询,并无任何搜索条件,我们如何根据条件进行结果筛选呢?
一样的使用match即可,主表条件与原来一致,使用我们只是需要关心,如何根据从表的数据进行查询。
那么,我们就来设计一下,主表从表查询条件,比如根据班级Id 或者学生Id 进行查询
定义一个接口,入参可选择学生id 或者班级Id
public JsonReturn MoreToOne(Long studentId, Long classId) {
LookupOperation lookup = LookupOperation.newLookup()
//关联的从表名字
.from("studentClass")
//主表中什么字段与从表相关联
.localField("classId")
//从表中的什么字段与主表相关联
.foreignField("_id")
//自定义的从表结果集名 与主表关联的数据归于此结果集下
.as("class");
Criteria criteria = new Criteria();
if (studentId != null) {
//主表可能选择的条件
criteria.and("_id").is(studentId);
}
//从表可能选择的条件
if (classId != null) {
//class 为我之前定义的从表结果集名
criteria.and("class._id").is(classId);
}
//将筛选条件放入管道中
MatchOperation match = Aggregation.match(criteria);
Aggregation agg = Aggregation.newAggregation(lookup, match);
try {
AggregationResults<Map> studentAggregation = mongoTemplate.aggregate(agg, "student", Map.class);
return JsonReturn.buildSuccess(studentAggregation.getMappedResults());
} catch (Exception e) {
e.printStackTrace();
return JsonReturn.buildFailure("error");
}
}
查询学生ID 为2的数据
查询班级ID 为2的数据,可以看到 小红 和小菜都是班级Id为2中的学生,因为我这里主表是学生啊,所以会有两条数据,如果我主表是以班级为查询那么就会变为一对多查询了,就只会有一条数据,其中students列表中包含小红和小明罢了
注意**:从表条件不能直接根据从表的列名 而是要通过(从表结果集名字**.**列名)作为要查询的列
既然,数据我们都获取到了,那么我们拿一下从表数据试一试?
比如在elementui中 的table组件中,在clomn中展示从表数据呢
先在控制台打印一波从表数据
http://localhost:8080/mongo/moreToOne?studentId=2 我们吧查询条件还是设为学生id为2
发现明明只是一条数据,为什么还是要给我结果集设为一个数组呢,那么取从表数据不是要 结果集[索引].字段 了?
这显然不是我们想要的结果 ,看了很久,终于发现了,可以使用Aggregation.unwind()方法来拆分一个结果集
官方说明:
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
那么我们在原来aggregation管道中加入unwind
//将筛选条件放入管道中
MatchOperation match = Aggregation.match(criteria);
Aggregation agg = Aggregation.newAggregation(lookup, match,Aggregation.unwind("class"));
我们这样写呢,就会将class 从表结果集拆分了
我们在次来查询所有,与之前相比,发现class数组中的索引,已经没有了
未使用unwind之前
使用unwind后
一个班级 对应多个学生
public JsonReturn manyToOne() {
LookupOperation lookupOperation = LookupOperation.newLookup()
//关联的表
.from("student")
//主表以什么字段与从表相关联的
.localField("_id")
//从表关联的字段
.foreignField("classId")
//定义的从表数据查询出的结果结合
.as("studentList");
Aggregation agg = Aggregation.newAggregation(lookupOperation, Aggregation.unwind("studentList"));
try {
AggregationResults<Map> studentAggregation = mongoTemplate.aggregate(agg, "studentClass", Map.class);
return JsonReturn.buildSuccess(studentAggregation.getMappedResults());
} catch (Exception e) {
e.printStackTrace();
return JsonReturn.buildFailure("error");
}
}
因为我们使用了unwind来拆分studentList 学生从表结果集 那么结果集数组就会拆成一个个对象 如果原来结果集中有多个数据那么也会根据主表数据来显示出多条数据
可以看到 三年级二班数据中的小菜和小红已经根据主表 拆分成了两条数据了 如果不使用unwind 结果又会是这样
从学生关联班级 班级关联学校 学校关联城市
多表联查 需要注意 可能当前的从表又是下一个关联关系的主表 ,那么主表中关联从表的字段就不能直接写明了,要通过之前设为的结果集.字段
需要注意的点以及代码注释我已经写得很详细了,当然我这里没有写unwind了,写出来 效果与之前一致,拆分数组为一个个具体的对象
/**
* 多表一对一 以Student为主表(第一视角)
*
* @return
*/
@Override
public JsonReturn moreTableOneToOne() {
//学生关联班级
LookupOperation lookupOne = LookupOperation.newLookup()
//关联的从表 (班级)
.from("studentClass")
//主表中什么字段与从表(班级)关联
.localField("classId")
//从表(班级)什么字段与主表关联字段对应
.foreignField("_id")
//从表结果集
.as("class");
//班级关联学校 那么此时 这两者之间 班级又是 学校的主表 班级还是学生的从表
LookupOperation lookupTwo = LookupOperation.newLookup()
//班级关联的从表(学校)
.from("school")
//主表中什么字段与从表(学校)关联 因为班级也是student从表 且已经设了结果集为class 那么主表字段也只能结果集.字段
.localField("class.schoolId")
.foreignField("_id")
.as("school");
//学校关联城市 两者之前 学校则为城市二者关联关系中的主表 学校还是班级的从表
LookupOperation lookupThree = LookupOperation.newLookup()
//学校关联的从表(城市)
.from("city")
//学校是班级的从表 且设了结果集名为school 那么要获取学校字段 也只能由之前设立的学校结果集名.字段 来获取了
.localField("school.cityId")
.foreignField("_id")
.as("city");
//将几者关联关系放入管道中 作为条件进行查询
Aggregation aggregation = Aggregation.newAggregation(lookupOne, lookupTwo, lookupThree);
try {
//注意,我这里还是以student为主表 那么查询结果第一视角(最外层)还是为student
AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation, "student", Map.class);
return JsonReturn.buildSuccess(aggregate.getMappedResults());
} catch (Exception e) {
e.printStackTrace();
return JsonReturn.buildFailure("error");
}
}
项目源码:Springboot整合Mongodb 连表查询