spring data mongodb dbref 关联查询

时间:2022-05-06
本文章向大家介绍spring data mongodb dbref 关联查询,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

今天我们学习下DBRef的使用,用过mongodb的都知道mongodb不能做关联查询,关系型数据库中是可以的,当然我们不要用关系型数据库的思想来用nosql。

但是实际应用中也是会有类似的需求的。

我们就以学生和班级的关系来讲解一对一以及一对多的关联操作。

  • 一个班级有多个学生,班级对学生是一对多的关系
  • 一个学生属于一个班级,学生对班级是一对一的关系

如果用mysql那么就是下面2张表: 班级表: classId className 学生表: studentId studentName classId

查询学生信息带出班级信息的查询也方便

select * from 班级 inner join 学生 on 班级.classId=学生.classId

用mongodb要如何设计集合呢???

班级集合中嵌套学生信息:

{
    "_id": ObjectId("57fa3f22d4c63d5f5c416ee2"),
    "className": "五年级一班",
    "students": [
        {
            "studentId": "1001",
            "studentName": "张三"
        },
        {
            "studentId": "1002",
            "studentName": "张三2"
        }
    ]
}

上面的嵌套在学生数量有限的情况下是可以的,如果量大超过16M的时候就不适用了,学生有很多信息,我这边只列了简单的。

为了减少文档的大小,那么能不能像mysql一样,之存储id然后做关联呢?

在mongodb中可以使用DBRef来关联

定义要用到的实体类

@Document
public class Class {
    @Id
    private String id;
    //班级名称
    private String className;
    //开班时间
    private Date openDate;
    //引用学生信息
    @DBRef
    private List<Student> students;
}

@Document
public class Student {
    @Id
    private String id;
    //学生姓名
    private String stuName;
    //引用班级
    @DBRef
    private Class classObj;
}

保存数据的时候先保存班级数据,班级有了学生对象中的班级才能引用到,因为引用是通过_id来的。

Class classObj = new Class();
classObj.setClassName("五年级一班");
classObj.setOpenDate(new Date());
mongoTemplate.save(classObj);

Student student = new Student();
student.setStuName("张学生");

student.setClassObj(classObj);

mongoTemplate.save(student);

我们可以看到保存后的数据在学生集合中有DBRef引用class中的57fa4b99d4c68bb7d044d616

db.class.find();
{
    "_id": ObjectId("57fa4b99d4c68bb7d044d616"),
    "className": "五年级一班",
    "openDate": ISODate("2016-10-09T13:52:25.678Z")
}
db.student.find();
{
    "_id": ObjectId("57fa4b99d4c68bb7d044d617"),
    "stuName": "张学生",
    "classObj": DBRef("class",
    ObjectId("57fa4b99d4c68bb7d044d616"))
}

然后我们查询这个学生的信息就可以自动带出班级的信息了,用过hibernate的一看就知道哈。。

//自动带出班级信息
Student student = mongoTemplate.findOne(Query.query(Criteria.where("stuName").is("张学生")), Student.class);
System.out.println(student.getStuName() + "t" + student.getClassObj().getClassName());

上面将的是一对一的操作,一对多的话就比较麻烦了

Class classObj = new Class();
classObj.setClassName("五年级二班");
classObj.setOpenDate(new Date());

List<Student> students = new ArrayList<>();
Student student = new Student();
student.setStuName("李学生");
student.setClassObj(classObj);
students.add(student);

Student student2 = new Student();
student2.setStuName("王学生");
student2.setClassObj(classObj);
students.add(student2);

classObj.setStudents(students);

mongoTemplate.save(student);
mongoTemplate.save(student2);
mongoTemplate.save(classObj);

我们看上面这段代码,大家觉得这段代码能执行成功吗?不能...

上面也说了,引用一定要引用已经插入到数据的数据。

这边先保存学生信息,学生中引用了班级,班级还没保存

先保存班级信息的话,班级中引用了学生,学生此时还没保存

如果引用没保存的信息就会报错

Exception in thread "main" org.springframework.data.mapping.model.MappingException: Cannot create a reference to an object with a NULL id.

这样不行,我们就只能曲线救国了

从业务上来说首先肯定是开班级,班级有了再招生

我们就利用上面已经存在的五年级一班来添加学生

Student student = new Student();
student.setStuName("李学生");
Class classObj = mongoTemplate.findOne(
   Query.query(Criteria.where("className").is("五年级一班")),Class.class);
student.setClassObj(classObj);
mongoTemplate.save(student);

List<Student> students = classObj.getStudents();
if (students == null) {
    students = new ArrayList<>();
}
students.add(student);
classObj.setStudents(students);
mongoTemplate.save(classObj);

在李学生加入班级后,马上把班级集合中的学生List对象改掉,这样班级中也就存在了对这个学生的引用信息, 这样就会比较麻烦。

{
    "_id": ObjectId("57fa4b99d4c68bb7d044d616"),
    "className": "五年级一班",
    "openDate": ISODate("2016-10-09T13:52:25.678Z"),
    "students": [
        DBRef("student",
        ObjectId("57fa4f59d4c6731d0c83f933"))
    ]
}

我们在查询班级的时候就可以关联出这个班级下所有的学生信息了

Class classObj = mongoTemplate.findOne(
   Query.query(Criteria.where("className").is("五年级一班")),Class.class);
classObj.getStudents().forEach(stu -> {
   System.err.println(stu.getStuName());
});

搞个一对多这么麻烦,还不如不弄呢,,不要急,条条大路通罗马,这条路不通,还有别的路啊。

我们的需求无非就是想知道某个班级下有多少个学生吗?如果不用关联的话就自己查呗,查的话我们没在学生集合中单独存储班级的id啊,引用里不是有id吗,就用那个查,但是要注意语法classObj.$id

mongoTemplate.find(
    Query.query(Criteria.where("classObj.$id")
         .is(new ObjectId("57fa4b99d4c68bb7d044d616"))), Student.class)
    .forEach(stu -> {
        System.err.println(stu.getStuName());
    });

源码地址:https://github.com/yinjihuan/cxytiandi