MyBatis 复杂查询环境搭建

本文围绕MyBatis复杂查询环境搭建展开,介绍多对一和一对一关系处理。阐述结果映射作用及最佳实践,强调单元测试重要性。详细说明测试环境搭建步骤,对比按照查询嵌套和结果嵌套处理方式,最后总结注意点并提及面试常考的MySQL相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

复杂查询环境搭建

多对一

  • 多个学生对应一个老师
  • 对于学生这边而言,多对一 — 关联 — 多个学生关联一个老师
    • association — 一个复杂类型的关联;许多结果将包装成这种类型
  • 对于老师而言,一对多 — 集合 — 一个老师有多个学生
    • collection — 一个复杂类型的集合

结果映射(resultMap)

  • constructor - 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator – 使用结果值来决定使用哪个 resultMap
    • case – 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

最佳实践 最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次性创建像上面示例那么巨大的结果映射,不仅容易出错,难度也会直线上升。 所以,从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。

测试环境的搭建
  1. 导入Lombok插件
  2. 新建实体类Teacher、Student
  3. 建立Mapper接口
  4. 建立Mapper.xml文件
  5. 在核心配置文件中绑定、注册我们的Mapper接口或者文件(方式很多,随心选)
  6. 测试查询是否成功

准备环境:

  1. 创建数据库

    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. 新建module,创建实体类

    package xyz.luck1y.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Teacher {
        private int id;
        private String name;
    }
    
    package xyz.luck1y.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
    
        private int id;
        private String name;
    
        // 学生需要关联一个老师
        private Teacher teacher;
    }
    
  3. 新建Mapper接口

    package xyz.luck1y.dao;
    
    public interface StudentMapper {
    }
    
    package xyz.luck1y.dao;
    
    public interface TeacherMapper {
    }
    
  4. 新建mapper映射文件

    我们发现dao里内容比较多,所以将映射文件建立在resources包中,切记同包名,在resources文件夹新建包的时候要按照这样的格式写:xyz/luck1y/dao,不能使用.要用/,这样的话,最后生成的文件中,xml所在的包和接口所在的包会合并在一起~

    在新建映射文件的时候,我们可以把mybatis-config的内容复制过来,然后将所有的configuration改成mapper

    然后配置namespace,选择要绑定的接口,一一对应

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="xyz.luck1y.dao.TeacherMapper">
    
    </mapper>
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="xyz.luck1y.dao.StudentMapper">
    
    </mapper>
    
  5. 在核心配置文件中进行绑定

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
    
        <!--引入外部配置文件-->
        <properties resource="db.properties"/>
        
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>
        
        <!--可以给实体类起别名-->
        <typeAliases>
            <package name="xyz.luck1y.pojo"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper class="xyz.luck1y.dao.TeacherMapper"></mapper>
            <mapper class="xyz.luck1y.dao.StudentMapper"></mapper>
            <mapper resource="xyz/luck1y/dao/TeacherMapper.xml"></mapper>
            <mapper resource="xyz/luck1y/dao/StudentMapper.xml"></mapper>
        </mappers>
    
    </configuration>
    
  6. 测试类:

    public class MyTest {
        @Test
        public void teacherTest(){
    
        }
    }
    

开始测试项目:

  1. Teacher接口

    package xyz.luck1y.dao;
    
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import xyz.luck1y.pojo.Teacher;
    
    public interface TeacherMapper {
        @Select("select * from teacher where id = #{tid}")
        Teacher getTeacher(@Param("tid") int id);
    }
    
  2. 测试类:

    import org.apache.ibatis.session.SqlSession;
    import org.junit.jupiter.api.Test;
    import xyz.luck1y.dao.TeacherMapper;
    import xyz.luck1y.pojo.Teacher;
    import xyz.luck1y.utils.MybatisUtils;
    
    public class MyTest {
        @Test
        public void teacherTest(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
            Teacher teacher = mapper.getTeacher(1);
            System.out.println(teacher);
            sqlSession.close();
        }
    }
    
  3. 运行结果:

多对一的处理

回顾MySQL多对一的查询方式:

  • 子查询
  • 联表查询

子查询对应的就是按照查询嵌套处理

联表查询对应的就是按照结果嵌套处理

按照查询嵌套处理
  1. 接口:

    package xyz.luck1y.dao;
    
    import xyz.luck1y.pojo.Student;
    
    import java.util.List;
    
    public interface StudentMapper {
    
        // 查询所有的学生信息,以及对应的老师的信息
        public List<Student> getStudent();
    
    }
    
  2. mapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="xyz.luck1y.dao.StudentMapper">
    
        <!--
        方式一:子查询 按照查询嵌套处理
        思路:
        1.查询所有的学生信息
        2.根据查询出来的学生tid,寻找对应的老师
        -->
        <!--如何将这两个查询语句关联起来-->
        <select id="getStudent" resultMap="StudentTeacher">
            select * from mybatis.student;
        </select>
        <resultMap id="StudentTeacher" type="Student">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
        <!--复杂的属性(对象或集合),我们需要单独处理
            对象;association
            集合:collection
        -->
        <!--由于teacher属性是一个Java的Teacher对象,而不是一个简单的属性,所以需要声明javaType,并且用select查出这个对象的信息-->
            <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
        </resultMap>
    
        <select id="getTeacher" resultType="Teacher">
            <!--注意这里的#{}里可以随意,它会自动匹配成column="tid"(上面使用了结果映射)-->
            select * from mybatis.teacher where id = #{id};
        </select>
    
    </mapper>
    
  3. 测试类:

    @Test
    public void testStudent(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudent();
        for (Student student : studentList) {
            System.out.println(student);
        }
        sqlSession.close();
    }
    
  4. 测试结果:

