六、Mybatis 多对一和一对多关系的处理

10、多对一处理

10.1、环境准备

多对一的关系在数据库中十分常见,例如:一个老师教很多学生,从老师的角度来是一对多的关系,而从学生的角度来看就是多对一的关系。我们重新创建一个项目,来准备下多对一场景需要的环境数据,这里就用学生和老师作为例子。

1、新建一张学生表和老师表,设置学生表中老师id为外键:

CREATE TABLE `teacher`(
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`,`name`) VALUES (1, '秦老师')

CREATE TABLE `student`(
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	`tid` INT(10) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `fktid` (`tid`),
	CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO student(`id`,`name`,`tid`) VALUES (1, '小红', 1)
INSERT INTO student(`id`,`name`,`tid`) VALUES (2, '小明', 1)
INSERT INTO student(`id`,`name`,`tid`) VALUES (3, '小方', 1)
INSERT INTO student(`id`,`name`,`tid`) VALUES (4, '小李', 1)
INSERT INTO student(`id`,`name`,`tid`) VALUES (5, '小王', 1)

2、整理一下项目结构:

  1. 将*Mapper.xml文件移动到resources下的com/luca/dao下,避免dao包下内容杂乱,并且resources下的com.luca.dao目录和java下的com.luca.dao目录再target中回合并,这样不影响编译后查找资源。

    image-20210831113430329

  2. 在pojo中创建Student和Teacher的实体类(项目结构参考上图)

    pojo/Teacher.java

    package com.luca.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Teacher {
        private int id;
        private String name;
    }
    

    pojo/Student.java

    package com.luca.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Student {
        private int id;
        private String name;
        private Teacher teacher;
    }
    

10.2、测试使用

现在我们想要从student表中查询出学生的数据,按照之前的方式:

  1. StudentMapper

    List<Student> getStudents();
    
  2. StudentMapper.xml

    <select id="getStudents" resultType="student">
      select * from student
    </select>
    
  3. 测试MyTest

    @Test
    public void TestGetTeacherById() {
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      List<Student> students = mapper.getStudents();
      for (Student student : students) {
        System.out.println(student);
      }
      sqlSession.close();
    }
    

执行测试方法,我们看输出的结果:

image-20210901163134222

虽然我们查出了所有的学生信息,但是其中teacher字段显示的是null,如果这里需要展示老师的姓名,该怎样处理呢?

10.3、按照结果嵌套处理(关联查询方式)

在进行多对一查询之前,我们需要确立两个概念。多个学生对应一个老师,那么从学生的角度看,老师是一个关联association;相反的,从老师的角度看,老师拥有一个学生的集合collection

首先来看一种关联查询的方式,也是我比较好接受的方式:

StudentMapper中自然不用改变,仍是:

List<Student> getStudents();

在StudentMapper.xml中我们需要借助ResultMap来做一些操作了:

<select id="getStudents" resultMap="StudentWithTeacher">
  select s.id sid, s.name sname, t.name tname
  from student s, teacher t
  where s.tid = t.id
</select>
<!--  如果直接使用sql语句来查询,这不难实现,我们通过上面的语句就可以实现,我们把它写在select中  -->
<resultMap id="StudentWithTeacher" type="Student">
  <result property="id" column="sid"/>
  <result property="name" column="sname"/>
  <association property="teacher" javaType="Teacher">
    <result property="name" column="tname"/>
  </association>
</resultMap>
<!--  
    接下来我们需要对sql语句中的字段作映射:
    1. 前面两个result标签,分别定义了查询的id, name分别对应了sql中的sid和sname
    2. 然后我们使用了前面定义的association概念,从学生的角度和老师的关系是一个关联,
      设置teacher对应的Java对象是Teacher, 然后设置teacher对象的name属性对应了sql中的tname 
-->

这样我们再执行前面的测试方法,可以看到teacher的姓名正常的查询了出来:

image-20210901164915574

10.4、按照查询嵌套处理(子查询方式)

StudentMapper.xml

<select id="getStudents2" resultMap="StudentWithTeacher2">
  select * from student
</select>
<resultMap id="StudentWithTeacher2" type="Student">
  <result property="id" column="id"/>
  <result property="name" column="name"/>
  <!-- 复杂的对象需要特殊处理:对象 association 集合 collection -->
  <association property="teacher" javaType="Teacher" column="tid" select="getTeacher"/>
  <!-- 同样的使用association关联,设置关联的对象是Teacher,对应的sql字段是tid,在select中还设置了另一个select,相当与子查询的语句 -->
</resultMap>
<select id="getTeacher" resultType="Teacher">
  select * from teacher where id = #{id}
</select>

个人认为这种方式没有关联查询好理解。

11、一对多处理

环境准备

与多对一相对的,我们来看下一对多的处理方式。同样的,首先准备下环境信息,环境配置和上面多对一基本相同,有区别的是实体类的部分。

pojo/Student

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}

pojo/Teacher

@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
}

写一个简单的测试方法,确保可以正常查询数据,即可继续下面的内容。

业务场景

我们的业务场景是:从Teacher表中查询老师的信息,并且将老师的所有学生信息都查询出来。

首先写一个最简单的查询,从老师表中查询出所有的老师信息:

TeacherMapper.java

Teacher getTeacherById(@Param("tid") int id);

TeacherMapper.xml

<select id="getTeacherById" resultType="Teacher">
  select * from teacher where id = ${tid};
</select>

MyTest.java

@Test
public void test(){
  SqlSession sqlSession = MybatisUtils.getSqlSession();
  TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
  Teacher teacher = mapper.getTeacherById(1);
  System.out.println(teacher);
  sqlSession.close();
}

这段代码都已经相当熟悉了,我们看查询出的结果:

image-20210906103407719

可以看到和预期的结果一样,老师的信息查询了出来,但是其中学生的信息显示为null,当然接下里就是处理学生为null的问题。

按照查询嵌套处理

和多对一的时候一样,直接上代码:

TeacherMapper.xml

<select id="getTeacherById" resultMap="TeacherStudent">
  select t.id tid, t.name tname, s.id sid, s.name sname
  from teacher t, student s
  where t.id = s.tid
  and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
  <result property="id" column="tid"/>
  <result property="name" column="tname"/>
  <collection property="students" ofType="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <result property="tid" column="tid"/>
  </collection>
</resultMap>

查询结果,学生信息正确的查询了出来:

image-20210906111016547

其中需要注意的是:

  1. ResultMap中的collection,因为这里一个老师下有很多的学生,所以使用集合collection来表示
  2. 由于collection中对应的是一个Student集合(即一个列表),在这里没有用JavaType来指定实体类型,改成使用ofType来实现

按照结果结果嵌套查询

TeacherMapper.java

Teacher getTeacherById2(@Param("tid") int id);

TeacherMapper.xml

<select id="getTeacherById2" resultMap="TeacherStudent2">
  select * from teacher where id = #{tid};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
  <collection property="students" javaType="ArrayList" ofType="Student"
              select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
  select * from student where tid = #{tid}
</select>

使用上看子查询的方式也可以正常查询出来。

小结

  1. 关联:association
  2. 集合:collection
  3. javaType & ofType
    • javaType用来指定实体类中属性的类型
    • ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型。

注意点:

  1. 保证SQL的可读性,尽量保证通俗易懂
  2. 注意多对一和一对多种,属性名和字段的问题
  3. 问题排查可借助日志,建议使用Log4j
posted @ 2021-09-12 21:54  LucaZ  阅读(129)  评论(0编辑  收藏  举报