复杂查询环境搭建
多对一

- 多个学生对应一个老师
- 对于学生这边而言,多对一 — 关联 — 多个学生关联一个老师
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 时也能起到很大的作用。
测试环境的搭建
- 导入Lombok插件
- 新建实体类Teacher、Student
- 建立Mapper接口
- 建立Mapper.xml文件
- 在核心配置文件中绑定、注册我们的Mapper接口或者文件(方式很多,随心选)
- 测试查询是否成功
准备环境:
-
创建数据库
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');
-
新建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; }
-
新建Mapper接口
package xyz.luck1y.dao; public interface StudentMapper { }
package xyz.luck1y.dao; public interface TeacherMapper { }
-
新建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>
-
在核心配置文件中进行绑定
<?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>
-
测试类:
public class MyTest { @Test public void teacherTest(){ } }
开始测试项目:
-
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); }
-
测试类:
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(); } }
-
运行结果:
多对一的处理
回顾MySQL多对一的查询方式:
- 子查询
- 联表查询
子查询对应的就是按照查询嵌套处理
联表查询对应的就是按照结果嵌套处理
按照查询嵌套处理
-
接口:
package xyz.luck1y.dao; import xyz.luck1y.pojo.Student; import java.util.List; public interface StudentMapper { // 查询所有的学生信息,以及对应的老师的信息 public List<Student> getStudent(); }
-
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>
-
测试类:
@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(); }
-
测试结果:
按照结果嵌套处理
-
接口
public List<Student> getStudent2();
-
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>
-
测试类:
@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(); }
-
测试结果:
一对多
一个老师拥有多名学生,对于老师而言就是一对多的关系
测试环境的搭建
过程和刚才一样
实体类的变化:
学生类:学生成为单独的个体,不需要关联老师
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,于是进一步测试~
一对多的处理
按照查询嵌套处理
-
接口:
Teacher getTeacher2(@Param("tid") int id);
-
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>
-
测试类:
@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(); }
-
测试结果:
/* 查询结果: 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)] ) */
按照结果嵌套处理
-
接口
// 获取指定老师下的所有学生信息 Teacher getTeacher(@Param("tid") int id);
-
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>
-
测试类:
@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(); }
-
测试结果:
/*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底层原理
- 索引
- 索引优化