【MyBatis】六、多对一和一对多

10、多对一情景

情景:一个教师负责教多个学生。

  • 站在学生的角度,学生对教师是多对一关联关系;

  • 站在教师的角度,教师对学生是一对多集合关系。

需求:查询所有学生,及相关联的教师信息。

10.1、环境搭建

1、数据库表

teacher表

【MyBatis】六、多对一和一对多

student表

【MyBatis】六、多对一和一对多

2、实体类

根据需求,学生表中的教师设为实体类,方便引用。

以下只列出实体类属性,自行添加 getter和 setter、构造方法等。

Teacher类

/**
 * 教师ID
 */
private int id;
/**
 * 教师姓名
 */
private String name;

public Teacher() {
}

Student类

/**
 * 学生ID
 */
private int id;
/**
 * 学生姓名
 */
private String name;
/**
 * 关联教师
 */
private Teacher teacher;

3、Mapper

先看看直接查询出来是什么结果,再分析该如何处理。

StudentMapper

/**
 * 查询所有学生,以及关联教师信息
 * @return 学生列表
 */
List<Student> listStudents();

StudentMapper.xml

<select id="listStudents" resultType="student">
    select *
    from mybatis.student;
</select>

注:

  1. Mapper.xml建议存放在resources目录下,与接口同级。
  2. 需要在MyBatis配置文件注册 MapperMapper.xml

【MyBatis】六、多对一和一对多

4、JUnit

@Test
public void testListStudents() {
    // 获取SqlSession实例
	SqlSession sqlSession = MyBatisUtils.getSqlSession();
    // 获取Mapper
	StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    // 执行方法
    List<Student> students = mapper.listStudents();

    for (Student student : students) {
        System.out.println(student);
    }
    // 关闭连接
    sqlSession.close();
}

查询结果:teacher为null

【MyBatis】六、多对一和一对多

原因

  1. 数据库中的student表,字段为tid:关联教师的ID;
  2. 实体类中的Student类,属性为teacher:关联教师的对象引用(实现逻辑外键);
  3. 它们之间无法自动映射,所以需要通过Mapper配置结果集映射;
  4. 在学生的角度,学生与教师是多对一的关系,即关联(association)关系。

10.2、多对一处理

站在学生的角度,学生对教师是多对一关联关系。

有以下两种方法:

1、子查询

StudentMapper.xml

<select id="listStudents" resultMap="studentMap">
    select id, name, tid
    from mybatis.student;
</select>

<resultMap id="studentMap" type="student">
    <!-- 数据库字段和实体类属性能自动映射,可以删去 -->
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 学生对教师:一对多,是关联关系 -->
    <association property="teacher" column="tid" javaType="teacher" select="getTeacherById"/>
</resultMap>

<select id="getTeacherById" resultType="teacher">
    select id, name
    from mybatis.teacher
    where id = #{tid};
</select>

分析:

  • 原本的select标签中的resultType已无法满足需求,所以应该用resultMap结果映射。

  • 创建一个resultMap,用于处理数据库实体类的映射关系:

    1. result标签:处理简单映射,若数据库字段和实体类属性能自动映射,无需显式配置;
      • property:实体类的属性名;
      • column:数据库表的字段名。
    2. association标签:处理复杂映射,代表一对多的关联关系;
      • property:实体类的属性名;
      • column:数据库表的字段名;
      • javaType:属性的Java类型,使用全限类名或别名;
      • select:引用子查询语句,Teacher需要通过学生表的tid查出;
  • 相当于以下SQL语句:

    select s.id   as s_id,
           s.name as s_name,
           s.tid  as t_id,
           (select name
           from teacher
           where id = t_id)as t_name
    from mybatis.student as s;
    

2、联表查询

StudenMapper.xml

<select id="listStudents" resultMap="studentMap">
    select s.id   as s_id,
           s.name as s_name,
           s.tid  as t_id,
           t.name as t_name
    from mybatis.student as s
             left join mybatis.teacher as t
                       on s.tid = t.id
</select>

<resultMap id="studentMap1" type="student">
    <result property="id" column="s_id"/>
    <result property="name" column="s_name"/>

    <association property="teacher" javaType="teacher">
        <result property="id" column="t_id"/>
        <result property="name" column="t_name"/>
    </association>
</resultMap>

分析

  • 原本的select标签中的resultType已无法满足需求,所以应该用resultMap结果映射;

  • 创建一个resultMap,用于处理数据库实体类的映射关系:

    1. 使用联表查询,select子句后的字段可能出现同名(如两张表中都有name字段)。因此需要为每个字段起别名,并在resultMap中配置。
    2. result标签:处理简单映射,由于SQL语句中起了别名,所以需要显式配置;
      • property:实体类的属性名;
      • column:数据库表的字段名。
    3. association标签:处理复杂映射,代表一对多的关联关系;
      • property:实体类的属性名;
      • javaType:属性的Java类型,使用全限类名或别名;
      • 相比子查询:无需配置columnselect两个属性,但需要在内部配置result标签,将查到的teacher的字段映射到Teacher类的属性上。
  • 相当于以下SQL语句:

    select s.id   as s_id,
           s.name as s_name,
           s.tid  as t_id,
           t.name as t_name
    from mybatis.student as s
             left join mybatis.teacher as t
                       on s.tid = t.id;
    

3、对比

建议:使用联表查询!

子查询

  1. 查出student表包含的属性:属于Student本身的属性(id、name),跟Teacher有关的属性(逻辑外键tid);
  2. 使用resultMap映射:tidteacher
  3. 通过子查询,将teacher表的属性查出并封装到Teacher对象中。