按照结果嵌套处理
  1. 接口

    public List<Student> getStudent2();
    
  2. mapper.xml

    <!--方式二:联表查询 按照结果嵌套处理-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid, s.name sname, t.name tname from student s,teacher t where s.tid = t.id;
    </select>
    <resultMap id="StudentTeacher2" type="Student">
        <!--起别名之后,对应的数据库中的列要用别名-->
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
    
  3. 测试类:

    @Test
    public void testStudent2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudent2();
        for (Student student : studentList) {
            System.out.println(student);
        }
        sqlSession.close();
    }
    
  4. 测试结果:

一对多

一个老师拥有多名学生,对于老师而言就是一对多的关系

测试环境的搭建

过程和刚才一样

实体类的变化:

学生类:学生成为单独的个体,不需要关联老师

package xyz.luck1y.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private int id;
    private String name;
    private int tid;
}

老师类:老师是一对多,需要包含每一个学生

package xyz.luck1y.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;

    // 一个老师拥有多个学生
    private List<Student> students;
}

测试:

package xyz.luck1y.dao;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import xyz.luck1y.pojo.Teacher;

import java.util.List;

public interface TeacherMapper {
    // 获取所有老师
    List<Teacher> getTeacher();
}
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher;
</select>
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import xyz.luck1y.dao.TeacherMapper;
import xyz.luck1y.pojo.Teacher;
import xyz.luck1y.utils.MybatisUtils;

import java.util.List;

public class MyTest {

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teacher = mapper.getTeacher();
        for (Teacher teacher1 : teacher) {
            System.out.println(teacher1);
        }
        sqlSession.close();
    }
}

测试结果:

可以看到查询没什么问题,只是student为null,于是进一步测试~

一对多的处理
按照查询嵌套处理
  1. 接口:

    Teacher getTeacher2(@Param("tid") int id);
    
  2. mapper.xml:

    <!--方式一:子查询 按查询嵌套处理-->
    <select id="getTeacher2" resultMap="TeacherStudent2">
        select * from teacher where id = #{tid}
    </select>
    <resultMap id="TeacherStudent2" type="Teacher">
        <result property="id" column="id"/>
        <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid = #{tid};
    </select>
    
  3. 测试类:

    @Test
    public void test2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);
        System.out.println(teacher);
        sqlSession.close();
    }
    
  4. 测试结果:

    /* 查询结果:
    Teacher(
    id=1,
    name=秦老师,
    students=
    [Student(id=1, name=小明, tid=1),
    Student(id=2, name=小红, tid=1),
    Student(id=3, name=小张, tid=1),
    Student(id=4, name=小李, tid=1),
    Student(id=5, name=小王, tid=1)]
    )
    */
    
按照结果嵌套处理
  1. 接口

    // 获取指定老师下的所有学生信息
    Teacher getTeacher(@Param("tid") int id);
    
  2. mapper.xml

    <!--方式二:联表查询 按结果嵌套查询-->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid, s.name sname, t.name tname, t.id tid
        from student s, teacher t
        where s.tid = t.id and t.id = #{tid};
    </select>
    
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!--javaType:指定属性的类型
            集合中的泛型信息,我们使用ofType获取
            -->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
    
  3. 测试类:

    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);
        System.out.println(teacher);
        sqlSession.close();
    }
    
  4. 测试结果:

    /*Teacher(
    id=1,
    name=秦老师,
    students=
    [Student(id=1, name=小明, tid=1),
    Student(id=2, name=小红, tid=1),
    Student(id=3, name=小张, tid=1),
    Student(id=4, name=小李, tid=1),
    Student(id=5, name=小王, tid=1)]
    )
    */
    

小结多对一和一对多

多个学生 — 一个老师

  • 对于学生而言,多对一关联 — 多个学生关联一个老师,association — 一个复杂类型的关联;许多结果包装成这种类型。
  • 对于老师而言,一对多集合 — 一个老师有多名学生,collection — 一个复杂类型的集合
  • javaType:用来指定实体类中属性的类型
  • ofType:用来指定映射到List或集合中泛型的约束的类型

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂,易于程序维护
  • 注意一对多和多对一中属性名和字段的问题
  • 如果问题不好排查,可以使用日志

结果映射部分:

结果映射(resultMap)

  • constructor - 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator – 使用结果值来决定使用哪个 resultMap
    • case – 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

最佳实践 最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次性创建像上面示例那么巨大的结果映射,不仅容易出错,难度也会直线上升。 所以,从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。

面试必问问题:

  • MySQL引擎
  • InnoDB底层原理
  • 索引
  • 索引优化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luck1y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值