联表查询

  1. 查出所有属性:属于Student本身的属性(id、name),跟Teacher有关的属性(逻辑外键tid),并通过联表查询查出属于Teacher本身的属性(name);
  2. 为字段起别名,方便resultMap结果映射;
  3. 使用resultMap结果集映射:所有字段和属性。

11、一对多情景

需求:查询一个教师,及包含的学生集合

11.1、环境搭建

1、数据库表

无需变动,同上。

2、实体类

根据需求:

  • 需要查询一个教师及其包含学生的集合,所以为 Teacher类增加一个学生集合的属性。

  • 不再那么关注Student类的属性中的 Teacher引用,所以修改为 tid即可。

Teacher类

/**
 * 教师ID
 */
private int id;
/**
 * 教师姓名
 */
private String name;
/**
 * 学生集合
 */
private List<Student> studentList;

Student类

/**
 * 学生ID
 */
private int id;
/**
 * 学生姓名
 */
private String name;
/**
 * 关联教师ID
 */
private int tid;

3、Mapper

先看看直接查询出来是什么结果,再分析该如何处理。

TeacherMapper

/**
 * 通过ID查询教师
 * @param id 教师ID
 * @return 教师
 */
Teacher getTeacherById(int id);

TeacherMapper.xml

<select id="getTeacherById" resultType="teacher">
    select * from mybatis.teacher where id = #{id}
</select>

4、JUnit

@Test
public void testGetTeacherById(){
    // 获取SqlSession实例
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    // 获取Mapper
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    // 执行方法
    Teacher teacher = mapper.getTeacherById(1);

    System.out.println(teacher);
    
    // 关闭连接
    sqlSession.close();
}

查询结果:学生列表为空。

【MyBatis】六、多对一和一对多

原因

  1. 数据库teacher表中没有关于student的字段,无法映射,所以需要通过Mapper配置结果集映射;
  2. 在教师的角度,教师与学生是一对多的关系,即集合(collection)关系。

11.2、一对多处理

站在教师的角度,教师对学生是一对多集合关系。

有以下两种方法:

1、子查询

TeacherMapper.xml

<select id="getTeacherById1" resultMap="teacherMap1">
    select id, name
    from mybatis.teacher
    where id = #{id}
</select>

<resultMap id="teacherMap1" type="teacher">
    <!-- column代表传递给子查询的值 -->
    <collection property="studentList" column="id" javaType="List" ofType="student" select="listStudentsByTid"/>
</resultMap>

<select id="listStudentsByTid" resultType="student">
    select *
    from mybatis.student
    where tid = #{id}
</select>

分析:

  • 原本的select标签中的resultType已无法满足需求,所以应该用resultMap结果映射。

  • 创建一个resultMap,用于处理数据库实体类的映射关系:

    1. result标签:处理简单映射,若数据库字段和实体类属性能自动映射,无需显式配置;

    2. collection标签:处理复杂映射,代表多对一的关联关系;

      • property:实体类的属性名;

      • column:传递给子查询的参数

        思路:这里本应该填写数据库表中的有关学生的字段,但是teacher表中没有相应字段,所以先空着。写到后面发现,没有地方给子查询语句传参,所以此处填写的值就是给子查询语句传递参数的。

      • javaType:属性的Java类型,使用全限类名或别名;

      • select:引用子查询语句,Teacher需要通过学生表的tid查出;

2、联表查询

TeacherMapper

<select id="getTeacherById" resultMap="teacherMap">
    select t.id as t_id,
           t.name as t_name,
           s.id as s_id,
           s.name as s_name
    from mybatis.teacher as t
             left join mybatis.student s
                       on t.id = s.tid
    where t.id = #{id}
    </select>

<resultMap id="teacherMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
    <collection property="studentList" javaType="ArrayList" ofType="student">
        <result property="id" column="s_id"/>
        <result property="name" column="s_name"/>
        <result property="tid" column="t_id"/>
    </collection>
</resultMap>

分析

  • 原本的select标签中的resultType已无法满足需求,所以应该用resultMap结果映射;

  • 创建一个resultMap,用于处理数据库实体类的映射关系:

    1. 使用联表查询,select子句后的字段可能出现同名(如两张表中都有name字段)。因此需要为每个字段起别名,并在resultMap中配置。
    2. result标签:处理简单映射,由于SQL语句中起了别名,所以需要显式配置;
      • property:实体类的属性名;
      • column:数据库表的字段名。
    3. collection标签:处理复杂映射,代表多对一的关联关系;
      • property:实体类的属性名;
      • javaType:属性的Java类型,使用全限类名或别名;
      • ofType:映射到List集合中指定的泛型类型,使用全限类名或别名;
      • 相比子查询:无需配置columnselect两个属性,但需要在内部配置result标签,将查到的student的字段映射到Student类的属性上。
  • 相当于以下SQL语句

    select t.id as t_id,
           t.name as t_name,
           s.id as s_id,
           s.name as s_name
    from teacher as t
        left join student s
            on t.id = s.tid
    where t.id = ?
    

3、对比

建议:使用联表查询!

子查询:

  1. 查出student表包含的属性:属于Student本身的属性(id、name),跟Teacher有关的属性(逻辑外键tid);
  2. 使用resultMap映射:tidteacher
  3. 通过子查询,将teacher的属性查出并封装到Teacher对象中。

联表查询:

  1. 查出所有属性:属于Teacher本身的属性(id、name),并通过联表查询查出Student的属性(name);
  2. 为字段起别名,方便resultMap结果映射;
  3. 使用resultMap结果集映射:所有字段和属性。

【MyBatis】六、多对一和一对多

上一篇:卷积扩展知识


下一篇:点击按钮,回到页面顶部的5种写法